Integrated Triplane Gaussian Transformers & Better 3DGS Import & Export
This commit is contained in:
+10
-1
@@ -155,4 +155,13 @@ cython_debug/
|
||||
diff-gaussian-rasterization/
|
||||
|
||||
# Nvdiffrast
|
||||
nvdiffrast/
|
||||
nvdiffrast/
|
||||
|
||||
# Model files
|
||||
*.ckpt
|
||||
*.bin
|
||||
*.pth
|
||||
*.safetensors
|
||||
|
||||
# Ignore test outputs
|
||||
outputs/
|
||||
@@ -14,6 +14,50 @@ ps: I'll show some generated result soon
|
||||
- I tried install this package with ComfyUI embed python env first, but I can't find a way to build CUDA related libraries, e.g. diff-gaussian-rasterization, nvdiffrast, simple-knn.
|
||||
- Torch version: 2.1.2+cu121/V.2.1.2+cu118
|
||||
|
||||
## Currently support:
|
||||
- For use case please check [example workflows](./_Example_Workflows/)
|
||||
- **Note:** you need to put [Example Inputs Files & Folders](_Example_Workflows/_Example_Inputs_Files/) under ComfyUI Root Directory\ComfyUI\input folder before you can run the example workflow
|
||||
- Triplane Gaussian Transformers:
|
||||
- Enable single image to 3D Gaussian, then you can use Deep Marching Tetrahedrons node to convert it to mesh
|
||||
|
||||
|
||||
- Stack Orbit Camera Poses node to automatically generate all range of camera pose combinations
|
||||
- You can use it to conditioning the [StableZero123 (You need to Download the checkpoint first)](https://comfyanonymous.github.io/ComfyUI_examples/3d/), with full range of camera poses in one prompt pass
|
||||
- You can use it to generate the orbit camera poses and directly input to other 3D process node (e.g. GaussianSplatting and BakeTextureToMesh)
|
||||
- Example usage:
|
||||
|
||||
<img src="_Example_Workflows/_Example_Outputs/Cammy_Cam_Rotate_Clockwise_Camposes.png" width="256"/> <img src="_Example_Workflows/_Example_Outputs/Cammy_Cam_Rotate_Counter_Clockwise_Camposes.png" width="256"/>
|
||||
<img src="_Example_Workflows/_Example_Outputs/Cammy_Cam_Rotate_Clockwise.gif" width="256"/> <img src="_Example_Workflows/_Example_Outputs/Cammy_Cam_Rotate_Counter_Clockwise.gif" width="256"/>
|
||||
- Load 3D file (.obj, .ply, .glb)
|
||||
- Also have node that support save and load 3DGS .ply file
|
||||
- 3D Gaussian Splatting, with:
|
||||
- [Improved Differential Gaussian Rasterization](https://github.com/ashawkey/diff-gaussian-rasterization)
|
||||
- Better Compactness-based Densification method from [Gsgen](https://gsgen3d.github.io/),
|
||||
- Support initialize gaussians from given 3D mesh (Optional)
|
||||
- Support mini-batch optimazation
|
||||
- Multi-View images as inputs
|
||||
- Export to .ply support
|
||||
|
||||
- Bake Multi-View images into UVTexture of given 3D mesh using [Nvdiffrast](https://github.com/NVlabs/nvdiffrast), supports:
|
||||
- Export to .obj, .ply, .glb
|
||||
|
||||
- Deep Marching Tetrahedrons
|
||||
- Allow convert 3DGS .ply file to 3D mesh <br>
|
||||
*Note: I didn't spent time to turn the hyperprameters yet, the result will be improved in the future!*
|
||||
|
||||
## Roadmap:
|
||||
- [x] Add DMTet algorithm to allow convertion from points cloud(Gaussian/.ply) to mesh (.obj, .ply, .glb)
|
||||
|
||||
- [x] Integrate [Triplane Meets Gaussian Splatting: Fast and Generalizable Single-View 3D Reconstruction with Transformers](https://github.com/VAST-AI-Research/TriplaneGaussian)
|
||||
|
||||
- [ ] Add interactive 3D UI inside ComfuUI to visulaize training and generated results for 3D representations
|
||||
|
||||
- [ ] Improve DMTet result & add support to training it with images(RGB, Alpha, Normal Map)
|
||||
|
||||
- [ ] Add a general SDS/VSD Optimization algorithm to allow training 3D representations with diffusion model, *The real fun begins here* ;)
|
||||
|
||||
- [ ] Add a few best Nerf algorithms (No idea yet, [instant-ngp](https://github.com/NVlabs/instant-ngp) maybe?)
|
||||
|
||||
## Install:
|
||||
|
||||
Assume you have already downloaded [ComfyUI](https://github.com/comfyanonymous/ComfyUI)
|
||||
@@ -56,53 +100,21 @@ install.bat
|
||||
|
||||
git clone --recursive https://github.com/NVlabs/nvdiffrast/`
|
||||
pip install ./nvdiffrast
|
||||
|
||||
# Install pointnet2_ops
|
||||
cd tgs/models/snowflake/pointnet2_ops_lib && python setup.py install && cd ../../../../
|
||||
|
||||
# Install pytorch_scatter
|
||||
pip install git+https://github.com/rusty1s/pytorch_scatter.git
|
||||
|
||||
# Install pytorch3d
|
||||
pip install git+https://github.com/facebookresearch/pytorch3d.git@stable
|
||||
```
|
||||
|
||||
## Run:
|
||||
Copy the files inside folder [__New_ComfyUI_Bats](./_New_ComfyUI_Bats/) to your ComfyUI root directory, and double click run_nvidia_gpu_miniconda.bat to start ComfyUI!
|
||||
- Alternatively you can just activate the Conda env: python_miniconda_env\ComfyUI, and go to your ComfyUI root directory then run command `python ./ComfyUI/main.py`
|
||||
|
||||
## Currently support:
|
||||
- For use case please check [example workflows](./_Example_Workflows/)
|
||||
- **Note:** you need to put [Example Inputs Files & Folders](_Example_Workflows/_Example_Inputs_Files/) under ComfyUI Root Directory\ComfyUI\input folder before you can run the example workflow
|
||||
- Stack Orbit Camera Poses node to automatically generate all range of camera pose combinations
|
||||
- You can use it to conditioning the [StableZero123 (You need to Download the checkpoint first)](https://comfyanonymous.github.io/ComfyUI_examples/3d/), with full range of camera poses in one prompt pass
|
||||
- You can use it to generate the orbit camera poses and directly input to other 3D process node (e.g. GaussianSplatting and BakeTextureToMesh)
|
||||
- Example usage:
|
||||
|
||||
<img src="_Example_Workflows/_Example_Outputs/Cammy_Cam_Rotate_Clockwise_Camposes.png" width="256"/> <img src="_Example_Workflows/_Example_Outputs/Cammy_Cam_Rotate_Counter_Clockwise_Camposes.png" width="256"/>
|
||||
<img src="_Example_Workflows/_Example_Outputs/Cammy_Cam_Rotate_Clockwise.gif" width="256"/> <img src="_Example_Workflows/_Example_Outputs/Cammy_Cam_Rotate_Counter_Clockwise.gif" width="256"/>
|
||||
- Load 3D file (.obj, .ply, .glb)
|
||||
- Also have node that support save and load 3DGS .ply file
|
||||
- 3D Gaussian Splatting, with:
|
||||
- [Improved Differential Gaussian Rasterization](https://github.com/ashawkey/diff-gaussian-rasterization)
|
||||
- Better Compactness-based Densification method from [Gsgen](https://gsgen3d.github.io/),
|
||||
- Support initialize gaussians from given 3D mesh (Optional)
|
||||
- Support mini-batch optimazation
|
||||
- Multi-View images as inputs
|
||||
- Export to .ply support
|
||||
|
||||
- Bake Multi-View images into UVTexture of given 3D mesh using [Nvdiffrast](https://github.com/NVlabs/nvdiffrast), supports:
|
||||
- Export to .obj, .ply, .glb
|
||||
|
||||
- Deep Marching Tetrahedrons
|
||||
- Allow convert 3DGS .ply file to 3D mesh <br>
|
||||
*Note: I didn't spent time to turn the hyperprameters yet, the result will be improved in the future!*
|
||||
|
||||
## Roadmap:
|
||||
- [x] Add DMTet algorithm to allow convertion from points cloud(Gaussian/.ply) to mesh (.obj, .ply, .glb)
|
||||
|
||||
- [ ] Integrate [Triplane Meets Gaussian Splatting: Fast and Generalizable Single-View 3D Reconstruction with Transformers](https://github.com/VAST-AI-Research/TriplaneGaussian)
|
||||
|
||||
- [ ] Add interactive 3D UI inside ComfuUI to visulaize training and generated results for 3D representations
|
||||
|
||||
- [ ] Improve DMTet result & add support to training it with images(RGB, Alpha, Normal Map)
|
||||
|
||||
- [ ] Add a general SDS/VSD Optimization algorithm to allow training 3D representations with diffusion model, *The real fun begins here* ;)
|
||||
|
||||
- [ ] Add a few best Nerf algorithms (No idea yet, [instant-ngp](https://github.com/NVlabs/instant-ngp) maybe?)
|
||||
|
||||
|
||||
## Tips
|
||||
* The world & camera coordinate system is the same as OpenGL:
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"last_node_id": 19,
|
||||
"last_link_id": 22,
|
||||
"last_node_id": 21,
|
||||
"last_link_id": 26,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 10,
|
||||
@@ -63,191 +63,6 @@
|
||||
"Node name for S&R": "MaskToImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"type": "[ComfyUI-3D] Save 3DGS",
|
||||
"pos": [
|
||||
2122,
|
||||
54
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "raw_3DGS",
|
||||
"type": "GS_RAW",
|
||||
"link": 21
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Save 3DGS"
|
||||
},
|
||||
"widgets_values": [
|
||||
"MeshTest\\3DGS_01.ply"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "InvertMask",
|
||||
"pos": [
|
||||
746,
|
||||
-14
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 26
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": 2
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
8,
|
||||
18
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "InvertMask"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "[ComfyUI-3D] Generate Orbit Camera Poses",
|
||||
"pos": [
|
||||
830,
|
||||
132
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 200
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "reference_images",
|
||||
"type": "IMAGE",
|
||||
"link": 1
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "orbit_camposes",
|
||||
"type": "ORBIT_CAMPOSES",
|
||||
"links": [
|
||||
19
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Generate Orbit Camera Poses"
|
||||
},
|
||||
"widgets_values": [
|
||||
"([0:120], 1.75, 0, 0, 360)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"type": "[ComfyUI-3D] Gaussian Splatting",
|
||||
"pos": [
|
||||
1486,
|
||||
53
|
||||
],
|
||||
"size": {
|
||||
"0": 468.5999755859375,
|
||||
"1": 718
|
||||
},
|
||||
"flags": {
|
||||
"collapsed": false
|
||||
},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "reference_images",
|
||||
"type": "IMAGE",
|
||||
"link": 17
|
||||
},
|
||||
{
|
||||
"name": "reference_masks",
|
||||
"type": "MASK",
|
||||
"link": 18
|
||||
},
|
||||
{
|
||||
"name": "reference_orbit_camera_poses",
|
||||
"type": "ORBIT_CAMPOSES",
|
||||
"link": 19
|
||||
},
|
||||
{
|
||||
"name": "mesh_to_initialize_gaussian",
|
||||
"type": "MESH",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "raw_3DGS",
|
||||
"type": "GS_RAW",
|
||||
"links": [
|
||||
21
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Gaussian Splatting"
|
||||
},
|
||||
"widgets_values": [
|
||||
49.1,
|
||||
1000,
|
||||
1,
|
||||
10000,
|
||||
0.2,
|
||||
3,
|
||||
0,
|
||||
0,
|
||||
0.5,
|
||||
0.01,
|
||||
0.05,
|
||||
0.005,
|
||||
0.005,
|
||||
0.001,
|
||||
0.00002,
|
||||
0.02,
|
||||
500,
|
||||
5000,
|
||||
3,
|
||||
0.01,
|
||||
100,
|
||||
100000,
|
||||
100,
|
||||
700,
|
||||
0.01,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"type": "[ComfyUI-3D] Load 3D Mesh",
|
||||
@@ -283,6 +98,31 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
420,
|
||||
-320
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
246
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 22
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "VHS_LoadImages",
|
||||
@@ -303,8 +143,8 @@
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
1,
|
||||
17,
|
||||
22
|
||||
22,
|
||||
23
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
@@ -349,29 +189,197 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"type": "PreviewImage",
|
||||
"id": 8,
|
||||
"type": "InvertMask",
|
||||
"pos": [
|
||||
420,
|
||||
-320
|
||||
746,
|
||||
-14
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 26
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 22
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": 2
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
8,
|
||||
24
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
"Node name for S&R": "InvertMask"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "[ComfyUI-3D] Generate Orbit Camera Poses",
|
||||
"pos": [
|
||||
830,
|
||||
132
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 200
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "reference_images",
|
||||
"type": "IMAGE",
|
||||
"link": 1
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "orbit_camposes",
|
||||
"type": "ORBIT_CAMPOSES",
|
||||
"links": [
|
||||
25
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Generate Orbit Camera Poses"
|
||||
},
|
||||
"widgets_values": [
|
||||
"([0:120], 1.75, 0, 0, 360)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"type": "[ComfyUI-3D] Gaussian Splatting",
|
||||
"pos": [
|
||||
1381,
|
||||
54
|
||||
],
|
||||
"size": {
|
||||
"0": 443.4000244140625,
|
||||
"1": 758
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "reference_images",
|
||||
"type": "IMAGE",
|
||||
"link": 23
|
||||
},
|
||||
{
|
||||
"name": "reference_masks",
|
||||
"type": "MASK",
|
||||
"link": 24
|
||||
},
|
||||
{
|
||||
"name": "reference_orbit_camera_poses",
|
||||
"type": "ORBIT_CAMPOSES",
|
||||
"link": 25
|
||||
},
|
||||
{
|
||||
"name": "points_cloud",
|
||||
"type": "POINTCLOUD",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "ply_to_initialize_gaussian",
|
||||
"type": "GS_PLY",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "mesh_to_initialize_gaussian",
|
||||
"type": "MESH",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "gs_ply",
|
||||
"type": "GS_PLY",
|
||||
"links": [
|
||||
26
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Gaussian Splatting"
|
||||
},
|
||||
"widgets_values": [
|
||||
49.1,
|
||||
1000,
|
||||
3,
|
||||
10000,
|
||||
0.2,
|
||||
3,
|
||||
0,
|
||||
0,
|
||||
0.5,
|
||||
0.01,
|
||||
0.05,
|
||||
0.005,
|
||||
0.005,
|
||||
0.001,
|
||||
0.00002,
|
||||
0.02,
|
||||
500,
|
||||
5000,
|
||||
3,
|
||||
0.01,
|
||||
100,
|
||||
100000,
|
||||
100,
|
||||
700,
|
||||
0.01,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"type": "[ComfyUI-3D] Save 3DGS",
|
||||
"pos": [
|
||||
1994,
|
||||
55
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "gs_ply",
|
||||
"type": "GS_PLY",
|
||||
"link": 26
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Save 3DGS"
|
||||
},
|
||||
"widgets_values": [
|
||||
"3DGS_[time(%Y-%m-%d)].ply"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
@@ -407,38 +415,6 @@
|
||||
0,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
17,
|
||||
7,
|
||||
0,
|
||||
18,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
18,
|
||||
8,
|
||||
0,
|
||||
18,
|
||||
1,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
19,
|
||||
4,
|
||||
0,
|
||||
18,
|
||||
2,
|
||||
"ORBIT_CAMPOSES"
|
||||
],
|
||||
[
|
||||
21,
|
||||
18,
|
||||
0,
|
||||
17,
|
||||
0,
|
||||
"GS_RAW"
|
||||
],
|
||||
[
|
||||
22,
|
||||
7,
|
||||
@@ -446,6 +422,38 @@
|
||||
19,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
23,
|
||||
7,
|
||||
0,
|
||||
20,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
24,
|
||||
8,
|
||||
0,
|
||||
20,
|
||||
1,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
25,
|
||||
4,
|
||||
0,
|
||||
20,
|
||||
2,
|
||||
"ORBIT_CAMPOSES"
|
||||
],
|
||||
[
|
||||
26,
|
||||
20,
|
||||
0,
|
||||
21,
|
||||
0,
|
||||
"GS_PLY"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
|
||||
+213
-205
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"last_node_id": 22,
|
||||
"last_link_id": 25,
|
||||
"last_node_id": 24,
|
||||
"last_link_id": 29,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 10,
|
||||
@@ -64,150 +64,29 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"type": "[ComfyUI-3D] Save 3DGS",
|
||||
"id": 19,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
2122,
|
||||
54
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "raw_3DGS",
|
||||
"type": "GS_RAW",
|
||||
"link": 21
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Save 3DGS"
|
||||
},
|
||||
"widgets_values": [
|
||||
"MeshTest\\3DGS_01.ply"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "InvertMask",
|
||||
"pos": [
|
||||
746,
|
||||
-14
|
||||
420,
|
||||
-320
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 26
|
||||
"1": 246
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": 2
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
8,
|
||||
18
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "InvertMask"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"type": "[ComfyUI-3D] Gaussian Splatting",
|
||||
"pos": [
|
||||
1486,
|
||||
53
|
||||
],
|
||||
"size": {
|
||||
"0": 468.5999755859375,
|
||||
"1": 718
|
||||
},
|
||||
"flags": {
|
||||
"collapsed": false
|
||||
},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "reference_images",
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 17
|
||||
},
|
||||
{
|
||||
"name": "reference_masks",
|
||||
"type": "MASK",
|
||||
"link": 18
|
||||
},
|
||||
{
|
||||
"name": "reference_orbit_camera_poses",
|
||||
"type": "ORBIT_CAMPOSES",
|
||||
"link": 25
|
||||
},
|
||||
{
|
||||
"name": "mesh_to_initialize_gaussian",
|
||||
"type": "MESH",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "raw_3DGS",
|
||||
"type": "GS_RAW",
|
||||
"links": [
|
||||
21
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
"link": 22
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Gaussian Splatting"
|
||||
},
|
||||
"widgets_values": [
|
||||
49.1,
|
||||
1000,
|
||||
1,
|
||||
10000,
|
||||
0.2,
|
||||
3,
|
||||
0,
|
||||
0,
|
||||
0.5,
|
||||
0.01,
|
||||
0.05,
|
||||
0.005,
|
||||
0.005,
|
||||
0.001,
|
||||
0.00002,
|
||||
0.02,
|
||||
500,
|
||||
5000,
|
||||
3,
|
||||
0.01,
|
||||
100,
|
||||
100000,
|
||||
100,
|
||||
700,
|
||||
0.01,
|
||||
0
|
||||
]
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
@@ -228,8 +107,8 @@
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
17,
|
||||
22
|
||||
22,
|
||||
26
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
@@ -274,85 +153,62 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"type": "PreviewImage",
|
||||
"id": 8,
|
||||
"type": "InvertMask",
|
||||
"pos": [
|
||||
420,
|
||||
-320
|
||||
746,
|
||||
-14
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 246
|
||||
"1": 26
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 22
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": 2
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"type": "[ComfyUI-3D] Load 3D Mesh",
|
||||
"pos": [
|
||||
1100,
|
||||
315
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 154
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "mesh",
|
||||
"type": "MESH",
|
||||
"links": [],
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
8,
|
||||
27
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"title": "Load 3D Mesh [Optional]",
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Load 3D Mesh"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Meshes\\Cammy_Uniform.obj",
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
"Node name for S&R": "InvertMask"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"type": "[ComfyUI-3D] Stack Orbit Camera Poses",
|
||||
"pos": [
|
||||
726,
|
||||
160
|
||||
696,
|
||||
174
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 586
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "orbit_camposes",
|
||||
"type": "ORBIT_CAMPOSES",
|
||||
"links": [
|
||||
25
|
||||
28
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
@@ -417,6 +273,158 @@
|
||||
0,
|
||||
0.1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"type": "[ComfyUI-3D] Save 3DGS",
|
||||
"pos": [
|
||||
2178,
|
||||
54
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "gs_ply",
|
||||
"type": "GS_PLY",
|
||||
"link": 29
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Save 3DGS"
|
||||
},
|
||||
"widgets_values": [
|
||||
"MeshTest\\3DGS_01.ply"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"type": "[ComfyUI-3D] Load 3D Mesh",
|
||||
"pos": [
|
||||
1109,
|
||||
327
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 154
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "mesh",
|
||||
"type": "MESH",
|
||||
"links": [],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"title": "Load 3D Mesh [Optional]",
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Load 3D Mesh"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Meshes\\Cammy_Uniform.obj",
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"type": "[ComfyUI-3D] Gaussian Splatting",
|
||||
"pos": [
|
||||
1516,
|
||||
54
|
||||
],
|
||||
"size": {
|
||||
"0": 443.4000244140625,
|
||||
"1": 758
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "reference_images",
|
||||
"type": "IMAGE",
|
||||
"link": 26
|
||||
},
|
||||
{
|
||||
"name": "reference_masks",
|
||||
"type": "MASK",
|
||||
"link": 27
|
||||
},
|
||||
{
|
||||
"name": "reference_orbit_camera_poses",
|
||||
"type": "ORBIT_CAMPOSES",
|
||||
"link": 28
|
||||
},
|
||||
{
|
||||
"name": "points_cloud",
|
||||
"type": "POINTCLOUD",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "ply_to_initialize_gaussian",
|
||||
"type": "GS_PLY",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "mesh_to_initialize_gaussian",
|
||||
"type": "MESH",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "gs_ply",
|
||||
"type": "GS_PLY",
|
||||
"links": [
|
||||
29
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Gaussian Splatting"
|
||||
},
|
||||
"widgets_values": [
|
||||
49.1,
|
||||
1000,
|
||||
3,
|
||||
10000,
|
||||
0.2,
|
||||
3,
|
||||
0,
|
||||
0,
|
||||
0.5,
|
||||
0.01,
|
||||
0.05,
|
||||
0.005,
|
||||
0.005,
|
||||
0.001,
|
||||
0.00002,
|
||||
0.02,
|
||||
500,
|
||||
5000,
|
||||
3,
|
||||
0.01,
|
||||
100,
|
||||
100000,
|
||||
100,
|
||||
700,
|
||||
0.01,
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
@@ -444,30 +452,6 @@
|
||||
0,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
17,
|
||||
7,
|
||||
0,
|
||||
18,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
18,
|
||||
8,
|
||||
0,
|
||||
18,
|
||||
1,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
21,
|
||||
18,
|
||||
0,
|
||||
17,
|
||||
0,
|
||||
"GS_RAW"
|
||||
],
|
||||
[
|
||||
22,
|
||||
7,
|
||||
@@ -477,12 +461,36 @@
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
25,
|
||||
26,
|
||||
7,
|
||||
0,
|
||||
24,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
27,
|
||||
8,
|
||||
0,
|
||||
24,
|
||||
1,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
28,
|
||||
22,
|
||||
0,
|
||||
18,
|
||||
24,
|
||||
2,
|
||||
"ORBIT_CAMPOSES"
|
||||
],
|
||||
[
|
||||
29,
|
||||
24,
|
||||
0,
|
||||
23,
|
||||
0,
|
||||
"GS_PLY"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"last_node_id": 22,
|
||||
"last_link_id": 26,
|
||||
"last_node_id": 27,
|
||||
"last_link_id": 36,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 10,
|
||||
@@ -14,7 +14,7 @@
|
||||
"1": 246
|
||||
},
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
@@ -39,7 +39,7 @@
|
||||
"1": 26
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
@@ -64,79 +64,63 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "InvertMask",
|
||||
"id": 19,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
746,
|
||||
-14
|
||||
420,
|
||||
-320
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 26
|
||||
"1": 246
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": 2
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
8,
|
||||
18
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 22
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "InvertMask"
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "[ComfyUI-3D] Generate Orbit Camera Poses",
|
||||
"id": 13,
|
||||
"type": "[ComfyUI-3D] Load 3D Mesh",
|
||||
"pos": [
|
||||
830,
|
||||
132
|
||||
1053,
|
||||
415
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 200
|
||||
"0": 315,
|
||||
"1": 154
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "reference_images",
|
||||
"type": "IMAGE",
|
||||
"link": 1
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "orbit_camposes",
|
||||
"type": "ORBIT_CAMPOSES",
|
||||
"links": [
|
||||
19
|
||||
],
|
||||
"name": "mesh",
|
||||
"type": "MESH",
|
||||
"links": [],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"title": "Load 3D Mesh [Optional]",
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Generate Orbit Camera Poses"
|
||||
"Node name for S&R": "[ComfyUI-3D] Load 3D Mesh"
|
||||
},
|
||||
"widgets_values": [
|
||||
"([0:120], 1.75, 0, 0, 360)"
|
||||
"Meshes\\Cammy_Uniform.obj",
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -151,7 +135,7 @@
|
||||
194
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
@@ -159,8 +143,8 @@
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
1,
|
||||
17,
|
||||
22
|
||||
22,
|
||||
27
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
@@ -205,258 +189,128 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"type": "PreviewImage",
|
||||
"id": 8,
|
||||
"type": "InvertMask",
|
||||
"pos": [
|
||||
420,
|
||||
-320
|
||||
746,
|
||||
-14
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 246
|
||||
"1": 26
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": 2
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
8,
|
||||
28
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "InvertMask"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "[ComfyUI-3D] Generate Orbit Camera Poses",
|
||||
"pos": [
|
||||
830,
|
||||
132
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 200
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 22
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"type": "[ComfyUI-3D] Gaussian Splatting",
|
||||
"pos": [
|
||||
1486,
|
||||
53
|
||||
],
|
||||
"size": {
|
||||
"0": 468.5999755859375,
|
||||
"1": 718
|
||||
},
|
||||
"flags": {
|
||||
"collapsed": false
|
||||
},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "reference_images",
|
||||
"type": "IMAGE",
|
||||
"link": 17
|
||||
},
|
||||
{
|
||||
"name": "reference_masks",
|
||||
"type": "MASK",
|
||||
"link": 18
|
||||
},
|
||||
{
|
||||
"name": "reference_orbit_camera_poses",
|
||||
"type": "ORBIT_CAMPOSES",
|
||||
"link": 19
|
||||
},
|
||||
{
|
||||
"name": "mesh_to_initialize_gaussian",
|
||||
"type": "MESH",
|
||||
"link": null
|
||||
"link": 1
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "raw_3DGS",
|
||||
"type": "GS_RAW",
|
||||
"name": "orbit_camposes",
|
||||
"type": "ORBIT_CAMPOSES",
|
||||
"links": [
|
||||
21,
|
||||
24
|
||||
29
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Gaussian Splatting"
|
||||
"Node name for S&R": "[ComfyUI-3D] Generate Orbit Camera Poses"
|
||||
},
|
||||
"widgets_values": [
|
||||
49.1,
|
||||
1000,
|
||||
1,
|
||||
10000,
|
||||
0.2,
|
||||
3,
|
||||
0,
|
||||
0,
|
||||
0.5,
|
||||
0.01,
|
||||
0.05,
|
||||
0.005,
|
||||
0.005,
|
||||
0.001,
|
||||
0.00002,
|
||||
0.02,
|
||||
500,
|
||||
5000,
|
||||
3,
|
||||
0.01,
|
||||
100,
|
||||
100000,
|
||||
100,
|
||||
700,
|
||||
0.01,
|
||||
0
|
||||
"([0:120], 1.75, 0, 0, 360)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"id": 24,
|
||||
"type": "[ComfyUI-3D] Save 3DGS",
|
||||
"pos": [
|
||||
2243,
|
||||
52
|
||||
2122,
|
||||
-131
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "raw_3DGS",
|
||||
"type": "GS_RAW",
|
||||
"link": 21
|
||||
"name": "gs_ply",
|
||||
"type": "GS_PLY",
|
||||
"link": 30
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Save 3DGS"
|
||||
},
|
||||
"widgets_values": [
|
||||
"MeshTest\\3DGS_01.ply"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"type": "[ComfyUI-3D] Convert 3DGS To Pointcloud",
|
||||
"pos": [
|
||||
2242,
|
||||
251
|
||||
],
|
||||
"size": {
|
||||
"0": 267,
|
||||
"1": 26
|
||||
},
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "raw_3DGS",
|
||||
"type": "GS_RAW",
|
||||
"link": 24
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "points_cloud",
|
||||
"type": "POINTCLOUD",
|
||||
"links": [
|
||||
25
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Convert 3DGS To Pointcloud"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"type": "[ComfyUI-3D] Load 3D Mesh",
|
||||
"pos": [
|
||||
1053,
|
||||
415
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 154
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "mesh",
|
||||
"type": "MESH",
|
||||
"links": [],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"title": "Load 3D Mesh [Optional]",
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Load 3D Mesh"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Meshes\\Cammy_Uniform.obj",
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"type": "[ComfyUI-3D] Save 3D Mesh",
|
||||
"pos": [
|
||||
3261,
|
||||
55
|
||||
],
|
||||
"size": {
|
||||
"0": 353.38916015625,
|
||||
"1": 61.879417419433594
|
||||
},
|
||||
"flags": {},
|
||||
"order": 11,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mesh",
|
||||
"type": "MESH",
|
||||
"link": 26
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Save 3D Mesh"
|
||||
},
|
||||
"widgets_values": [
|
||||
"MeshTest\\Cammy_Uniform_DMTet_01.obj"
|
||||
"MeshTest/3DGS_01.ply"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"type": "[ComfyUI-3D] Deep Marching Tetrahedrons",
|
||||
"pos": [
|
||||
2710,
|
||||
56
|
||||
2963,
|
||||
55
|
||||
],
|
||||
"size": {
|
||||
"0": 443.4000244140625,
|
||||
"1": 314
|
||||
},
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"order": 11,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "reference_points_cloud",
|
||||
"type": "POINTCLOUD",
|
||||
"link": 25
|
||||
"link": 35
|
||||
},
|
||||
{
|
||||
"name": "reference_images",
|
||||
@@ -495,6 +349,190 @@
|
||||
128,
|
||||
5
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"type": "[ComfyUI-3D] Save 3D Mesh",
|
||||
"pos": [
|
||||
3534,
|
||||
56
|
||||
],
|
||||
"size": {
|
||||
"0": 353.38916015625,
|
||||
"1": 61.879417419433594
|
||||
},
|
||||
"flags": {},
|
||||
"order": 12,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mesh",
|
||||
"type": "MESH",
|
||||
"link": 26
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Save 3D Mesh"
|
||||
},
|
||||
"widgets_values": [
|
||||
"MeshTest\\Cammy_Uniform_DMTet_01.obj"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"type": "[ComfyUI-3D] Load 3DGS",
|
||||
"pos": [
|
||||
2107,
|
||||
194
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "gs_ply",
|
||||
"type": "GS_PLY",
|
||||
"links": [],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Load 3DGS"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Meshes/3DGS_01.ply"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"type": "[ComfyUI-3D] Gaussian Splatting",
|
||||
"pos": [
|
||||
1501,
|
||||
53
|
||||
],
|
||||
"size": {
|
||||
"0": 443.4000244140625,
|
||||
"1": 758
|
||||
},
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "reference_images",
|
||||
"type": "IMAGE",
|
||||
"link": 27
|
||||
},
|
||||
{
|
||||
"name": "reference_masks",
|
||||
"type": "MASK",
|
||||
"link": 28
|
||||
},
|
||||
{
|
||||
"name": "reference_orbit_camera_poses",
|
||||
"type": "ORBIT_CAMPOSES",
|
||||
"link": 29
|
||||
},
|
||||
{
|
||||
"name": "points_cloud",
|
||||
"type": "POINTCLOUD",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "ply_to_initialize_gaussian",
|
||||
"type": "GS_PLY",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "mesh_to_initialize_gaussian",
|
||||
"type": "MESH",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "gs_ply",
|
||||
"type": "GS_PLY",
|
||||
"links": [
|
||||
30,
|
||||
36
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Gaussian Splatting"
|
||||
},
|
||||
"widgets_values": [
|
||||
49.1,
|
||||
1000,
|
||||
3,
|
||||
10000,
|
||||
0.2,
|
||||
3,
|
||||
0,
|
||||
0,
|
||||
0.5,
|
||||
0.01,
|
||||
0.05,
|
||||
0.005,
|
||||
0.005,
|
||||
0.001,
|
||||
0.00002,
|
||||
0.02,
|
||||
500,
|
||||
5000,
|
||||
3,
|
||||
0.01,
|
||||
100,
|
||||
100000,
|
||||
100,
|
||||
700,
|
||||
0.01,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"type": "[ComfyUI-3D] Convert 3DGS To Pointcloud",
|
||||
"pos": [
|
||||
2555,
|
||||
53
|
||||
],
|
||||
"size": {
|
||||
"0": 241.79998779296875,
|
||||
"1": 26
|
||||
},
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "gs_ply",
|
||||
"type": "GS_PLY",
|
||||
"link": 36
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "points_cloud",
|
||||
"type": "POINTCLOUD",
|
||||
"links": [
|
||||
35
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Convert 3DGS To Pointcloud"
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
@@ -530,38 +568,6 @@
|
||||
0,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
17,
|
||||
7,
|
||||
0,
|
||||
18,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
18,
|
||||
8,
|
||||
0,
|
||||
18,
|
||||
1,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
19,
|
||||
4,
|
||||
0,
|
||||
18,
|
||||
2,
|
||||
"ORBIT_CAMPOSES"
|
||||
],
|
||||
[
|
||||
21,
|
||||
18,
|
||||
0,
|
||||
17,
|
||||
0,
|
||||
"GS_RAW"
|
||||
],
|
||||
[
|
||||
22,
|
||||
7,
|
||||
@@ -570,22 +576,6 @@
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
24,
|
||||
18,
|
||||
0,
|
||||
20,
|
||||
0,
|
||||
"GS_RAW"
|
||||
],
|
||||
[
|
||||
25,
|
||||
20,
|
||||
0,
|
||||
21,
|
||||
0,
|
||||
"POINTCLOUD"
|
||||
],
|
||||
[
|
||||
26,
|
||||
21,
|
||||
@@ -593,6 +583,54 @@
|
||||
22,
|
||||
0,
|
||||
"MESH"
|
||||
],
|
||||
[
|
||||
27,
|
||||
7,
|
||||
0,
|
||||
23,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
28,
|
||||
8,
|
||||
0,
|
||||
23,
|
||||
1,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
29,
|
||||
4,
|
||||
0,
|
||||
23,
|
||||
2,
|
||||
"ORBIT_CAMPOSES"
|
||||
],
|
||||
[
|
||||
30,
|
||||
23,
|
||||
0,
|
||||
24,
|
||||
0,
|
||||
"GS_PLY"
|
||||
],
|
||||
[
|
||||
35,
|
||||
27,
|
||||
0,
|
||||
21,
|
||||
0,
|
||||
"POINTCLOUD"
|
||||
],
|
||||
[
|
||||
36,
|
||||
23,
|
||||
0,
|
||||
27,
|
||||
0,
|
||||
"GS_PLY"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
|
||||
@@ -1,109 +1,13 @@
|
||||
{
|
||||
"last_node_id": 4,
|
||||
"last_link_id": 3,
|
||||
"last_node_id": 28,
|
||||
"last_link_id": 37,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 2,
|
||||
"type": "[ComfyUI-3D] Convert 3DGS To Pointcloud",
|
||||
"pos": [
|
||||
561,
|
||||
484
|
||||
],
|
||||
"size": {
|
||||
"0": 267,
|
||||
"1": 26
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "raw_3DGS",
|
||||
"type": "GS_RAW",
|
||||
"link": 1
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "points_cloud",
|
||||
"type": "POINTCLOUD",
|
||||
"links": [
|
||||
2
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Convert 3DGS To Pointcloud"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"type": "[ComfyUI-3D] Load 3DGS",
|
||||
"pos": [
|
||||
166,
|
||||
484
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "raw_3DGS",
|
||||
"type": "GS_RAW",
|
||||
"links": [
|
||||
1
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Load 3DGS"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Meshes\\Generated\\Cammy_Uniform_3DGS_01.ply"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "[ComfyUI-3D] Save 3D Mesh",
|
||||
"pos": [
|
||||
1446,
|
||||
485
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mesh",
|
||||
"type": "MESH",
|
||||
"link": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Save 3D Mesh"
|
||||
},
|
||||
"widgets_values": [
|
||||
"MeshTest\\Cammy_Uniform_DMTet_01.obj"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"id": 21,
|
||||
"type": "[ComfyUI-3D] Deep Marching Tetrahedrons",
|
||||
"pos": [
|
||||
913,
|
||||
484
|
||||
1450,
|
||||
420
|
||||
],
|
||||
"size": {
|
||||
"0": 443.4000244140625,
|
||||
@@ -116,7 +20,7 @@
|
||||
{
|
||||
"name": "reference_points_cloud",
|
||||
"type": "POINTCLOUD",
|
||||
"link": 2
|
||||
"link": 37
|
||||
},
|
||||
{
|
||||
"name": "reference_images",
|
||||
@@ -134,7 +38,7 @@
|
||||
"name": "trained_mesh",
|
||||
"type": "MESH",
|
||||
"links": [
|
||||
3
|
||||
26
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
@@ -155,32 +59,128 @@
|
||||
128,
|
||||
5
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"type": "[ComfyUI-3D] Save 3D Mesh",
|
||||
"pos": [
|
||||
2020,
|
||||
420
|
||||
],
|
||||
"size": {
|
||||
"0": 353.38916015625,
|
||||
"1": 61.879417419433594
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mesh",
|
||||
"type": "MESH",
|
||||
"link": 26
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Save 3D Mesh"
|
||||
},
|
||||
"widgets_values": [
|
||||
"MeshTest\\picacho.obj"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"type": "[ComfyUI-3D] Convert 3DGS To Pointcloud",
|
||||
"pos": [
|
||||
1035,
|
||||
422
|
||||
],
|
||||
"size": {
|
||||
"0": 241.79998779296875,
|
||||
"1": 26
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "gs_ply",
|
||||
"type": "GS_PLY",
|
||||
"link": 36
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "points_cloud",
|
||||
"type": "POINTCLOUD",
|
||||
"links": [
|
||||
37
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Convert 3DGS To Pointcloud"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"type": "[ComfyUI-3D] Load 3DGS",
|
||||
"pos": [
|
||||
592,
|
||||
423
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "gs_ply",
|
||||
"type": "GS_PLY",
|
||||
"links": [
|
||||
36
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Load 3DGS"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Meshes/3DGS_TGS.ply"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
1,
|
||||
26,
|
||||
21,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
"GS_RAW"
|
||||
],
|
||||
[
|
||||
2,
|
||||
2,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"POINTCLOUD"
|
||||
],
|
||||
[
|
||||
3,
|
||||
3,
|
||||
0,
|
||||
4,
|
||||
22,
|
||||
0,
|
||||
"MESH"
|
||||
],
|
||||
[
|
||||
36,
|
||||
26,
|
||||
0,
|
||||
28,
|
||||
0,
|
||||
"GS_PLY"
|
||||
],
|
||||
[
|
||||
37,
|
||||
28,
|
||||
0,
|
||||
21,
|
||||
0,
|
||||
"POINTCLOUD"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
{
|
||||
"last_node_id": 9,
|
||||
"last_link_id": 11,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 7,
|
||||
"type": "[ComfyUI-3D] Run Triplane Gaussian Transformers",
|
||||
"pos": [
|
||||
1032,
|
||||
627
|
||||
],
|
||||
"size": {
|
||||
"0": 430.8000183105469,
|
||||
"1": 98
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "reference_image",
|
||||
"type": "IMAGE",
|
||||
"link": 6
|
||||
},
|
||||
{
|
||||
"name": "reference_mask",
|
||||
"type": "MASK",
|
||||
"link": 11
|
||||
},
|
||||
{
|
||||
"name": "tgs_model",
|
||||
"type": "TGS_MODEL",
|
||||
"link": 8
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "gs_ply",
|
||||
"type": "GS_PLY",
|
||||
"links": [
|
||||
9
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "gaussian_sh_degree",
|
||||
"type": "FLOAT",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Run Triplane Gaussian Transformers"
|
||||
},
|
||||
"widgets_values": [
|
||||
1.9
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "InvertMask",
|
||||
"pos": [
|
||||
506,
|
||||
647
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 26
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": 10
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
11
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "InvertMask"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "[ComfyUI-3D] Load Triplane Gaussian Transformers",
|
||||
"pos": [
|
||||
579,
|
||||
735
|
||||
],
|
||||
"size": {
|
||||
"0": 369,
|
||||
"1": 47.66668701171875
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "tgs_model",
|
||||
"type": "TGS_MODEL",
|
||||
"links": [
|
||||
8
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Load Triplane Gaussian Transformers"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "[ComfyUI-3D] Save 3DGS",
|
||||
"pos": [
|
||||
1531,
|
||||
627
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "gs_ply",
|
||||
"type": "GS_PLY",
|
||||
"link": 9
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[ComfyUI-3D] Save 3DGS"
|
||||
},
|
||||
"widgets_values": [
|
||||
"MeshTest/3DGS_TGS.ply"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
87,
|
||||
211
|
||||
],
|
||||
"size": {
|
||||
"0": 354.32330322265625,
|
||||
"1": 315.4895935058594
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
6
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
10
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 1
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"a_pikachu_with_smily_face-removebg.png",
|
||||
"image"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
6,
|
||||
4,
|
||||
0,
|
||||
7,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
8,
|
||||
2,
|
||||
0,
|
||||
7,
|
||||
2,
|
||||
"TGS_MODEL"
|
||||
],
|
||||
[
|
||||
9,
|
||||
7,
|
||||
0,
|
||||
8,
|
||||
0,
|
||||
"GS_PLY"
|
||||
],
|
||||
[
|
||||
10,
|
||||
4,
|
||||
1,
|
||||
9,
|
||||
0,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
11,
|
||||
9,
|
||||
0,
|
||||
7,
|
||||
1,
|
||||
"MASK"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 393 KiB |
@@ -29,6 +29,7 @@ class DiffTextureBaker:
|
||||
self.batch_size = batch_size
|
||||
|
||||
def prepare_img(self, img):
|
||||
# (H, W, C) -> (C, H, W)
|
||||
img_new = img.permute(2, 0, 1).unsqueeze(0).to(self.device)
|
||||
img_new = F.interpolate(img_new, (self.ref_size_H, self.ref_size_W), mode="bilinear", align_corners=False).contiguous()
|
||||
return img_new
|
||||
|
||||
@@ -31,7 +31,7 @@ from simple_knn._C import distCUDA2
|
||||
|
||||
from ..shared_utils.sh_utils import eval_sh, SH2RGB, RGB2SH
|
||||
from ..mesh_processer.mesh import Mesh
|
||||
from ..mesh_processer.mesh_utils import decimate_mesh, clean_mesh
|
||||
from ..mesh_processer.mesh_utils import read_gs_ply
|
||||
|
||||
def inverse_sigmoid(x):
|
||||
return torch.log(x/(1-x))
|
||||
@@ -500,6 +500,11 @@ class GaussianModel:
|
||||
lr = self.xyz_scheduler_args(iteration)
|
||||
param_group['lr'] = lr
|
||||
return lr
|
||||
|
||||
def reset_opacity(self):
|
||||
opacities_new = inverse_sigmoid(torch.min(self.get_opacity, torch.ones_like(self.get_opacity)*0.01))
|
||||
optimizable_tensors = self.replace_tensor_to_optimizer(opacities_new, "opacity")
|
||||
self._opacity = optimizable_tensors["opacity"]
|
||||
|
||||
def construct_list_of_attributes(self):
|
||||
l = ['x', 'y', 'z', 'nx', 'ny', 'nz']
|
||||
@@ -525,9 +530,7 @@ class GaussianModel:
|
||||
def get_mesh(self):
|
||||
xyz = self._xyz.detach().cpu().numpy()
|
||||
|
||||
def save_ply(self, path):
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
|
||||
def to_ply(self):
|
||||
xyz = self._xyz.detach().cpu().numpy()
|
||||
normals = np.zeros_like(xyz)
|
||||
f_dc = self._features_dc.detach().transpose(1, 2).flatten(start_dim=1).contiguous().cpu().numpy()
|
||||
@@ -542,45 +545,10 @@ class GaussianModel:
|
||||
attributes = np.concatenate((xyz, normals, f_dc, f_rest, opacities, scale, rotation), axis=1)
|
||||
elements[:] = list(map(tuple, attributes))
|
||||
el = PlyElement.describe(elements, 'vertex')
|
||||
PlyData([el]).write(path)
|
||||
return PlyData([el])
|
||||
|
||||
def reset_opacity(self):
|
||||
opacities_new = inverse_sigmoid(torch.min(self.get_opacity, torch.ones_like(self.get_opacity)*0.01))
|
||||
optimizable_tensors = self.replace_tensor_to_optimizer(opacities_new, "opacity")
|
||||
self._opacity = optimizable_tensors["opacity"]
|
||||
|
||||
def load_ply(self, path):
|
||||
plydata = PlyData.read(path)
|
||||
|
||||
xyz = np.stack((np.asarray(plydata.elements[0]["x"]),
|
||||
np.asarray(plydata.elements[0]["y"]),
|
||||
np.asarray(plydata.elements[0]["z"])), axis=1)
|
||||
opacities = np.asarray(plydata.elements[0]["opacity"])[..., np.newaxis]
|
||||
|
||||
print("Number of points at loading : ", xyz.shape[0])
|
||||
|
||||
features_dc = np.zeros((xyz.shape[0], 3, 1))
|
||||
features_dc[:, 0, 0] = np.asarray(plydata.elements[0]["f_dc_0"])
|
||||
features_dc[:, 1, 0] = np.asarray(plydata.elements[0]["f_dc_1"])
|
||||
features_dc[:, 2, 0] = np.asarray(plydata.elements[0]["f_dc_2"])
|
||||
|
||||
extra_f_names = [p.name for p in plydata.elements[0].properties if p.name.startswith("f_rest_")]
|
||||
assert len(extra_f_names)==3*(self.max_sh_degree + 1) ** 2 - 3
|
||||
features_extra = np.zeros((xyz.shape[0], len(extra_f_names)))
|
||||
for idx, attr_name in enumerate(extra_f_names):
|
||||
features_extra[:, idx] = np.asarray(plydata.elements[0][attr_name])
|
||||
# Reshape (P,F*SH_coeffs) to (P, F, SH_coeffs except DC)
|
||||
features_extra = features_extra.reshape((features_extra.shape[0], 3, (self.max_sh_degree + 1) ** 2 - 1))
|
||||
|
||||
scale_names = [p.name for p in plydata.elements[0].properties if p.name.startswith("scale_")]
|
||||
scales = np.zeros((xyz.shape[0], len(scale_names)))
|
||||
for idx, attr_name in enumerate(scale_names):
|
||||
scales[:, idx] = np.asarray(plydata.elements[0][attr_name])
|
||||
|
||||
rot_names = [p.name for p in plydata.elements[0].properties if p.name.startswith("rot")]
|
||||
rots = np.zeros((xyz.shape[0], len(rot_names)))
|
||||
for idx, attr_name in enumerate(rot_names):
|
||||
rots[:, idx] = np.asarray(plydata.elements[0][attr_name])
|
||||
def create_from_ply(self, plydata):
|
||||
xyz, features_dc, features_extra, opacities, scales, rots = read_gs_ply(plydata)
|
||||
|
||||
self._xyz = nn.Parameter(torch.tensor(xyz, dtype=torch.float, device="cuda").requires_grad_(True))
|
||||
self._features_dc = nn.Parameter(torch.tensor(features_dc, dtype=torch.float, device="cuda").transpose(1, 2).contiguous().requires_grad_(True))
|
||||
@@ -894,6 +862,11 @@ class Renderer:
|
||||
if isinstance(input, Mesh):
|
||||
# load from 3D mesh
|
||||
self.gaussians.create_from_mesh(input, num_pts)
|
||||
elif isinstance(input, PlyData):
|
||||
self.gaussians.create_from_ply(input)
|
||||
elif isinstance(input, PointCloud):
|
||||
# load from a provided pcd
|
||||
self.gaussians.create_from_pcd(input, 1)
|
||||
elif input is None:
|
||||
# init from random point cloud
|
||||
|
||||
@@ -913,9 +886,6 @@ class Renderer:
|
||||
points=xyz, colors=SH2RGB(shs), normals=np.zeros((num_pts, 3))
|
||||
)
|
||||
self.gaussians.create_from_pcd(pcd, 10)
|
||||
elif isinstance(input, PointCloud):
|
||||
# load from a provided pcd
|
||||
self.gaussians.create_from_pcd(input, 1)
|
||||
else:
|
||||
self.gaussians.create_from_uv_data(input)
|
||||
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
import torch
|
||||
from dataclasses import dataclass, field
|
||||
from einops import rearrange
|
||||
|
||||
from ..tgs import find
|
||||
from ..tgs.models.image_feature import ImageFeature
|
||||
from ..tgs.utils.saving import SaverMixin
|
||||
from ..tgs.utils.config import parse_structured
|
||||
from ..tgs.utils.ops import points_projection
|
||||
from ..tgs.utils.misc import load_module_weights
|
||||
from ..tgs.utils.typing import *
|
||||
|
||||
class TGS(torch.nn.Module, SaverMixin):
|
||||
@dataclass
|
||||
class Config:
|
||||
weights: Optional[str] = None
|
||||
weights_ignore_modules: Optional[List[str]] = None
|
||||
|
||||
camera_embedder_cls: str = ""
|
||||
camera_embedder: dict = field(default_factory=dict)
|
||||
|
||||
image_feature: dict = field(default_factory=dict)
|
||||
|
||||
image_tokenizer_cls: str = ""
|
||||
image_tokenizer: dict = field(default_factory=dict)
|
||||
|
||||
tokenizer_cls: str = ""
|
||||
tokenizer: dict = field(default_factory=dict)
|
||||
|
||||
backbone_cls: str = ""
|
||||
backbone: dict = field(default_factory=dict)
|
||||
|
||||
post_processor_cls: str = ""
|
||||
post_processor: dict = field(default_factory=dict)
|
||||
|
||||
renderer_cls: str = ""
|
||||
renderer: dict = field(default_factory=dict)
|
||||
|
||||
pointcloud_generator_cls: str = ""
|
||||
pointcloud_generator: dict = field(default_factory=dict)
|
||||
|
||||
pointcloud_encoder_cls: str = ""
|
||||
pointcloud_encoder: dict = field(default_factory=dict)
|
||||
|
||||
cfg: Config
|
||||
|
||||
def load_weights(self, weights: str, ignore_modules: Optional[List[str]] = None):
|
||||
state_dict = load_module_weights(
|
||||
weights, ignore_modules=ignore_modules, map_location="cpu"
|
||||
)
|
||||
self.load_state_dict(state_dict, strict=False)
|
||||
|
||||
def __init__(self, cfg):
|
||||
super().__init__()
|
||||
self.cfg = parse_structured(self.Config, cfg)
|
||||
|
||||
self.image_tokenizer = find(self.cfg.image_tokenizer_cls)(
|
||||
self.cfg.image_tokenizer
|
||||
)
|
||||
|
||||
assert self.cfg.camera_embedder_cls == 'tgs.models.networks.MLP'
|
||||
weights = self.cfg.camera_embedder.pop("weights") if "weights" in self.cfg.camera_embedder else None
|
||||
self.camera_embedder = find(self.cfg.camera_embedder_cls)(**self.cfg.camera_embedder)
|
||||
if weights:
|
||||
from ..tgs.utils.misc import load_module_weights
|
||||
weights_path, module_name = weights.split(":")
|
||||
state_dict = load_module_weights(
|
||||
weights_path, module_name=module_name, map_location="cpu"
|
||||
)
|
||||
self.camera_embedder.load_state_dict(state_dict)
|
||||
|
||||
self.image_feature = ImageFeature(self.cfg.image_feature)
|
||||
|
||||
self.tokenizer = find(self.cfg.tokenizer_cls)(self.cfg.tokenizer)
|
||||
|
||||
self.backbone = find(self.cfg.backbone_cls)(self.cfg.backbone)
|
||||
|
||||
self.post_processor = find(self.cfg.post_processor_cls)(
|
||||
self.cfg.post_processor
|
||||
)
|
||||
|
||||
self.renderer = find(self.cfg.renderer_cls)(self.cfg.renderer)
|
||||
|
||||
# pointcloud generator
|
||||
self.pointcloud_generator = find(self.cfg.pointcloud_generator_cls)(self.cfg.pointcloud_generator)
|
||||
|
||||
self.point_encoder = find(self.cfg.pointcloud_encoder_cls)(self.cfg.pointcloud_encoder)
|
||||
|
||||
# load checkpoint
|
||||
if self.cfg.weights is not None:
|
||||
self.load_weights(self.cfg.weights, self.cfg.weights_ignore_modules)
|
||||
|
||||
def _forward(self, batch: Dict[str, Any]) -> Dict[str, Any]:
|
||||
# generate point cloud
|
||||
out = self.pointcloud_generator(batch)
|
||||
pointclouds = out["points"]
|
||||
|
||||
batch_size, n_input_views = batch["rgb_cond"].shape[:2]
|
||||
|
||||
# Camera modulation
|
||||
camera_extri = batch["c2w_cond"].view(*batch["c2w_cond"].shape[:-2], -1)
|
||||
camera_intri = batch["intrinsic_normed_cond"].view(*batch["intrinsic_normed_cond"].shape[:-2], -1)
|
||||
camera_feats = torch.cat([camera_intri, camera_extri], dim=-1)
|
||||
|
||||
camera_feats = self.camera_embedder(camera_feats)
|
||||
|
||||
input_image_tokens: Float[Tensor, "B Cit Nit"] = self.image_tokenizer(
|
||||
rearrange(batch["rgb_cond"], 'B Nv H W C -> B Nv C H W'),
|
||||
modulation_cond=camera_feats,
|
||||
)
|
||||
input_image_tokens = rearrange(input_image_tokens, 'B Nv C Nt -> B (Nv Nt) C', Nv=n_input_views)
|
||||
|
||||
# get image features for projection
|
||||
image_features = self.image_feature(
|
||||
rgb = batch["rgb_cond"],
|
||||
mask = batch.get("mask_cond", None),
|
||||
feature = input_image_tokens
|
||||
)
|
||||
|
||||
# only support number of input view is one
|
||||
c2w_cond = batch["c2w_cond"].squeeze(1)
|
||||
intrinsic_cond = batch["intrinsic_cond"].squeeze(1)
|
||||
proj_feats = points_projection(pointclouds, c2w_cond, intrinsic_cond, image_features)
|
||||
|
||||
point_cond_embeddings = self.point_encoder(torch.cat([pointclouds, proj_feats], dim=-1))
|
||||
tokens: Float[Tensor, "B Ct Nt"] = self.tokenizer(batch_size, cond_embeddings=point_cond_embeddings)
|
||||
|
||||
tokens = self.backbone(
|
||||
tokens,
|
||||
encoder_hidden_states=input_image_tokens,
|
||||
modulation_cond=None,
|
||||
)
|
||||
|
||||
scene_codes = self.post_processor(self.tokenizer.detokenize(tokens))
|
||||
rend_out = self.renderer(scene_codes,
|
||||
query_points=pointclouds,
|
||||
additional_features=proj_feats,
|
||||
**batch)
|
||||
|
||||
return {**out, **rend_out}
|
||||
|
||||
def forward(self, batch):
|
||||
plys = []
|
||||
|
||||
out = self._forward(batch)
|
||||
batch_size = batch["index"].shape[0]
|
||||
for b in range(batch_size):
|
||||
if batch["view_index"][b, 0] == 0:
|
||||
plys.append(out["3dgs"][b].to_ply())
|
||||
|
||||
#self.render_test_video(out, batch, b)
|
||||
|
||||
return plys
|
||||
|
||||
def render_test_video(self, out, batch, b):
|
||||
for index, render_image in enumerate(out["comp_rgb"][b]):
|
||||
view_index = batch["view_index"][b, index]
|
||||
self.save_image_grid(
|
||||
f"video/{batch['instance_id'][b]}/{view_index}.png",
|
||||
[
|
||||
{
|
||||
"type": "rgb",
|
||||
"img": render_image,
|
||||
"kwargs": {"data_format": "HWC"},
|
||||
}
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,150 @@
|
||||
data:
|
||||
background_color: [1.0, 1.0, 1.0]
|
||||
cond_width: 252 # multiply of 14
|
||||
cond_height: 252
|
||||
|
||||
relative_pose: true
|
||||
|
||||
num_workers: 16
|
||||
eval_batch_size: 1
|
||||
eval_height: 512
|
||||
eval_width: 512
|
||||
|
||||
system:
|
||||
camera_embedder_cls: tgs.models.networks.MLP
|
||||
camera_embedder:
|
||||
dim_in: 25 # c2w + [fx, fy, cx, cy]
|
||||
dim_out: 768
|
||||
n_neurons: 768
|
||||
n_hidden_layers: 1
|
||||
activation: silu
|
||||
|
||||
image_feature:
|
||||
out_dim: 773
|
||||
|
||||
image_tokenizer_cls: tgs.models.tokenizers.image.DINOV2SingleImageTokenizer
|
||||
image_tokenizer:
|
||||
pretrained_model_name_or_path: "facebook/dinov2-base"
|
||||
width: ${data.cond_width}
|
||||
height: ${data.cond_height}
|
||||
|
||||
modulation: true
|
||||
modulation_zero_init: true
|
||||
modulation_single_layer: true
|
||||
modulation_cond_dim: ${system.camera_embedder.dim_out} # c2w + intrinsic
|
||||
|
||||
freeze_backbone_params: false
|
||||
enable_memory_efficient_attention: ${system.backbone.enable_memory_efficient_attention}
|
||||
enable_gradient_checkpointing: ${system.backbone.gradient_checkpointing}
|
||||
|
||||
tokenizer_cls: tgs.models.tokenizers.triplane.TriplaneLearnablePositionalEmbedding
|
||||
tokenizer:
|
||||
plane_size: 32
|
||||
num_channels: 512
|
||||
|
||||
backbone_cls: tgs.models.transformers.Transformer1D
|
||||
backbone:
|
||||
in_channels: ${system.tokenizer.num_channels}
|
||||
num_attention_heads: 8
|
||||
attention_head_dim: 64
|
||||
num_layers: 10
|
||||
cross_attention_dim: 768 # hard-code, =DINO feature dim
|
||||
|
||||
norm_type: "layer_norm"
|
||||
|
||||
enable_memory_efficient_attention: false
|
||||
gradient_checkpointing: false
|
||||
|
||||
post_processor_cls: tgs.models.networks.TriplaneUpsampleNetwork
|
||||
post_processor:
|
||||
in_channels: ${system.tokenizer.num_channels}
|
||||
out_channels: 80
|
||||
|
||||
pointcloud_generator_cls: tgs.models.pointclouds.simplepoint.SimplePointGenerator
|
||||
pointcloud_generator:
|
||||
camera_embedder_cls: tgs.models.networks.MLP
|
||||
camera_embedder:
|
||||
dim_in: 25 # c2w + [fx, fy, cx, cy]
|
||||
dim_out: 768
|
||||
n_neurons: 768
|
||||
n_hidden_layers: 1
|
||||
activation: silu
|
||||
|
||||
image_tokenizer_cls: tgs.models.tokenizers.image.DINOV2SingleImageTokenizer
|
||||
image_tokenizer:
|
||||
pretrained_model_name_or_path: "facebook/dinov2-base"
|
||||
width: ${data.cond_width}
|
||||
height: ${data.cond_height}
|
||||
|
||||
modulation: true
|
||||
modulation_zero_init: true
|
||||
modulation_single_layer: true
|
||||
modulation_cond_dim: ${system.camera_embedder.dim_out} # c2w + intrinsic
|
||||
|
||||
freeze_backbone_params: true
|
||||
enable_memory_efficient_attention: ${system.backbone.enable_memory_efficient_attention}
|
||||
enable_gradient_checkpointing: false
|
||||
|
||||
tokenizer_cls: tgs.models.tokenizers.point.PointLearnablePositionalEmbedding
|
||||
tokenizer:
|
||||
num_pcl: 2048
|
||||
num_channels: 512
|
||||
|
||||
backbone_cls: tgs.models.transformers.Transformer1D
|
||||
backbone:
|
||||
in_channels: ${system.pointcloud_generator.tokenizer.num_channels}
|
||||
num_attention_heads: 8
|
||||
attention_head_dim: 64
|
||||
num_layers: 10
|
||||
cross_attention_dim: 768 # hard-code, =DINO feature dim
|
||||
|
||||
norm_type: "layer_norm"
|
||||
|
||||
enable_memory_efficient_attention: ${system.backbone.enable_memory_efficient_attention}
|
||||
gradient_checkpointing: ${system.backbone.gradient_checkpointing}
|
||||
|
||||
post_processor_cls: tgs.models.networks.PointOutLayer
|
||||
post_processor:
|
||||
in_channels: 512
|
||||
out_channels: 3
|
||||
|
||||
pointcloud_upsampling_cls: tgs.models.snowflake.model_spdpp.SnowflakeModelSPDPP
|
||||
pointcloud_upsampling:
|
||||
input_channels: 768
|
||||
dim_feat: 128
|
||||
num_p0: 2048
|
||||
radius: 1
|
||||
bounding: true
|
||||
use_fps: true
|
||||
up_factors: [2,4]
|
||||
token_type: "image_token"
|
||||
|
||||
pointcloud_encoder_cls: tgs.models.pointclouds.pointnet.LocalPoolPointnet
|
||||
pointcloud_encoder:
|
||||
input_channels: 776 # 3 + 3 + 768 + 1 + 1 [xyz, local features]
|
||||
c_dim: ${system.tokenizer.num_channels}
|
||||
hidden_dim: 128
|
||||
plane_size: ${system.tokenizer.plane_size}
|
||||
n_blocks: 5
|
||||
radius: ${system.renderer.radius}
|
||||
|
||||
renderer_cls: tgs.models.renderer.GS3DRenderer
|
||||
renderer:
|
||||
sh_degree: 3
|
||||
radius: 0.6
|
||||
mlp_network_config:
|
||||
n_neurons: ${system.renderer.gs_out.in_channels}
|
||||
n_hidden_layers: 2
|
||||
activation: silu
|
||||
gs_out:
|
||||
in_channels: 128
|
||||
xyz_offset: true
|
||||
restrict_offset: true
|
||||
use_rgb: false
|
||||
feature_channels:
|
||||
xyz: 3
|
||||
scaling: 3
|
||||
rotation: 4
|
||||
opacity: 1
|
||||
shs: ${shsdim:${system.renderer.sh_degree}}
|
||||
clip_scaling: 0.2
|
||||
+7
-2
@@ -16,8 +16,13 @@ if exist "%python_miniconda_exec%" (
|
||||
git clone --recursive https://github.com/NVlabs/nvdiffrast
|
||||
"%python_miniconda_exec%" -s -m pip install ./nvdiffrast
|
||||
"%python_miniconda_exec%" -s -m pip install git+https://github.com/facebookresearch/pytorch3d.git@stable
|
||||
)
|
||||
else (
|
||||
|
||||
"%python_miniconda_exec%" -s -m pip install git+https://github.com/rusty1s/pytorch_scatter.git
|
||||
|
||||
set "python_miniconda_exec=..\..\..\..\..\..\..\python_miniconda_env\ComfyUI\python.exe"
|
||||
|
||||
cd tgs/models/snowflake/pointnet2_ops_lib && "%python_miniconda_exec%" -s setup.py install && cd ../../../../
|
||||
) else (
|
||||
echo ERROR: Cannot find Miniconda Environment "%python_miniconda_exec%"
|
||||
)
|
||||
|
||||
|
||||
@@ -240,10 +240,6 @@ class Mesh:
|
||||
albedo = albedo.astype(np.float32) / 255
|
||||
print(f"[load_obj] load texture: {albedo.shape}")
|
||||
|
||||
# import matplotlib.pyplot as plt
|
||||
# plt.imshow(albedo)
|
||||
# plt.show()
|
||||
|
||||
mesh.albedo = torch.tensor(albedo, dtype=torch.float32, device=device)
|
||||
|
||||
return mesh
|
||||
|
||||
@@ -2,6 +2,9 @@ import torch
|
||||
import numpy as np
|
||||
import pymeshlab as pml
|
||||
|
||||
from .mesh import PointCloud
|
||||
from ..shared_utils.sh_utils import SH2RGB, RGB2SH
|
||||
|
||||
def _base_face_areas(face_vertices_0, face_vertices_1, face_vertices_2):
|
||||
"""Base function to compute the face areas."""
|
||||
x1, x2, x3 = torch.split(face_vertices_0 - face_vertices_1, 1, dim=-1)
|
||||
@@ -297,3 +300,44 @@ def clean_mesh(
|
||||
)
|
||||
|
||||
return verts, faces
|
||||
|
||||
def read_gs_ply(plydata):
|
||||
xyz = np.stack((np.asarray(plydata.elements[0]["x"]),
|
||||
np.asarray(plydata.elements[0]["y"]),
|
||||
np.asarray(plydata.elements[0]["z"])), axis=1)
|
||||
opacities = np.asarray(plydata.elements[0]["opacity"])[..., np.newaxis]
|
||||
|
||||
features_dc = np.zeros((xyz.shape[0], 3, 1))
|
||||
features_dc[:, 0, 0] = np.asarray(plydata.elements[0]["f_dc_0"])
|
||||
features_dc[:, 1, 0] = np.asarray(plydata.elements[0]["f_dc_1"])
|
||||
features_dc[:, 2, 0] = np.asarray(plydata.elements[0]["f_dc_2"])
|
||||
|
||||
extra_f_names = [p.name for p in plydata.elements[0].properties if p.name.startswith("f_rest_")]
|
||||
#assert len(extra_f_names)!=3*(max_sh_degree + 1) ** 2 - 3:
|
||||
max_sh_degree = int(((len(extra_f_names) + 3) / 3) ** 0.5 - 1)
|
||||
features_extra = np.zeros((xyz.shape[0], len(extra_f_names)))
|
||||
for idx, attr_name in enumerate(extra_f_names):
|
||||
features_extra[:, idx] = np.asarray(plydata.elements[0][attr_name])
|
||||
# Reshape (P,F*SH_coeffs) to (P, F, SH_coeffs except DC)
|
||||
features_extra = features_extra.reshape((features_extra.shape[0], 3, (max_sh_degree + 1) ** 2 - 1))
|
||||
|
||||
scale_names = [p.name for p in plydata.elements[0].properties if p.name.startswith("scale_")]
|
||||
scales = np.zeros((xyz.shape[0], len(scale_names)))
|
||||
for idx, attr_name in enumerate(scale_names):
|
||||
scales[:, idx] = np.asarray(plydata.elements[0][attr_name])
|
||||
|
||||
rot_names = [p.name for p in plydata.elements[0].properties if p.name.startswith("rot")]
|
||||
rots = np.zeros((xyz.shape[0], len(rot_names)))
|
||||
for idx, attr_name in enumerate(rot_names):
|
||||
rots[:, idx] = np.asarray(plydata.elements[0][attr_name])
|
||||
|
||||
return xyz, features_dc, features_extra, opacities, scales, rots
|
||||
|
||||
def ply_to_points_cloud(plydata):
|
||||
xyz, features_dc, features_extra, opacities, scales, rots = read_gs_ply(plydata)
|
||||
features_dc = np.transpose(features_dc, (0, 2, 1)) # equivalent of torch.transpose(features_dc, 1, 2)
|
||||
features_extra = np.transpose(features_extra, (0, 2, 1))
|
||||
shs = np.concatenate((features_dc, features_extra), axis=1)
|
||||
normals = np.zeros_like(xyz)
|
||||
pcd = PointCloud(points=xyz, colors=SH2RGB(shs), normals=normals)
|
||||
return pcd
|
||||
@@ -1,18 +1,31 @@
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import math
|
||||
import copy
|
||||
from enum import Enum
|
||||
import subprocess
|
||||
import folder_paths as comfy_paths
|
||||
|
||||
import torch
|
||||
from torch.utils.data import DataLoader
|
||||
import numpy as np
|
||||
|
||||
from plyfile import PlyData
|
||||
|
||||
from .shared_utils.common_utils import cstr
|
||||
from .mesh_processer.mesh import Mesh
|
||||
from .mesh_processer.mesh_utils import ply_to_points_cloud
|
||||
from .algorithms.main_3DGS import GaussianSplatting, GSParams
|
||||
from .algorithms.diff_texturing import DiffTextureBaker
|
||||
from .algorithms.dmtet import DMTetMesh
|
||||
from .algorithms.triplane_gaussian_transformers import TGS
|
||||
from .tgs.utils.config import ExperimentConfig, load_config
|
||||
from .tgs.data import CustomImageOrbitDataset
|
||||
from .tgs.utils.misc import todevice, get_device
|
||||
|
||||
ROOT_PATH = os.path.join(comfy_paths.get_folder_paths("custom_nodes")[0], "ComfyUI-3D-Pack")
|
||||
sys.path.append(ROOT_PATH)
|
||||
|
||||
MANIFEST = {
|
||||
"name": "ComfyUI-3D-Pack",
|
||||
@@ -83,38 +96,34 @@ class Load_3DGS:
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"raw_3DGS_file_path": ("STRING", {"default": '', "multiline": False}),
|
||||
"gs_ply_file_path": ("STRING", {"default": '', "multiline": False}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (
|
||||
"GS_RAW",
|
||||
|
||||
"GS_PLY",
|
||||
)
|
||||
RETURN_NAMES = (
|
||||
"raw_3DGS",
|
||||
"gs_ply",
|
||||
)
|
||||
FUNCTION = "load_mesh"
|
||||
FUNCTION = "load_gs"
|
||||
CATEGORY = "ComfyUI3D/Import|Export"
|
||||
|
||||
def load_mesh(self, raw_3DGS_file_path):
|
||||
raw_3DGS = None
|
||||
def load_gs(self, gs_ply_file_path):
|
||||
gs_ply = None
|
||||
|
||||
if not os.path.isabs(raw_3DGS_file_path):
|
||||
raw_3DGS_file_path = os.path.join(comfy_paths.input_directory, raw_3DGS_file_path)
|
||||
if not os.path.isabs(gs_ply_file_path):
|
||||
gs_ply_file_path = os.path.join(comfy_paths.input_directory, gs_ply_file_path)
|
||||
|
||||
if os.path.exists(raw_3DGS_file_path):
|
||||
folder, filename = os.path.split(raw_3DGS_file_path)
|
||||
if os.path.exists(gs_ply_file_path):
|
||||
folder, filename = os.path.split(gs_ply_file_path)
|
||||
if filename.lower().endswith(SUPPORTED_3DGS_EXTENSIONS):
|
||||
with torch.inference_mode(False):
|
||||
raw_3DGS = GaussianSplatting()
|
||||
raw_3DGS.renderer.gaussians.load_ply(raw_3DGS_file_path)
|
||||
# TODO: better way of loading 3DGS, since this method omit render & training parameters
|
||||
gs_ply = PlyData.read(gs_ply_file_path)
|
||||
else:
|
||||
cstr(f"[{self.__class__.__name__}] File name {filename} does not end with supported 3DGS file extensions: {SUPPORTED_3DGS_EXTENSIONS}").error.print()
|
||||
else:
|
||||
cstr(f"[{self.__class__.__name__}] File {raw_3DGS_file_path} does not exist").error.print()
|
||||
return (raw_3DGS, )
|
||||
cstr(f"[{self.__class__.__name__}] File {gs_ply_file_path} does not exist").error.print()
|
||||
return (gs_ply, )
|
||||
|
||||
class Save_3D_Mesh:
|
||||
|
||||
@@ -156,7 +165,7 @@ class Save_3DGS:
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"raw_3DGS": ("GS_RAW",),
|
||||
"gs_ply": ("GS_PLY",),
|
||||
"save_path": ("STRING", {"default": '3DGS_[time(%Y-%m-%d)].ply', "multiline": False}),
|
||||
},
|
||||
}
|
||||
@@ -166,7 +175,7 @@ class Save_3DGS:
|
||||
FUNCTION = "save_3DGS"
|
||||
CATEGORY = "ComfyUI3D/Import|Export"
|
||||
|
||||
def save_3DGS(self, raw_3DGS, save_path):
|
||||
def save_3DGS(self, gs_ply, save_path):
|
||||
|
||||
folder_path, filename = os.path.split(save_path)
|
||||
|
||||
@@ -177,8 +186,7 @@ class Save_3DGS:
|
||||
|
||||
os.makedirs(folder_path, exist_ok=True)
|
||||
|
||||
raw_3DGS.renderer.gaussians.save_ply(save_path)
|
||||
# TODO: better way of saving 3DGS, since this method omit render & training parameters
|
||||
gs_ply.write(save_path)
|
||||
else:
|
||||
cstr(f"[{self.__class__.__name__}] File name {filename} does not end with supported 3DGS file extensions: {SUPPORTED_3DGS_EXTENSIONS}").error.print()
|
||||
|
||||
@@ -190,7 +198,7 @@ class Convert_3DGS_To_Pointcloud:
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"raw_3DGS": ("GS_RAW",),
|
||||
"gs_ply": ("GS_PLY",),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -200,12 +208,12 @@ class Convert_3DGS_To_Pointcloud:
|
||||
RETURN_NAMES = (
|
||||
"points_cloud",
|
||||
)
|
||||
FUNCTION = "convert_3DGS"
|
||||
FUNCTION = "convert_gs_ply"
|
||||
CATEGORY = "ComfyUI3D/Preprocessor"
|
||||
|
||||
def convert_3DGS(self, raw_3DGS):
|
||||
def convert_gs_ply(self, gs_ply):
|
||||
|
||||
points_cloud = raw_3DGS.renderer.gaussians.get_points_cloud()
|
||||
points_cloud = ply_to_points_cloud(gs_ply)
|
||||
|
||||
return (points_cloud, )
|
||||
|
||||
@@ -510,7 +518,7 @@ class Gaussian_Splatting:
|
||||
"reference_orbit_camera_poses": ("ORBIT_CAMPOSES",), # (orbit radius, elevation, azimuth, orbit center X, orbit center Y, orbit center Z)
|
||||
"reference_orbit_camera_fovy": ("FLOAT", {"default": 49.1, "min": 0.0, "max": 180.0, "step": 0.1}),
|
||||
"training_iterations": ("INT", {"default": 1000, "min": 1, "max": 100000}),
|
||||
"batch_size": ("INT", {"default": 5, "min": 1, "max": 0xffffffffffffffff}),
|
||||
"batch_size": ("INT", {"default": 3, "min": 1, "max": 0xffffffffffffffff}),
|
||||
"loss_value_scale": ("FLOAT", {"default": 10000.0, "min": 1.0}),
|
||||
"ms_ssim_loss_weight": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 1.0, }),
|
||||
"alpha_loss_weight": ("FLOAT", {"default": 3, "min": 0.0, }),
|
||||
@@ -537,53 +545,58 @@ class Gaussian_Splatting:
|
||||
},
|
||||
|
||||
"optional": {
|
||||
"points_cloud": ("POINTCLOUD",),
|
||||
"ply_to_initialize_gaussian": ("GS_PLY",),
|
||||
"mesh_to_initialize_gaussian": ("MESH",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (
|
||||
"GS_RAW",
|
||||
"GS_PLY",
|
||||
)
|
||||
RETURN_NAMES = (
|
||||
"raw_3DGS",
|
||||
"gs_ply",
|
||||
)
|
||||
FUNCTION = "run_3DGS"
|
||||
CATEGORY = "ComfyUI3D/Algorithm"
|
||||
|
||||
def run_3DGS(self,
|
||||
reference_images,
|
||||
reference_masks,
|
||||
reference_orbit_camera_poses,
|
||||
reference_orbit_camera_fovy,
|
||||
training_iterations,
|
||||
batch_size,
|
||||
loss_value_scale,
|
||||
ms_ssim_loss_weight,
|
||||
alpha_loss_weight,
|
||||
offset_loss_weight,
|
||||
offset_opacity_loss_weight,
|
||||
invert_background_probability,
|
||||
feature_learning_rate,
|
||||
opacity_learning_rate,
|
||||
scaling_learning_rate,
|
||||
rotation_learning_rate,
|
||||
position_learning_rate_init,
|
||||
position_learning_rate_final,
|
||||
position_learning_rate_delay_mult,
|
||||
position_learning_rate_max_steps,
|
||||
initial_gaussians_num,
|
||||
K_nearest_neighbors,
|
||||
percent_dense,
|
||||
density_start_iterations,
|
||||
density_end_iterations,
|
||||
densification_interval,
|
||||
opacity_reset_interval,
|
||||
densify_grad_threshold,
|
||||
gaussian_sh_degree,
|
||||
mesh_to_initialize_gaussian=None):
|
||||
def run_3DGS(
|
||||
self,
|
||||
reference_images,
|
||||
reference_masks,
|
||||
reference_orbit_camera_poses,
|
||||
reference_orbit_camera_fovy,
|
||||
training_iterations,
|
||||
batch_size,
|
||||
loss_value_scale,
|
||||
ms_ssim_loss_weight,
|
||||
alpha_loss_weight,
|
||||
offset_loss_weight,
|
||||
offset_opacity_loss_weight,
|
||||
invert_background_probability,
|
||||
feature_learning_rate,
|
||||
opacity_learning_rate,
|
||||
scaling_learning_rate,
|
||||
rotation_learning_rate,
|
||||
position_learning_rate_init,
|
||||
position_learning_rate_final,
|
||||
position_learning_rate_delay_mult,
|
||||
position_learning_rate_max_steps,
|
||||
initial_gaussians_num,
|
||||
K_nearest_neighbors,
|
||||
percent_dense,
|
||||
density_start_iterations,
|
||||
density_end_iterations,
|
||||
densification_interval,
|
||||
opacity_reset_interval,
|
||||
densify_grad_threshold,
|
||||
gaussian_sh_degree,
|
||||
points_cloud=None,
|
||||
ply_to_initialize_gaussian=None,
|
||||
mesh_to_initialize_gaussian=None,
|
||||
):
|
||||
|
||||
|
||||
raw_3DGS = None
|
||||
gs_ply = None
|
||||
|
||||
ref_imgs_num = len(reference_images)
|
||||
ref_masks_num = len(reference_masks)
|
||||
@@ -624,18 +637,26 @@ class Gaussian_Splatting:
|
||||
densify_grad_threshold,
|
||||
gaussian_sh_degree)
|
||||
|
||||
gs = GaussianSplatting(gs_params, mesh_to_initialize_gaussian)
|
||||
|
||||
if points_cloud is not None:
|
||||
gs_init_input = points_cloud
|
||||
elif ply_to_initialize_gaussian is not None:
|
||||
gs_init_input = ply_to_initialize_gaussian
|
||||
else:
|
||||
gs_init_input = mesh_to_initialize_gaussian
|
||||
|
||||
gs = GaussianSplatting(gs_params, gs_init_input)
|
||||
gs.prepare_training(reference_images, reference_masks, reference_orbit_camera_poses, reference_orbit_camera_fovy)
|
||||
gs.training()
|
||||
|
||||
raw_3DGS = gs
|
||||
gs_ply = gs.renderer.gaussians.to_ply()
|
||||
|
||||
else:
|
||||
cstr(f"[{self.__class__.__name__}] Number of reference images {ref_imgs_num} does not equal to number of reference camera poses {ref_cam_poses_num}").error.print()
|
||||
else:
|
||||
cstr(f"[{self.__class__.__name__}] Number of reference images {ref_imgs_num} does not equal to number of masks {ref_masks_num}").error.print()
|
||||
|
||||
return (raw_3DGS, )
|
||||
return (gs_ply, )
|
||||
|
||||
class Bake_Texture_To_Mesh:
|
||||
|
||||
@@ -652,7 +673,7 @@ class Bake_Texture_To_Mesh:
|
||||
"reference_orbit_camera_fovy": ("FLOAT", {"default": 49.1, "min": 0.0, "max": 180.0, "step": 0.1}),
|
||||
"mesh": ("MESH",),
|
||||
"training_iterations": ("INT", {"default": 1000, "min": 1, "max": 100000}),
|
||||
"batch_size": ("INT", {"default": 5, "min": 1, "max": 0xffffffffffffffff}),
|
||||
"batch_size": ("INT", {"default": 3, "min": 1, "max": 0xffffffffffffffff}),
|
||||
"texture_learning_rate": ("FLOAT", {"default": 0.1, "min": 0.00001, "step": 0.00001}),
|
||||
"train_mesh_geometry": ("BOOLEAN", {"default": False},),
|
||||
"geometry_learning_rate": ("FLOAT", {"default": 0.01, "min": 0.00001, "step": 0.00001}),
|
||||
@@ -761,4 +782,112 @@ class Deep_Marching_Tetrahedrons:
|
||||
|
||||
dmtet_mesh.training(reference_points_cloud, reference_images, reference_masks)
|
||||
mesh = dmtet_mesh.get_mesh()
|
||||
return (mesh, )
|
||||
return (mesh, )
|
||||
|
||||
TGS_CONFIG_PATH = "configs/tgs_config.yaml"
|
||||
|
||||
class Load_Triplane_Gaussian_Transformers:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
},
|
||||
|
||||
"optional": {
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (
|
||||
"TGS_MODEL",
|
||||
)
|
||||
RETURN_NAMES = (
|
||||
"tgs_model",
|
||||
)
|
||||
FUNCTION = "load_TGS"
|
||||
CATEGORY = "ComfyUI3D/Import|Export"
|
||||
|
||||
def load_TGS(self):
|
||||
|
||||
device = get_device()
|
||||
|
||||
config_path = os.path.join(ROOT_PATH, TGS_CONFIG_PATH)
|
||||
cfg: ExperimentConfig = load_config(config_path)
|
||||
|
||||
# Download pre-trained model if it not exist locally
|
||||
model_name = "model_lvis_rel.ckpt"
|
||||
model_path = os.path.join(ROOT_PATH, f"checkpoints/{model_name}")
|
||||
if not os.path.isfile(model_path):
|
||||
from huggingface_hub import hf_hub_download
|
||||
hf_hub_download(repo_id="VAST-AI/TriplaneGaussian", local_dir="./checkpoints", filename=model_name, repo_type="model")
|
||||
|
||||
cfg.system.weights=model_path
|
||||
tgs_model = TGS(cfg=cfg.system).to(device)
|
||||
|
||||
save_path = os.path.join(ROOT_PATH, "outputs")
|
||||
tgs_model.set_save_dir(save_path)
|
||||
|
||||
cstr(f"[{self.__class__.__name__}] loaded model ckpt from {model_path}").msg.print()
|
||||
|
||||
return (tgs_model, )
|
||||
|
||||
class Run_Triplane_Gaussian_Transformers:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"reference_image": ("IMAGE", ),
|
||||
"reference_mask": ("MASK",),
|
||||
"tgs_model": ("TGS_MODEL", ),
|
||||
"cam_dist": ("FLOAT", {"default": 1.9, "min": 0.01, "step": 0.01}),
|
||||
},
|
||||
}
|
||||
|
||||
OUTPUT_NODE = True
|
||||
RETURN_TYPES = (
|
||||
"GS_PLY",
|
||||
"FLOAT"
|
||||
)
|
||||
RETURN_NAMES = (
|
||||
"gs_ply",
|
||||
"gaussian_sh_degree"
|
||||
)
|
||||
FUNCTION = "run_TGS"
|
||||
CATEGORY = "ComfyUI3D/Algorithm"
|
||||
|
||||
def run_TGS(self, reference_image, reference_mask, tgs_model, cam_dist):
|
||||
config_path = os.path.join(ROOT_PATH, TGS_CONFIG_PATH)
|
||||
cfg: ExperimentConfig = load_config(config_path)
|
||||
|
||||
#save_path = os.path.join(ROOT_PATH, "outputs")
|
||||
#tgs_model.set_save_dir(save_path)
|
||||
|
||||
cfg.data.cond_camera_distance = cam_dist
|
||||
cfg.data.eval_camera_distance = cam_dist
|
||||
dataset = CustomImageOrbitDataset(reference_image, reference_mask, cfg.data)
|
||||
dataloader = DataLoader(dataset,
|
||||
batch_size=cfg.data.eval_batch_size,
|
||||
shuffle=False,
|
||||
collate_fn=dataset.collate
|
||||
)
|
||||
|
||||
|
||||
gs_ply = []
|
||||
for batch in dataloader:
|
||||
batch = todevice(batch)
|
||||
gs_ply.extend(tgs_model(batch))
|
||||
|
||||
"""
|
||||
# Output rendered video, for testing this node only
|
||||
tgs_model.save_img_sequences(
|
||||
"video",
|
||||
"(\d+)\.png",
|
||||
save_format="mp4",
|
||||
fps=30,
|
||||
delete=True,
|
||||
)
|
||||
"""
|
||||
|
||||
return (gs_ply[0], tgs_model.renderer.cfg.sh_degree)
|
||||
|
||||
+12
-1
@@ -1,14 +1,24 @@
|
||||
#base
|
||||
cmake
|
||||
ninja
|
||||
kornia
|
||||
OmegaConf
|
||||
numpy
|
||||
einops
|
||||
scipy
|
||||
opencv-python
|
||||
kornia
|
||||
|
||||
# for use ML models
|
||||
diffusers
|
||||
transformers
|
||||
|
||||
# for training differentiable tensors
|
||||
pytorch_msssim
|
||||
|
||||
# for process images & videos
|
||||
imageio
|
||||
imageio-ffmpeg
|
||||
|
||||
# for dmtet and mesh import & export
|
||||
trimesh
|
||||
plyfile
|
||||
@@ -19,3 +29,4 @@ pymeshlab
|
||||
# extra
|
||||
torchtyping
|
||||
tqdm
|
||||
jaxtyping
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
set "python_miniconda_exec=..\..\..\python_miniconda_env\ComfyUI\python.exe"
|
||||
set "python_miniconda_exec=..\..\..\..\..\..\..\python_miniconda_env\ComfyUI\python.exe"
|
||||
|
||||
echo ERROR: Cannot find Miniconda Environment "%python_miniconda_exec%"
|
||||
|
||||
pause
|
||||
cd tgs/models/snowflake/pointnet2_ops_lib && "%python_miniconda_exec%" -s setup.py install && cd ../../../../
|
||||
@@ -0,0 +1,11 @@
|
||||
import importlib
|
||||
from .utils.typing import *
|
||||
|
||||
def find(cls_string) -> Type:
|
||||
cls_full_name = cls_string.split(".")
|
||||
module_string = ".".join(cls_full_name[:-1])
|
||||
cls_name = cls_full_name[-1]
|
||||
print(module_string)
|
||||
module = importlib.import_module(module_string, package=__name__)
|
||||
cls = getattr(module, cls_name)
|
||||
return cls
|
||||
+237
@@ -0,0 +1,237 @@
|
||||
import json
|
||||
import math
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
import os
|
||||
import imageio
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from PIL import Image
|
||||
from torch.utils.data import Dataset
|
||||
|
||||
from .utils.config import parse_structured
|
||||
from .utils.ops import get_intrinsic_from_fov, get_ray_directions, get_rays
|
||||
from .utils.typing import *
|
||||
|
||||
@dataclass
|
||||
class CustomImageDataModuleConfig:
|
||||
background_color: Tuple[float, float, float] = field(
|
||||
default_factory=lambda: (1.0, 1.0, 1.0)
|
||||
)
|
||||
|
||||
relative_pose: bool = False
|
||||
cond_height: int = 512
|
||||
cond_width: int = 512
|
||||
cond_camera_distance: float = 1.6
|
||||
cond_fovy_deg: float = 40.0
|
||||
cond_elevation_deg: float = 0.0
|
||||
cond_azimuth_deg: float = 0.0
|
||||
num_workers: int = 16
|
||||
|
||||
eval_height: int = 512
|
||||
eval_width: int = 512
|
||||
eval_batch_size: int = 1
|
||||
eval_elevation_deg: float = 0.0
|
||||
eval_camera_distance: float = 1.6
|
||||
eval_fovy_deg: float = 40.0
|
||||
n_test_views: int = 120
|
||||
num_views_output: int = 120
|
||||
only_3dgs: bool = False
|
||||
|
||||
class CustomImageOrbitDataset(Dataset):
|
||||
def __init__(self, reference_image, reference_mask, cfg: Any) -> None:
|
||||
super().__init__()
|
||||
self.cfg: CustomImageDataModuleConfig = parse_structured(CustomImageDataModuleConfig, cfg)
|
||||
|
||||
self.n_views = self.cfg.n_test_views
|
||||
assert self.n_views % self.cfg.num_views_output == 0
|
||||
|
||||
self.all_images = reference_image
|
||||
self.all_masks = reference_mask
|
||||
|
||||
azimuth_deg: Float[Tensor, "B"] = torch.linspace(0, 360.0, self.n_views + 1)[
|
||||
: self.n_views
|
||||
]
|
||||
elevation_deg: Float[Tensor, "B"] = torch.full_like(
|
||||
azimuth_deg, self.cfg.eval_elevation_deg
|
||||
)
|
||||
camera_distances: Float[Tensor, "B"] = torch.full_like(
|
||||
elevation_deg, self.cfg.eval_camera_distance
|
||||
)
|
||||
|
||||
elevation = elevation_deg * math.pi / 180
|
||||
azimuth = azimuth_deg * math.pi / 180
|
||||
|
||||
# convert spherical coordinates to cartesian coordinates
|
||||
# right hand coordinate system, x back, y right, z up
|
||||
# elevation in (-90, 90), azimuth from +x to +y in (-180, 180)
|
||||
camera_positions: Float[Tensor, "B 3"] = torch.stack(
|
||||
[
|
||||
camera_distances * torch.cos(elevation) * torch.cos(azimuth),
|
||||
camera_distances * torch.cos(elevation) * torch.sin(azimuth),
|
||||
camera_distances * torch.sin(elevation),
|
||||
],
|
||||
dim=-1,
|
||||
)
|
||||
|
||||
# default scene center at origin
|
||||
center: Float[Tensor, "B 3"] = torch.zeros_like(camera_positions)
|
||||
# default camera up direction as +z
|
||||
up: Float[Tensor, "B 3"] = torch.as_tensor([0, 0, 1], dtype=torch.float32)[
|
||||
None, :
|
||||
].repeat(self.n_views, 1)
|
||||
|
||||
fovy_deg: Float[Tensor, "B"] = torch.full_like(
|
||||
elevation_deg, self.cfg.eval_fovy_deg
|
||||
)
|
||||
fovy = fovy_deg * math.pi / 180
|
||||
|
||||
lookat: Float[Tensor, "B 3"] = F.normalize(center - camera_positions, dim=-1)
|
||||
right: Float[Tensor, "B 3"] = F.normalize(torch.cross(lookat, up), dim=-1)
|
||||
up = F.normalize(torch.cross(right, lookat), dim=-1)
|
||||
c2w3x4: Float[Tensor, "B 3 4"] = torch.cat(
|
||||
[torch.stack([right, up, -lookat], dim=-1), camera_positions[:, :, None]],
|
||||
dim=-1,
|
||||
)
|
||||
c2w: Float[Tensor, "B 4 4"] = torch.cat(
|
||||
[c2w3x4, torch.zeros_like(c2w3x4[:, :1])], dim=1
|
||||
)
|
||||
c2w[:, 3, 3] = 1.0
|
||||
|
||||
# get directions by dividing directions_unit_focal by focal length
|
||||
focal_length: Float[Tensor, "B"] = (
|
||||
0.5 * self.cfg.eval_height / torch.tan(0.5 * fovy)
|
||||
)
|
||||
directions_unit_focal = get_ray_directions(
|
||||
H=self.cfg.eval_height,
|
||||
W=self.cfg.eval_width,
|
||||
focal=1.0,
|
||||
)
|
||||
directions: Float[Tensor, "B H W 3"] = directions_unit_focal[
|
||||
None, :, :, :
|
||||
].repeat(self.n_views, 1, 1, 1)
|
||||
directions[:, :, :, :2] = (
|
||||
directions[:, :, :, :2] / focal_length[:, None, None, None]
|
||||
)
|
||||
# must use normalize=True to normalize directions here
|
||||
rays_o, rays_d = get_rays(directions, c2w, keepdim=True)
|
||||
|
||||
intrinsic: Float[Tensor, "B 3 3"] = get_intrinsic_from_fov(
|
||||
self.cfg.eval_fovy_deg * math.pi / 180,
|
||||
H=self.cfg.eval_height,
|
||||
W=self.cfg.eval_width,
|
||||
bs=self.n_views,
|
||||
)
|
||||
intrinsic_normed: Float[Tensor, "B 3 3"] = intrinsic.clone()
|
||||
intrinsic_normed[..., 0, 2] /= self.cfg.eval_width
|
||||
intrinsic_normed[..., 1, 2] /= self.cfg.eval_height
|
||||
intrinsic_normed[..., 0, 0] /= self.cfg.eval_width
|
||||
intrinsic_normed[..., 1, 1] /= self.cfg.eval_height
|
||||
|
||||
self.rays_o, self.rays_d = rays_o, rays_d
|
||||
self.intrinsic = intrinsic
|
||||
self.intrinsic_normed = intrinsic_normed
|
||||
self.c2w = c2w
|
||||
self.camera_positions = camera_positions
|
||||
|
||||
self.background_color = torch.as_tensor(self.cfg.background_color)
|
||||
|
||||
# condition
|
||||
self.intrinsic_cond = get_intrinsic_from_fov(
|
||||
np.deg2rad(self.cfg.cond_fovy_deg),
|
||||
H=self.cfg.cond_height,
|
||||
W=self.cfg.cond_width,
|
||||
)
|
||||
self.intrinsic_normed_cond = self.intrinsic_cond.clone()
|
||||
self.intrinsic_normed_cond[..., 0, 2] /= self.cfg.cond_width
|
||||
self.intrinsic_normed_cond[..., 1, 2] /= self.cfg.cond_height
|
||||
self.intrinsic_normed_cond[..., 0, 0] /= self.cfg.cond_width
|
||||
self.intrinsic_normed_cond[..., 1, 1] /= self.cfg.cond_height
|
||||
|
||||
|
||||
if self.cfg.relative_pose:
|
||||
self.c2w_cond = torch.as_tensor(
|
||||
[
|
||||
[0, 0, 1, self.cfg.cond_camera_distance],
|
||||
[1, 0, 0, 0],
|
||||
[0, 1, 0, 0],
|
||||
[0, 0, 0, 1],
|
||||
]
|
||||
).float()
|
||||
else:
|
||||
cond_elevation = self.cfg.cond_elevation_deg * math.pi / 180
|
||||
cond_azimuth = self.cfg.cond_azimuth_deg * math.pi / 180
|
||||
cond_camera_position: Float[Tensor, "3"] = torch.as_tensor(
|
||||
[
|
||||
self.cfg.cond_camera_distance * np.cos(cond_elevation) * np.cos(cond_azimuth),
|
||||
self.cfg.cond_camera_distance * np.cos(cond_elevation) * np.sin(cond_azimuth),
|
||||
self.cfg.cond_camera_distance * np.sin(cond_elevation),
|
||||
], dtype=torch.float32
|
||||
)
|
||||
|
||||
cond_center: Float[Tensor, "3"] = torch.zeros_like(cond_camera_position)
|
||||
cond_up: Float[Tensor, "3"] = torch.as_tensor([0, 0, 1], dtype=torch.float32)
|
||||
cond_lookat: Float[Tensor, "3"] = F.normalize(cond_center - cond_camera_position, dim=-1)
|
||||
cond_right: Float[Tensor, "3"] = F.normalize(torch.cross(cond_lookat, cond_up), dim=-1)
|
||||
cond_up = F.normalize(torch.cross(cond_right, cond_lookat), dim=-1)
|
||||
cond_c2w3x4: Float[Tensor, "3 4"] = torch.cat(
|
||||
[torch.stack([cond_right, cond_up, -cond_lookat], dim=-1), cond_camera_position[:, None]],
|
||||
dim=-1,
|
||||
)
|
||||
cond_c2w: Float[Tensor, "4 4"] = torch.cat(
|
||||
[cond_c2w3x4, torch.zeros_like(cond_c2w3x4[:1])], dim=0
|
||||
)
|
||||
cond_c2w[3, 3] = 1.0
|
||||
self.c2w_cond = cond_c2w
|
||||
|
||||
def __len__(self):
|
||||
if self.cfg.only_3dgs:
|
||||
return len(self.all_images)
|
||||
else:
|
||||
return len(self.all_images) * self.n_views // self.cfg.num_views_output
|
||||
|
||||
def __getitem__(self, index):
|
||||
if self.cfg.only_3dgs:
|
||||
scene_index = index
|
||||
view_index = [0]
|
||||
else:
|
||||
scene_index = index * self.cfg.num_views_output // self.n_views
|
||||
view_start = index % (self.n_views // self.cfg.num_views_output)
|
||||
view_index = list(range(self.n_views))[view_start * self.cfg.num_views_output :
|
||||
(view_start + 1) * self.cfg.num_views_output]
|
||||
|
||||
mask_cond: Float[Tensor, "Hc Wc 1"] = self.resize_image(self.all_masks[scene_index].unsqueeze(2))
|
||||
rgb_cond: Float[Tensor, "Hc Wc 3"] = self.resize_image(self.all_images[scene_index]) * mask_cond + self.background_color[None, None, :] * (1 - mask_cond)
|
||||
|
||||
out = {
|
||||
"rgb_cond": rgb_cond,
|
||||
"c2w_cond": self.c2w_cond.unsqueeze(0),
|
||||
"mask_cond": mask_cond,
|
||||
"intrinsic_cond": self.intrinsic_cond.unsqueeze(0),
|
||||
"intrinsic_normed_cond": self.intrinsic_normed_cond.unsqueeze(0),
|
||||
"view_index": torch.as_tensor(view_index),
|
||||
"rays_o": self.rays_o[view_index],
|
||||
"rays_d": self.rays_d[view_index],
|
||||
"intrinsic": self.intrinsic[view_index],
|
||||
"intrinsic_normed": self.intrinsic_normed[view_index],
|
||||
"c2w": self.c2w[view_index],
|
||||
"camera_positions": self.camera_positions[view_index],
|
||||
}
|
||||
out["c2w"][..., :3, 1:3] *= -1
|
||||
out["c2w_cond"][..., :3, 1:3] *= -1
|
||||
out["index"] = torch.as_tensor(scene_index)
|
||||
out["background_color"] = self.background_color
|
||||
instance_id = str(scene_index)
|
||||
out["instance_id"] = instance_id
|
||||
return out
|
||||
|
||||
def resize_image(self, img):
|
||||
img_new = img.permute(2, 0, 1).unsqueeze(0)
|
||||
img_new = F.interpolate(img_new, (self.cfg.cond_width, self.cfg.cond_height), mode="bilinear", align_corners=False).contiguous()
|
||||
return img_new.permute(0, 2, 3, 1)
|
||||
|
||||
def collate(self, batch):
|
||||
batch = torch.utils.data.default_collate(batch)
|
||||
batch.update({"height": self.cfg.eval_height, "width": self.cfg.eval_width})
|
||||
return batch
|
||||
@@ -0,0 +1,48 @@
|
||||
from dataclasses import dataclass
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from einops import rearrange
|
||||
|
||||
from ..utils.base import BaseModule
|
||||
from ..utils.ops import compute_distance_transform
|
||||
from ..utils.typing import *
|
||||
|
||||
class ImageFeature(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
use_rgb: bool = True
|
||||
use_feature: bool = True
|
||||
use_mask: bool = True
|
||||
feature_dim: int = 128
|
||||
out_dim: int = 133
|
||||
backbone: str = "default"
|
||||
freeze_backbone_params: bool = True
|
||||
|
||||
cfg: Config
|
||||
|
||||
def forward(self, rgb, mask=None, feature=None):
|
||||
B, Nv, H, W = rgb.shape[:4]
|
||||
rgb = rearrange(rgb, "B Nv H W C -> (B Nv) C H W")
|
||||
if mask is not None:
|
||||
mask = rearrange(mask, "B Nv H W C -> (B Nv) C H W")
|
||||
|
||||
assert feature is not None
|
||||
# reshape dino tokens to image-like size
|
||||
feature = rearrange(feature, "B (Nv Nt) C -> (B Nv) Nt C", Nv=Nv)
|
||||
feature = feature[:, 1:].reshape(B * Nv, H // 14, W // 14, -1).permute(0, 3, 1, 2).contiguous()
|
||||
feature = F.interpolate(feature, size=(H, W), mode='bilinear', align_corners=False)
|
||||
|
||||
if mask is not None and mask.is_floating_point():
|
||||
mask = mask > 0.5
|
||||
|
||||
image_features = []
|
||||
if self.cfg.use_rgb:
|
||||
image_features.append(rgb)
|
||||
if self.cfg.use_feature:
|
||||
image_features.append(feature)
|
||||
if self.cfg.use_mask:
|
||||
image_features += [mask, compute_distance_transform(mask)]
|
||||
|
||||
# detach features, occur error when with grad
|
||||
image_features = torch.cat(image_features, dim=1)#.detach()
|
||||
return rearrange(image_features, "(B Nv) C H W -> B Nv C H W", B=B, Nv=Nv).squeeze(1)
|
||||
@@ -0,0 +1,204 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from einops import rearrange
|
||||
import numpy as np
|
||||
|
||||
from ..utils.base import BaseModule
|
||||
from ..utils.ops import get_activation
|
||||
from ..utils.typing import *
|
||||
|
||||
class PointOutLayer(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
in_channels: int = 1024
|
||||
out_channels: int = 3
|
||||
cfg: Config
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
self.point_layer = nn.Linear(self.cfg.in_channels, self.cfg.out_channels)
|
||||
self.initialize_weights()
|
||||
|
||||
def initialize_weights(self):
|
||||
nn.init.constant_(self.point_layer.weight, 0)
|
||||
nn.init.constant_(self.point_layer.bias, 0)
|
||||
|
||||
def forward(self, x):
|
||||
return self.point_layer(x)
|
||||
|
||||
class TriplaneUpsampleNetwork(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
in_channels: int = 1024
|
||||
out_channels: int = 80
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
self.upsample = nn.ConvTranspose2d(
|
||||
self.cfg.in_channels, self.cfg.out_channels, kernel_size=2, stride=2
|
||||
)
|
||||
|
||||
def forward(
|
||||
self, triplanes: Float[Tensor, "B 3 Ci Hp Wp"]
|
||||
) -> Float[Tensor, "B 3 Co Hp2 Wp2"]:
|
||||
triplanes_up = rearrange(
|
||||
self.upsample(
|
||||
rearrange(triplanes, "B Np Ci Hp Wp -> (B Np) Ci Hp Wp", Np=3)
|
||||
),
|
||||
"(B Np) Co Hp Wp -> B Np Co Hp Wp",
|
||||
Np=3,
|
||||
)
|
||||
return triplanes_up
|
||||
|
||||
|
||||
class MLP(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
dim_in: int,
|
||||
dim_out: int,
|
||||
n_neurons: int,
|
||||
n_hidden_layers: int,
|
||||
activation: str = "relu",
|
||||
output_activation: Optional[str] = None,
|
||||
bias: bool = True,
|
||||
):
|
||||
super().__init__()
|
||||
layers = [
|
||||
self.make_linear(
|
||||
dim_in, n_neurons, is_first=True, is_last=False, bias=bias
|
||||
),
|
||||
self.make_activation(activation),
|
||||
]
|
||||
for i in range(n_hidden_layers - 1):
|
||||
layers += [
|
||||
self.make_linear(
|
||||
n_neurons, n_neurons, is_first=False, is_last=False, bias=bias
|
||||
),
|
||||
self.make_activation(activation),
|
||||
]
|
||||
layers += [
|
||||
self.make_linear(
|
||||
n_neurons, dim_out, is_first=False, is_last=True, bias=bias
|
||||
)
|
||||
]
|
||||
self.layers = nn.Sequential(*layers)
|
||||
self.output_activation = get_activation(output_activation)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.layers(x)
|
||||
x = self.output_activation(x)
|
||||
return x
|
||||
|
||||
def make_linear(self, dim_in, dim_out, is_first, is_last, bias=True):
|
||||
layer = nn.Linear(dim_in, dim_out, bias=bias)
|
||||
return layer
|
||||
|
||||
def make_activation(self, activation):
|
||||
if activation == "relu":
|
||||
return nn.ReLU(inplace=True)
|
||||
elif activation == "silu":
|
||||
return nn.SiLU(inplace=True)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
class GSProjection(nn.Module):
|
||||
def __init__(self,
|
||||
in_channels: int = 80,
|
||||
sh_degree: int = 3,
|
||||
init_scaling: float = -5.0,
|
||||
init_density: float = 0.1) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.out_keys = GS_KEYS + ["shs"]
|
||||
self.out_channels = GS_CHANNELS + [(sh_degree + 1) ** 2 * 3]
|
||||
|
||||
self.out_layers = nn.ModuleList()
|
||||
for key, ch in zip(self.out_keys, self.out_channels):
|
||||
layer = nn.Linear(in_channels, ch)
|
||||
# initialize
|
||||
nn.init.constant_(layer.weight, 0)
|
||||
nn.init.constant_(layer.bias, 0)
|
||||
|
||||
if key == "scaling":
|
||||
nn.init.constant_(layer.bias, init_scaling)
|
||||
elif key == "rotation":
|
||||
nn.init.constant_(layer.bias, 0)
|
||||
nn.init.constant_(layer.bias[0], 1.0)
|
||||
elif key == "opacity":
|
||||
inverse_sigmoid = lambda x: np.log(x / (1 - x))
|
||||
nn.init.constant_(layer.bias, inverse_sigmoid(init_density))
|
||||
|
||||
self.out_layers.append(layer)
|
||||
|
||||
def forward(self, x):
|
||||
ret = []
|
||||
for k, layer in zip(self.out_keys, self.out_layers):
|
||||
v = layer(x)
|
||||
if k == "rotation":
|
||||
v = torch.nn.functional.normalize(v)
|
||||
elif k == "scaling":
|
||||
v = torch.exp(v)
|
||||
# v = v.detach() # FIXME: for DEBUG
|
||||
elif k == "opacity":
|
||||
v = torch.sigmoid(v)
|
||||
# elif k == "shs":
|
||||
# v = torch.reshape(v, (v.shape[0], -1, 3))
|
||||
ret.append(v)
|
||||
ret = torch.cat(ret, dim=-1)
|
||||
return ret
|
||||
|
||||
def get_encoding(n_input_dims: int, config) -> nn.Module:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def get_mlp(n_input_dims, n_output_dims, config) -> nn.Module:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# Resnet Blocks for pointnet
|
||||
class ResnetBlockFC(nn.Module):
|
||||
''' Fully connected ResNet Block class.
|
||||
|
||||
Args:
|
||||
size_in (int): input dimension
|
||||
size_out (int): output dimension
|
||||
size_h (int): hidden dimension
|
||||
'''
|
||||
|
||||
def __init__(self, size_in, size_out=None, size_h=None):
|
||||
super().__init__()
|
||||
# Attributes
|
||||
if size_out is None:
|
||||
size_out = size_in
|
||||
|
||||
if size_h is None:
|
||||
size_h = min(size_in, size_out)
|
||||
|
||||
self.size_in = size_in
|
||||
self.size_h = size_h
|
||||
self.size_out = size_out
|
||||
# Submodules
|
||||
self.fc_0 = nn.Linear(size_in, size_h)
|
||||
self.fc_1 = nn.Linear(size_h, size_out)
|
||||
self.actvn = nn.ReLU()
|
||||
|
||||
if size_in == size_out:
|
||||
self.shortcut = None
|
||||
else:
|
||||
self.shortcut = nn.Linear(size_in, size_out, bias=False)
|
||||
# Initialization
|
||||
nn.init.zeros_(self.fc_1.weight)
|
||||
|
||||
def forward(self, x):
|
||||
net = self.fc_0(self.actvn(x))
|
||||
dx = self.fc_1(self.actvn(net))
|
||||
|
||||
if self.shortcut is not None:
|
||||
x_s = self.shortcut(x)
|
||||
else:
|
||||
x_s = x
|
||||
|
||||
return x_s + dx
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Songyou Peng, Michael Niemeyer, Lars Mescheder, Marc Pollefeys, Andreas Geiger
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,121 @@
|
||||
# modified from https://github.com/autonomousvision/convolutional_occupancy_networks/blob/master/src/encoder/pointnet.py
|
||||
from dataclasses import dataclass
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from torch_scatter import scatter_mean, scatter_max
|
||||
|
||||
from tgs.utils.base import BaseModule
|
||||
from tgs.models.networks import ResnetBlockFC
|
||||
from tgs.utils.ops import scale_tensor
|
||||
|
||||
class LocalPoolPointnet(BaseModule):
|
||||
''' PointNet-based encoder network with ResNet blocks for each point.
|
||||
Number of input points are fixed.
|
||||
|
||||
Args:
|
||||
c_dim (int): dimension of latent code c
|
||||
dim (int): input points dimension
|
||||
hidden_dim (int): hidden dimension of the network
|
||||
scatter_type (str): feature aggregation when doing local pooling
|
||||
plane_resolution (int): defined resolution for plane feature
|
||||
padding (float): conventional padding paramter of ONet for unit cube, so [-0.5, 0.5] -> [-0.55, 0.55]
|
||||
n_blocks (int): number of blocks ResNetBlockFC layers
|
||||
'''
|
||||
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
input_channels: int = 3
|
||||
c_dim: int = 128
|
||||
hidden_dim: int = 128
|
||||
scatter_type: str = "max"
|
||||
plane_size: int = 32
|
||||
n_blocks: int = 5
|
||||
radius: float = 1.
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
self.fc_pos = nn.Linear(self.cfg.input_channels, 2 * self.cfg.hidden_dim)
|
||||
self.blocks = nn.ModuleList([
|
||||
ResnetBlockFC(2 * self.cfg.hidden_dim, self.cfg.hidden_dim) for i in range(self.cfg.n_blocks)
|
||||
])
|
||||
self.fc_c = nn.Linear(self.cfg.hidden_dim, self.cfg.c_dim)
|
||||
|
||||
self.actvn = nn.ReLU()
|
||||
|
||||
if self.cfg.scatter_type == 'max':
|
||||
self.scatter = scatter_max
|
||||
elif self.cfg.scatter_type == 'mean':
|
||||
self.scatter = scatter_mean
|
||||
else:
|
||||
raise ValueError('incorrect scatter type')
|
||||
|
||||
|
||||
def generate_plane_features(self, index, c):
|
||||
# acquire indices of features in plane
|
||||
# xy = normalize_coordinate(p.clone(), plane=plane, padding=self.padding) # normalize to the range of (0, 1)
|
||||
# index = self.coordinate2index(x, self.cfg.plane_size)
|
||||
|
||||
# scatter plane features from points
|
||||
fea_plane = c.new_zeros(index.shape[0], self.cfg.c_dim, self.cfg.plane_size ** 2)
|
||||
c = c.permute(0, 2, 1) # B x 512 x T
|
||||
fea_plane = scatter_mean(c, index, out=fea_plane) # B x 512 x reso^2
|
||||
fea_plane = fea_plane.reshape(index.shape[0], self.cfg.c_dim, self.cfg.plane_size, self.cfg.plane_size) # sparce matrix (B x 512 x reso x reso)
|
||||
|
||||
return fea_plane
|
||||
|
||||
def pool_local(self, xy, index, c):
|
||||
bs, fea_dim = c.shape[0], c.shape[2]
|
||||
keys = xy.keys()
|
||||
|
||||
c_out = 0
|
||||
for key in keys:
|
||||
# scatter plane features from points
|
||||
fea = self.scatter(c.permute(0, 2, 1), index[key], dim_size=self.cfg.plane_size ** 2)
|
||||
if self.scatter == scatter_max:
|
||||
fea = fea[0]
|
||||
# gather feature back to points
|
||||
fea = fea.gather(dim=2, index=index[key].expand(-1, fea_dim, -1))
|
||||
c_out += fea
|
||||
return c_out.permute(0, 2, 1)
|
||||
|
||||
def coordinate2index(self, x):
|
||||
x = (x * self.cfg.plane_size).long()
|
||||
index = x[..., 0] + self.cfg.plane_size * x[..., 1]
|
||||
assert index.max() < self.cfg.plane_size ** 2
|
||||
return index[:, None, :]
|
||||
|
||||
def forward(self, p):
|
||||
batch_size, T, D = p.shape
|
||||
|
||||
# acquire the index for each point
|
||||
coord = {}
|
||||
index = {}
|
||||
|
||||
position = torch.clamp(p[..., :3], -self.cfg.radius + 1e-6, self.cfg.radius - 1e-6)
|
||||
position_norm = scale_tensor(position, (-self.cfg.radius, self.cfg.radius), (0, 1))
|
||||
coord["xy"] = position_norm[..., [0, 1]]
|
||||
coord["xz"] = position_norm[..., [0, 2]]
|
||||
coord["yz"] = position_norm[..., [1, 2]]
|
||||
index["xy"] = self.coordinate2index(coord["xy"])
|
||||
index["xz"] = self.coordinate2index(coord["xz"])
|
||||
index["yz"] = self.coordinate2index(coord["yz"])
|
||||
|
||||
net = self.fc_pos(p)
|
||||
|
||||
net = self.blocks[0](net)
|
||||
for block in self.blocks[1:]:
|
||||
pooled = self.pool_local(coord, index, net)
|
||||
net = torch.cat([net, pooled], dim=2)
|
||||
net = block(net)
|
||||
|
||||
c = self.fc_c(net)
|
||||
|
||||
features = torch.stack([
|
||||
self.generate_plane_features(index["xy"], c),
|
||||
self.generate_plane_features(index["xz"], c),
|
||||
self.generate_plane_features(index["yz"], c)
|
||||
], dim=1)
|
||||
|
||||
return features
|
||||
@@ -0,0 +1,110 @@
|
||||
from dataclasses import dataclass, field
|
||||
import torch
|
||||
from einops import rearrange
|
||||
|
||||
import tgs
|
||||
from tgs.utils.base import BaseModule
|
||||
from tgs.utils.typing import *
|
||||
|
||||
class SimplePointGenerator(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
camera_embedder_cls: str = ""
|
||||
camera_embedder: dict = field(default_factory=dict)
|
||||
|
||||
image_tokenizer_cls: str = ""
|
||||
image_tokenizer: dict = field(default_factory=dict)
|
||||
|
||||
tokenizer_cls: str = ""
|
||||
tokenizer: dict = field(default_factory=dict)
|
||||
|
||||
backbone_cls: str = ""
|
||||
backbone: dict = field(default_factory=dict)
|
||||
|
||||
post_processor_cls: str = ""
|
||||
post_processor: dict = field(default_factory=dict)
|
||||
|
||||
pointcloud_upsampling_cls: str = ""
|
||||
pointcloud_upsampling: dict = field(default_factory=dict)
|
||||
|
||||
flip_c2w_cond: bool = True
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
|
||||
self.image_tokenizer = tgs.find(self.cfg.image_tokenizer_cls)(
|
||||
self.cfg.image_tokenizer
|
||||
)
|
||||
|
||||
assert self.cfg.camera_embedder_cls == 'tgs.models.networks.MLP'
|
||||
weights = self.cfg.camera_embedder.pop("weights") if "weights" in self.cfg.camera_embedder else None
|
||||
self.camera_embedder = tgs.find(self.cfg.camera_embedder_cls)(**self.cfg.camera_embedder)
|
||||
if weights:
|
||||
from tgs.utils.misc import load_module_weights
|
||||
weights_path, module_name = weights.split(":")
|
||||
state_dict = load_module_weights(
|
||||
weights_path, module_name=module_name, map_location="cpu"
|
||||
)
|
||||
self.camera_embedder.load_state_dict(state_dict)
|
||||
|
||||
self.tokenizer = tgs.find(self.cfg.tokenizer_cls)(self.cfg.tokenizer)
|
||||
|
||||
self.backbone = tgs.find(self.cfg.backbone_cls)(self.cfg.backbone)
|
||||
|
||||
self.post_processor = tgs.find(self.cfg.post_processor_cls)(
|
||||
self.cfg.post_processor
|
||||
)
|
||||
|
||||
self.pointcloud_upsampling = tgs.find(self.cfg.pointcloud_upsampling_cls)(self.cfg.pointcloud_upsampling)
|
||||
|
||||
def forward(self, batch, encoder_hidden_states=None, **kwargs):
|
||||
batch_size, n_input_views = batch["rgb_cond"].shape[:2]
|
||||
|
||||
if encoder_hidden_states is None:
|
||||
# Camera modulation
|
||||
c2w_cond = batch["c2w_cond"].clone()
|
||||
if self.cfg.flip_c2w_cond:
|
||||
c2w_cond[..., :3, 1:3] *= -1
|
||||
camera_extri = c2w_cond.view(*c2w_cond.shape[:-2], -1)
|
||||
camera_intri = batch["intrinsic_normed_cond"].view(
|
||||
*batch["intrinsic_normed_cond"].shape[:-2], -1)
|
||||
camera_feats = torch.cat([camera_intri, camera_extri], dim=-1)
|
||||
# camera_feats = rearrange(camera_feats, 'B Nv C -> (B Nv) C')
|
||||
|
||||
camera_feats = self.camera_embedder(camera_feats)
|
||||
|
||||
encoder_hidden_states: Float[Tensor, "B Cit Nit"] = self.image_tokenizer(
|
||||
rearrange(batch["rgb_cond"], 'B Nv H W C -> B Nv C H W'),
|
||||
modulation_cond=camera_feats,
|
||||
)
|
||||
encoder_hidden_states = rearrange(
|
||||
encoder_hidden_states, 'B Nv C Nt -> B (Nv Nt) C', Nv=n_input_views)
|
||||
|
||||
tokens: Float[Tensor, "B Ct Nt"] = self.tokenizer(batch_size)
|
||||
|
||||
tokens = self.backbone(
|
||||
tokens,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
modulation_cond=None,
|
||||
)
|
||||
pointclouds = self.post_processor(self.tokenizer.detokenize(tokens))
|
||||
|
||||
upsampling_input = {
|
||||
"input_image_tokens": encoder_hidden_states.permute(0, 2, 1),
|
||||
"input_image_tokens_global": encoder_hidden_states[:, :1],
|
||||
"c2w_cond": c2w_cond,
|
||||
"rgb_cond": batch["rgb_cond"],
|
||||
"intrinsic_cond": batch["intrinsic_cond"],
|
||||
"intrinsic_normed_cond": batch["intrinsic_normed_cond"],
|
||||
"points": pointclouds.float()
|
||||
}
|
||||
up_results = self.pointcloud_upsampling(upsampling_input)
|
||||
up_results.insert(0, pointclouds)
|
||||
pointclouds = up_results[-1]
|
||||
out = {
|
||||
"points": pointclouds,
|
||||
"up_results": up_results
|
||||
}
|
||||
return out
|
||||
@@ -0,0 +1,427 @@
|
||||
from dataclasses import dataclass, field
|
||||
from collections import defaultdict
|
||||
from diff_gaussian_rasterization import GaussianRasterizationSettings, GaussianRasterizer
|
||||
from plyfile import PlyData, PlyElement
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import numpy as np
|
||||
import math
|
||||
|
||||
from ..utils.typing import *
|
||||
from ..utils.base import BaseModule
|
||||
from ..utils.ops import trunc_exp
|
||||
from ..utils.ops import scale_tensor
|
||||
from .networks import MLP
|
||||
from einops import rearrange, reduce
|
||||
|
||||
inverse_sigmoid = lambda x: np.log(x / (1 - x))
|
||||
|
||||
def getWorld2View2(R, t, translate=np.array([.0, .0, .0]), scale=1.0):
|
||||
Rt = np.zeros((4, 4))
|
||||
Rt[:3, :3] = R.transpose()
|
||||
Rt[:3, 3] = t
|
||||
Rt[3, 3] = 1.0
|
||||
|
||||
C2W = np.linalg.inv(Rt)
|
||||
cam_center = C2W[:3, 3]
|
||||
cam_center = (cam_center + translate) * scale
|
||||
C2W[:3, 3] = cam_center
|
||||
Rt = np.linalg.inv(C2W)
|
||||
return np.float32(Rt)
|
||||
|
||||
def getProjectionMatrix(znear, zfar, fovX, fovY):
|
||||
tanHalfFovY = math.tan((fovY / 2))
|
||||
tanHalfFovX = math.tan((fovX / 2))
|
||||
|
||||
top = tanHalfFovY * znear
|
||||
bottom = -top
|
||||
right = tanHalfFovX * znear
|
||||
left = -right
|
||||
|
||||
P = torch.zeros(4, 4)
|
||||
|
||||
z_sign = 1.0
|
||||
|
||||
P[0, 0] = 2.0 * znear / (right - left)
|
||||
P[1, 1] = 2.0 * znear / (top - bottom)
|
||||
P[0, 2] = (right + left) / (right - left)
|
||||
P[1, 2] = (top + bottom) / (top - bottom)
|
||||
P[3, 2] = z_sign
|
||||
P[2, 2] = z_sign * zfar / (zfar - znear)
|
||||
P[2, 3] = -(zfar * znear) / (zfar - znear)
|
||||
return P
|
||||
|
||||
def intrinsic_to_fov(intrinsic, w, h):
|
||||
fx, fy = intrinsic[0, 0], intrinsic[1, 1]
|
||||
fov_x = 2 * torch.arctan2(w, 2 * fx)
|
||||
fov_y = 2 * torch.arctan2(h, 2 * fy)
|
||||
return fov_x, fov_y
|
||||
|
||||
|
||||
class Camera:
|
||||
def __init__(self, w2c, intrinsic, FoVx, FoVy, height, width, trans=np.array([0.0, 0.0, 0.0]), scale=1.0) -> None:
|
||||
self.FoVx = FoVx
|
||||
self.FoVy = FoVy
|
||||
self.height = height
|
||||
self.width = width
|
||||
self.world_view_transform = w2c.transpose(0, 1)
|
||||
|
||||
self.zfar = 100.0
|
||||
self.znear = 0.01
|
||||
|
||||
self.trans = trans
|
||||
self.scale = scale
|
||||
|
||||
self.projection_matrix = getProjectionMatrix(znear=self.znear, zfar=self.zfar, fovX=self.FoVx, fovY=self.FoVy).transpose(0,1).to(w2c.device)
|
||||
self.full_proj_transform = (self.world_view_transform.unsqueeze(0).bmm(self.projection_matrix.unsqueeze(0))).squeeze(0)
|
||||
self.camera_center = self.world_view_transform.inverse()[3, :3]
|
||||
|
||||
@staticmethod
|
||||
def from_c2w(c2w, intrinsic, height, width):
|
||||
w2c = torch.inverse(c2w)
|
||||
FoVx, FoVy = intrinsic_to_fov(intrinsic, w=torch.tensor(width, device=w2c.device), h=torch.tensor(height, device=w2c.device))
|
||||
return Camera(w2c=w2c, intrinsic=intrinsic, FoVx=FoVx, FoVy=FoVy, height=height, width=width)
|
||||
|
||||
class GaussianModel(NamedTuple):
|
||||
xyz: Tensor
|
||||
opacity: Tensor
|
||||
rotation: Tensor
|
||||
scaling: Tensor
|
||||
shs: Tensor
|
||||
|
||||
def construct_list_of_attributes(self):
|
||||
l = ['x', 'y', 'z', 'nx', 'ny', 'nz']
|
||||
features_dc = self.shs[:, :1]
|
||||
features_rest = self.shs[:, 1:]
|
||||
for i in range(features_dc.shape[1]*features_dc.shape[2]):
|
||||
l.append('f_dc_{}'.format(i))
|
||||
for i in range(features_rest.shape[1]*features_rest.shape[2]):
|
||||
l.append('f_rest_{}'.format(i))
|
||||
l.append('opacity')
|
||||
for i in range(self.scaling.shape[1]):
|
||||
l.append('scale_{}'.format(i))
|
||||
for i in range(self.rotation.shape[1]):
|
||||
l.append('rot_{}'.format(i))
|
||||
return l
|
||||
|
||||
def to_ply(self):
|
||||
|
||||
xyz = self.xyz.detach().cpu().numpy()
|
||||
normals = np.zeros_like(xyz)
|
||||
features_dc = self.shs[:, :1]
|
||||
features_rest = self.shs[:, 1:]
|
||||
f_dc = features_dc.detach().flatten(start_dim=1).contiguous().cpu().numpy()
|
||||
f_rest = features_rest.detach().flatten(start_dim=1).contiguous().cpu().numpy()
|
||||
opacities = inverse_sigmoid(torch.clamp(self.opacity, 1e-3, 1 - 1e-3).detach().cpu().numpy())
|
||||
scale = np.log(self.scaling.detach().cpu().numpy())
|
||||
rotation = self.rotation.detach().cpu().numpy()
|
||||
|
||||
dtype_full = [(attribute, 'f4') for attribute in self.construct_list_of_attributes()]
|
||||
|
||||
elements = np.empty(xyz.shape[0], dtype=dtype_full)
|
||||
attributes = np.concatenate((xyz, normals, f_dc, f_rest, opacities, scale, rotation), axis=1)
|
||||
elements[:] = list(map(tuple, attributes))
|
||||
el = PlyElement.describe(elements, 'vertex')
|
||||
return PlyData([el])
|
||||
|
||||
class GSLayer(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
in_channels: int = 128
|
||||
feature_channels: dict = field(default_factory=dict)
|
||||
xyz_offset: bool = True
|
||||
restrict_offset: bool = False
|
||||
use_rgb: bool = False
|
||||
clip_scaling: Optional[float] = None
|
||||
init_scaling: float = -5.0
|
||||
init_density: float = 0.1
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self, *args, **kwargs) -> None:
|
||||
self.out_layers = nn.ModuleList()
|
||||
for key, out_ch in self.cfg.feature_channels.items():
|
||||
if key == "shs" and self.cfg.use_rgb:
|
||||
out_ch = 3
|
||||
layer = nn.Linear(self.cfg.in_channels, out_ch)
|
||||
|
||||
# initialize
|
||||
if not (key == "shs" and self.cfg.use_rgb):
|
||||
nn.init.constant_(layer.weight, 0)
|
||||
nn.init.constant_(layer.bias, 0)
|
||||
if key == "scaling":
|
||||
nn.init.constant_(layer.bias, self.cfg.init_scaling)
|
||||
elif key == "rotation":
|
||||
nn.init.constant_(layer.bias, 0)
|
||||
nn.init.constant_(layer.bias[0], 1.0)
|
||||
elif key == "opacity":
|
||||
nn.init.constant_(layer.bias, inverse_sigmoid(self.cfg.init_density))
|
||||
|
||||
self.out_layers.append(layer)
|
||||
|
||||
def forward(self, x, pts):
|
||||
ret = {}
|
||||
for k, layer in zip(self.cfg.feature_channels.keys(), self.out_layers):
|
||||
v = layer(x)
|
||||
if k == "rotation":
|
||||
v = torch.nn.functional.normalize(v)
|
||||
elif k == "scaling":
|
||||
v = trunc_exp(v)
|
||||
if self.cfg.clip_scaling is not None:
|
||||
v = torch.clamp(v, min=0, max=self.cfg.clip_scaling)
|
||||
elif k == "opacity":
|
||||
v = torch.sigmoid(v)
|
||||
elif k == "shs":
|
||||
if self.cfg.use_rgb:
|
||||
v = torch.sigmoid(v)
|
||||
v = torch.reshape(v, (v.shape[0], -1, 3))
|
||||
elif k == "xyz":
|
||||
if self.cfg.restrict_offset:
|
||||
max_step = 1.2 / 32
|
||||
v = (torch.sigmoid(v) - 0.5) * max_step
|
||||
v = v + pts if self.cfg.xyz_offset else pts
|
||||
ret[k] = v
|
||||
|
||||
return GaussianModel(**ret)
|
||||
|
||||
class GS3DRenderer(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
mlp_network_config: Optional[dict] = None
|
||||
gs_out: dict = field(default_factory=dict)
|
||||
sh_degree: int = 3
|
||||
scaling_modifier: float = 1.0
|
||||
random_background: bool = False
|
||||
radius: float = 1.0
|
||||
feature_reduction: str = "concat"
|
||||
projection_feature_dim: int = 773
|
||||
background_color: Tuple[float, float, float] = field(
|
||||
default_factory=lambda: (1.0, 1.0, 1.0)
|
||||
)
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self, *args, **kwargs) -> None:
|
||||
if self.cfg.feature_reduction == "mean":
|
||||
mlp_in = 80
|
||||
elif self.cfg.feature_reduction == "concat":
|
||||
mlp_in = 80 * 3
|
||||
else:
|
||||
raise NotImplementedError
|
||||
mlp_in = mlp_in + self.cfg.projection_feature_dim
|
||||
if self.cfg.mlp_network_config is not None:
|
||||
self.mlp_net = MLP(mlp_in, self.cfg.gs_out.in_channels, **self.cfg.mlp_network_config)
|
||||
else:
|
||||
self.cfg.gs_out.in_channels = mlp_in
|
||||
self.gs_net = GSLayer(self.cfg.gs_out)
|
||||
|
||||
def forward_gs(self, x, p):
|
||||
if self.cfg.mlp_network_config is not None:
|
||||
x = self.mlp_net(x)
|
||||
return self.gs_net(x, p)
|
||||
|
||||
def forward_single_view(self,
|
||||
gs: GaussianModel,
|
||||
viewpoint_camera: Camera,
|
||||
background_color: Optional[Float[Tensor, "3"]],
|
||||
ret_mask: bool = True,
|
||||
):
|
||||
# Create zero tensor. We will use it to make pytorch return gradients of the 2D (screen-space) means
|
||||
screenspace_points = torch.zeros_like(gs.xyz, dtype=gs.xyz.dtype, requires_grad=True, device=self.device) + 0
|
||||
try:
|
||||
screenspace_points.retain_grad()
|
||||
except:
|
||||
pass
|
||||
|
||||
bg_color = background_color
|
||||
# Set up rasterization configuration
|
||||
tanfovx = math.tan(viewpoint_camera.FoVx * 0.5)
|
||||
tanfovy = math.tan(viewpoint_camera.FoVy * 0.5)
|
||||
|
||||
raster_settings = GaussianRasterizationSettings(
|
||||
image_height=int(viewpoint_camera.height),
|
||||
image_width=int(viewpoint_camera.width),
|
||||
tanfovx=tanfovx,
|
||||
tanfovy=tanfovy,
|
||||
bg=bg_color,
|
||||
scale_modifier=self.cfg.scaling_modifier,
|
||||
viewmatrix=viewpoint_camera.world_view_transform,
|
||||
projmatrix=viewpoint_camera.full_proj_transform.float(),
|
||||
sh_degree=self.cfg.sh_degree,
|
||||
campos=viewpoint_camera.camera_center,
|
||||
prefiltered=False,
|
||||
debug=False
|
||||
)
|
||||
|
||||
rasterizer = GaussianRasterizer(raster_settings=raster_settings)
|
||||
|
||||
means3D = gs.xyz
|
||||
means2D = screenspace_points
|
||||
opacity = gs.opacity
|
||||
|
||||
# If precomputed 3d covariance is provided, use it. If not, then it will be computed from
|
||||
# scaling / rotation by the rasterizer.
|
||||
scales = None
|
||||
rotations = None
|
||||
cov3D_precomp = None
|
||||
scales = gs.scaling
|
||||
rotations = gs.rotation
|
||||
|
||||
# If precomputed colors are provided, use them. Otherwise, if it is desired to precompute colors
|
||||
# from SHs in Python, do it. If not, then SH -> RGB conversion will be done by rasterizer.
|
||||
shs = None
|
||||
colors_precomp = None
|
||||
if self.gs_net.cfg.use_rgb:
|
||||
colors_precomp = gs.shs.squeeze(1)
|
||||
else:
|
||||
shs = gs.shs
|
||||
|
||||
# Rasterize visible Gaussians to image, obtain their radii (on screen).
|
||||
with torch.autocast(device_type=self.device.type, dtype=torch.float32):
|
||||
rendered_image, radii, rendered_depth, rendered_alpha = rasterizer(
|
||||
means3D = means3D,
|
||||
means2D = means2D,
|
||||
shs = shs,
|
||||
colors_precomp = colors_precomp,
|
||||
opacities = opacity,
|
||||
scales = scales,
|
||||
rotations = rotations,
|
||||
cov3D_precomp = cov3D_precomp)
|
||||
|
||||
ret = {
|
||||
"comp_rgb": rendered_image.permute(1, 2, 0),
|
||||
"comp_rgb_bg": bg_color
|
||||
}
|
||||
|
||||
if ret_mask:
|
||||
mask_bg_color = torch.zeros(3, dtype=torch.float32, device=self.device)
|
||||
raster_settings = GaussianRasterizationSettings(
|
||||
image_height=int(viewpoint_camera.height),
|
||||
image_width=int(viewpoint_camera.width),
|
||||
tanfovx=tanfovx,
|
||||
tanfovy=tanfovy,
|
||||
bg=mask_bg_color,
|
||||
scale_modifier=self.cfg.scaling_modifier,
|
||||
viewmatrix=viewpoint_camera.world_view_transform,
|
||||
projmatrix=viewpoint_camera.full_proj_transform.float(),
|
||||
sh_degree=0,
|
||||
campos=viewpoint_camera.camera_center,
|
||||
prefiltered=False,
|
||||
debug=False
|
||||
)
|
||||
rasterizer = GaussianRasterizer(raster_settings=raster_settings)
|
||||
|
||||
with torch.autocast(device_type=self.device.type, dtype=torch.float32):
|
||||
rendered_mask, radii, rendered_depth, rendered_alpha = rasterizer(
|
||||
means3D = means3D,
|
||||
means2D = means2D,
|
||||
# shs = ,
|
||||
colors_precomp = torch.ones_like(means3D),
|
||||
opacities = opacity,
|
||||
scales = scales,
|
||||
rotations = rotations,
|
||||
cov3D_precomp = cov3D_precomp)
|
||||
ret["comp_mask"] = rendered_mask.permute(1, 2, 0)
|
||||
|
||||
return ret
|
||||
|
||||
def query_triplane(
|
||||
self,
|
||||
positions: Float[Tensor, "*B N 3"],
|
||||
triplanes: Float[Tensor, "*B 3 Cp Hp Wp"],
|
||||
) -> Dict[str, Tensor]:
|
||||
batched = positions.ndim == 3
|
||||
if not batched:
|
||||
# no batch dimension
|
||||
triplanes = triplanes[None, ...]
|
||||
positions = positions[None, ...]
|
||||
|
||||
positions = scale_tensor(positions, (-self.cfg.radius, self.cfg.radius), (-1, 1))
|
||||
indices2D: Float[Tensor, "B 3 N 2"] = torch.stack(
|
||||
(positions[..., [0, 1]], positions[..., [0, 2]], positions[..., [1, 2]]),
|
||||
dim=-3,
|
||||
)
|
||||
out: Float[Tensor, "B3 Cp 1 N"] = F.grid_sample(
|
||||
rearrange(triplanes, "B Np Cp Hp Wp -> (B Np) Cp Hp Wp", Np=3),
|
||||
rearrange(indices2D, "B Np N Nd -> (B Np) () N Nd", Np=3),
|
||||
align_corners=False,
|
||||
mode="bilinear",
|
||||
)
|
||||
if self.cfg.feature_reduction == "concat":
|
||||
out = rearrange(out, "(B Np) Cp () N -> B N (Np Cp)", Np=3)
|
||||
elif self.cfg.feature_reduction == "mean":
|
||||
out = reduce(out, "(B Np) Cp () N -> B N Cp", Np=3, reduction="mean")
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
if not batched:
|
||||
out = out.squeeze(0)
|
||||
|
||||
return out
|
||||
|
||||
def forward_single_batch(
|
||||
self,
|
||||
gs_hidden_features: Float[Tensor, "Np Cp"],
|
||||
query_points: Float[Tensor, "Np 3"],
|
||||
c2ws: Float[Tensor, "Nv 4 4"],
|
||||
intrinsics: Float[Tensor, "Nv 4 4"],
|
||||
height: int,
|
||||
width: int,
|
||||
background_color: Optional[Float[Tensor, "3"]],
|
||||
):
|
||||
gs: GaussianModel = self.forward_gs(gs_hidden_features, query_points)
|
||||
out_list = []
|
||||
|
||||
for c2w, intrinsic in zip(c2ws, intrinsics):
|
||||
out_list.append(self.forward_single_view(
|
||||
gs,
|
||||
Camera.from_c2w(c2w, intrinsic, height, width),
|
||||
background_color
|
||||
))
|
||||
|
||||
out = defaultdict(list)
|
||||
for out_ in out_list:
|
||||
for k, v in out_.items():
|
||||
out[k].append(v)
|
||||
out = {k: torch.stack(v, dim=0) for k, v in out.items()}
|
||||
out["3dgs"] = gs
|
||||
|
||||
return out
|
||||
|
||||
def forward(self,
|
||||
gs_hidden_features: Float[Tensor, "B Np Cp"],
|
||||
query_points: Float[Tensor, "B Np 3"],
|
||||
c2w: Float[Tensor, "B Nv 4 4"],
|
||||
intrinsic: Float[Tensor, "B Nv 4 4"],
|
||||
height,
|
||||
width,
|
||||
additional_features: Optional[Float[Tensor, "B C H W"]] = None,
|
||||
background_color: Optional[Float[Tensor, "B 3"]] = None,
|
||||
**kwargs):
|
||||
batch_size = gs_hidden_features.shape[0]
|
||||
out_list = []
|
||||
gs_hidden_features = self.query_triplane(query_points, gs_hidden_features)
|
||||
if additional_features is not None:
|
||||
gs_hidden_features = torch.cat([gs_hidden_features, additional_features], dim=-1)
|
||||
|
||||
for b in range(batch_size):
|
||||
out_list.append(self.forward_single_batch(
|
||||
gs_hidden_features[b],
|
||||
query_points[b],
|
||||
c2w[b],
|
||||
intrinsic[b],
|
||||
height, width,
|
||||
background_color[b] if background_color is not None else None))
|
||||
|
||||
out = defaultdict(list)
|
||||
for out_ in out_list:
|
||||
for k, v in out_.items():
|
||||
out[k].append(v)
|
||||
for k, v in out.items():
|
||||
if isinstance(v[0], torch.Tensor):
|
||||
out[k] = torch.stack(v, dim=0)
|
||||
else:
|
||||
out[k] = v
|
||||
return out
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 AllenXiang
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,68 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Author: Peng Xiang
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from .utils import MLP_Res, MLP_CONV
|
||||
from .skip_transformer import SkipTransformer
|
||||
|
||||
|
||||
class SPD(nn.Module):
|
||||
def __init__(self, dim_feat=512, up_factor=2, i=0, radius=1, bounding=True, global_feat=True):
|
||||
"""Snowflake Point Deconvolution"""
|
||||
super(SPD, self).__init__()
|
||||
self.i = i
|
||||
self.up_factor = up_factor
|
||||
|
||||
self.bounding = bounding
|
||||
self.radius = radius
|
||||
|
||||
self.global_feat = global_feat
|
||||
self.ps_dim = 32 if global_feat else 64
|
||||
|
||||
self.mlp_1 = MLP_CONV(in_channel=3, layer_dims=[64, 128])
|
||||
self.mlp_2 = MLP_CONV(in_channel=128 * 2 + dim_feat if self.global_feat else 128, layer_dims=[256, 128])
|
||||
|
||||
self.skip_transformer = SkipTransformer(in_channel=128, dim=64)
|
||||
|
||||
self.mlp_ps = MLP_CONV(in_channel=128, layer_dims=[64, self.ps_dim])
|
||||
self.ps = nn.ConvTranspose1d(self.ps_dim, 128, up_factor, up_factor, bias=False) # point-wise splitting
|
||||
|
||||
self.up_sampler = nn.Upsample(scale_factor=up_factor)
|
||||
self.mlp_delta_feature = MLP_Res(in_dim=256, hidden_dim=128, out_dim=128)
|
||||
|
||||
self.mlp_delta = MLP_CONV(in_channel=128, layer_dims=[64, 3])
|
||||
|
||||
def forward(self, pcd_prev, feat_global=None, K_prev=None):
|
||||
"""
|
||||
Args:
|
||||
pcd_prev: Tensor, (B, 3, N_prev)
|
||||
feat_global: Tensor, (B, dim_feat, 1)
|
||||
K_prev: Tensor, (B, 128, N_prev)
|
||||
|
||||
Returns:
|
||||
pcd_child: Tensor, up sampled point cloud, (B, 3, N_prev * up_factor)
|
||||
K_curr: Tensor, displacement feature of current step, (B, 128, N_prev * up_factor)
|
||||
"""
|
||||
b, _, n_prev = pcd_prev.shape
|
||||
feat_1 = self.mlp_1(pcd_prev)
|
||||
feat_1 = torch.cat([feat_1,
|
||||
torch.max(feat_1, 2, keepdim=True)[0].repeat((1, 1, feat_1.size(2))),
|
||||
feat_global.repeat(1, 1, feat_1.size(2))], 1) if self.global_feat else feat_1
|
||||
Q = self.mlp_2(feat_1)
|
||||
|
||||
H = self.skip_transformer(pcd_prev, K_prev if K_prev is not None else Q, Q)
|
||||
|
||||
feat_child = self.mlp_ps(H)
|
||||
feat_child = self.ps(feat_child) # (B, 128, N_prev * up_factor)
|
||||
H_up = self.up_sampler(H)
|
||||
K_curr = self.mlp_delta_feature(torch.cat([feat_child, H_up], 1))
|
||||
|
||||
delta = self.mlp_delta(torch.relu(K_curr))
|
||||
if self.bounding:
|
||||
delta = torch.tanh(delta) / self.radius**self.i # (B, 3, N_prev * up_factor)
|
||||
|
||||
pcd_child = self.up_sampler(pcd_prev)
|
||||
pcd_child = pcd_child + delta
|
||||
|
||||
return pcd_child, K_curr
|
||||
@@ -0,0 +1,81 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Author: Peng Xiang
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from .utils import MLP_Res, MLP_CONV
|
||||
from .skip_transformer import SkipTransformer
|
||||
from .attention import ResidualTransformerBlock
|
||||
|
||||
class SPD_crossattn(nn.Module):
|
||||
def __init__(self, dim_feat=512, up_factor=2, i=0, radius=1, bounding=True, global_feat=True):
|
||||
"""Snowflake Point Deconvolution"""
|
||||
super().__init__()
|
||||
self.i = i
|
||||
self.up_factor = up_factor
|
||||
|
||||
self.bounding = bounding
|
||||
self.radius = radius
|
||||
|
||||
self.global_feat = global_feat
|
||||
self.ps_dim = 32 if global_feat else 64
|
||||
|
||||
self.mlp_1 = MLP_CONV(in_channel=3, layer_dims=[64, 128])
|
||||
self.pcd_image_attn = ResidualTransformerBlock(
|
||||
device=torch.device('cuda'),
|
||||
dtype=torch.float32,
|
||||
n_data=128,
|
||||
width=128,
|
||||
heads=8,
|
||||
init_scale=1.0,
|
||||
)
|
||||
|
||||
self.mlp_2 = MLP_CONV(in_channel=128 * 2 + dim_feat if self.global_feat else 128, layer_dims=[256, 128])
|
||||
|
||||
self.skip_transformer = SkipTransformer(in_channel=128, dim=64)
|
||||
|
||||
self.mlp_ps = MLP_CONV(in_channel=128, layer_dims=[64, self.ps_dim])
|
||||
self.ps = nn.ConvTranspose1d(self.ps_dim, 128, up_factor, up_factor, bias=False) # point-wise splitting
|
||||
|
||||
self.up_sampler = nn.Upsample(scale_factor=up_factor)
|
||||
self.mlp_delta_feature = MLP_Res(in_dim=256, hidden_dim=128, out_dim=128)
|
||||
|
||||
self.mlp_delta = MLP_CONV(in_channel=128, layer_dims=[64, 3])
|
||||
|
||||
def forward(self, pcd_prev, feat_global=None, K_prev=None):
|
||||
"""
|
||||
Args:
|
||||
pcd_prev: Tensor, (B, 3, N_prev)
|
||||
feat_global: Tensor, (B, dim_feat, 1)
|
||||
K_prev: Tensor, (B, 128, N_prev)
|
||||
|
||||
Returns:
|
||||
pcd_child: Tensor, up sampled point cloud, (B, 3, N_prev * up_factor)
|
||||
K_curr: Tensor, displacement feature of current step, (B, 128, N_prev * up_factor)
|
||||
"""
|
||||
b, _, n_prev = pcd_prev.shape
|
||||
feat_1 = self.mlp_1(pcd_prev)
|
||||
# feat_1 = torch.cat([feat_1,
|
||||
# torch.max(feat_1, 2, keepdim=True)[0].repeat((1, 1, feat_1.size(2))),
|
||||
# feat_global.repeat(1, 1, feat_1.size(2))], 1) if self.global_feat else feat_1
|
||||
feat_1 = torch.permute(feat_1, (0, 2, 1))
|
||||
feat_global = torch.permute(feat_global, (0, 2, 1))
|
||||
feat_1 = self.pcd_image_attn(feat_1, feat_global)
|
||||
Q = torch.permute(feat_1, (0, 2, 1))
|
||||
# Q = self.mlp_2(feat_1)
|
||||
|
||||
H = self.skip_transformer(pcd_prev, K_prev if K_prev is not None else Q, Q)
|
||||
|
||||
feat_child = self.mlp_ps(H)
|
||||
feat_child = self.ps(feat_child) # (B, 128, N_prev * up_factor)
|
||||
H_up = self.up_sampler(H)
|
||||
K_curr = self.mlp_delta_feature(torch.cat([feat_child, H_up], 1))
|
||||
|
||||
delta = self.mlp_delta(torch.relu(K_curr))
|
||||
if self.bounding:
|
||||
delta = torch.tanh(delta) / self.radius**self.i # (B, 3, N_prev * up_factor)
|
||||
|
||||
pcd_child = self.up_sampler(pcd_prev)
|
||||
pcd_child = pcd_child + delta
|
||||
|
||||
return pcd_child, K_curr
|
||||
@@ -0,0 +1,71 @@
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from .utils import MLP_Res, MLP_CONV
|
||||
from .skip_transformer import SkipTransformer
|
||||
|
||||
class SPD_pp(nn.Module):
|
||||
def __init__(self, dim_feat=512, up_factor=2, i=0, radius=1, bounding=True, global_feat=True):
|
||||
"""Snowflake Point Deconvolution"""
|
||||
super(SPD_pp, self).__init__()
|
||||
self.i = i
|
||||
self.up_factor = up_factor
|
||||
|
||||
self.bounding = bounding
|
||||
self.radius = radius
|
||||
|
||||
self.global_feat = global_feat
|
||||
self.ps_dim = 32 if global_feat else 64
|
||||
|
||||
self.mlp_1 = MLP_CONV(in_channel=3, layer_dims=[64, 128])
|
||||
self.mlp_2 = MLP_CONV(
|
||||
in_channel=128 * 2 + dim_feat if self.global_feat else 128, layer_dims=[256, 128])
|
||||
|
||||
self.skip_transformer = SkipTransformer(in_channel=128, dim=64)
|
||||
|
||||
self.mlp_ps = MLP_CONV(in_channel=128, layer_dims=[64, self.ps_dim])
|
||||
self.ps = nn.ConvTranspose1d(
|
||||
self.ps_dim, 128, up_factor, up_factor, bias=False) # point-wise splitting
|
||||
|
||||
self.up_sampler = nn.Upsample(scale_factor=up_factor)
|
||||
self.mlp_delta_feature = MLP_Res(
|
||||
in_dim=256, hidden_dim=128, out_dim=128)
|
||||
|
||||
self.mlp_delta = MLP_CONV(in_channel=128, layer_dims=[64, 3])
|
||||
|
||||
def forward(self, pcd_prev, feat_cond=None, K_prev=None):
|
||||
"""
|
||||
Args:
|
||||
pcd_prev: Tensor, (B, 3, N_prev)
|
||||
feat_cond: Tensor, (B, dim_feat, N_prev)
|
||||
K_prev: Tensor, (B, 128, N_prev)
|
||||
|
||||
Returns:
|
||||
pcd_child: Tensor, up sampled point cloud, (B, 3, N_prev * up_factor)
|
||||
K_curr: Tensor, displacement feature of current step, (B, 128, N_prev * up_factor)
|
||||
"""
|
||||
b, _, n_prev = pcd_prev.shape
|
||||
feat_1 = self.mlp_1(pcd_prev)
|
||||
feat_1 = torch.cat([feat_1,
|
||||
torch.max(feat_1, 2, keepdim=True)[
|
||||
0].repeat((1, 1, feat_1.size(2))),
|
||||
feat_cond], 1) if self.global_feat else feat_1
|
||||
Q = self.mlp_2(feat_1)
|
||||
|
||||
H = self.skip_transformer(
|
||||
pcd_prev, K_prev if K_prev is not None else Q, Q)
|
||||
|
||||
feat_child = self.mlp_ps(H)
|
||||
feat_child = self.ps(feat_child) # (B, 128, N_prev * up_factor)
|
||||
H_up = self.up_sampler(H)
|
||||
K_curr = self.mlp_delta_feature(torch.cat([feat_child, H_up], 1))
|
||||
|
||||
delta = self.mlp_delta(torch.relu(K_curr))
|
||||
if self.bounding:
|
||||
# (B, 3, N_prev * up_factor)
|
||||
delta = torch.tanh(delta) / self.radius**self.i
|
||||
|
||||
pcd_child = self.up_sampler(pcd_prev)
|
||||
pcd_child = pcd_child + delta
|
||||
|
||||
return pcd_child, K_curr
|
||||
@@ -0,0 +1,239 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import math
|
||||
import math
|
||||
from typing import Optional
|
||||
from typing import Callable, Iterable, Sequence, Union
|
||||
|
||||
import torch
|
||||
|
||||
def checkpoint(
|
||||
func: Callable[..., Union[torch.Tensor, Sequence[torch.Tensor]]],
|
||||
inputs: Sequence[torch.Tensor],
|
||||
params: Iterable[torch.Tensor],
|
||||
flag: bool,
|
||||
):
|
||||
"""
|
||||
Evaluate a function without caching intermediate activations, allowing for
|
||||
reduced memory at the expense of extra compute in the backward pass.
|
||||
:param func: the function to evaluate.
|
||||
:param inputs: the argument sequence to pass to `func`.
|
||||
:param params: a sequence of parameters `func` depends on but does not
|
||||
explicitly take as arguments.
|
||||
:param flag: if False, disable gradient checkpointing.
|
||||
"""
|
||||
if flag:
|
||||
args = tuple(inputs) + tuple(params)
|
||||
return CheckpointFunction.apply(func, len(inputs), *args)
|
||||
else:
|
||||
return func(*inputs)
|
||||
|
||||
|
||||
class CheckpointFunction(torch.autograd.Function):
|
||||
@staticmethod
|
||||
def forward(ctx, run_function, length, *args):
|
||||
ctx.run_function = run_function
|
||||
ctx.input_tensors = list(args[:length])
|
||||
ctx.input_params = list(args[length:])
|
||||
with torch.no_grad():
|
||||
output_tensors = ctx.run_function(*ctx.input_tensors)
|
||||
return output_tensors
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, *output_grads):
|
||||
ctx.input_tensors = [x.detach().requires_grad_(True)
|
||||
for x in ctx.input_tensors]
|
||||
with torch.enable_grad():
|
||||
# Fixes a bug where the first op in run_function modifies the
|
||||
# Tensor storage in place, which is not allowed for detach()'d
|
||||
# Tensors.
|
||||
shallow_copies = [x.view_as(x) for x in ctx.input_tensors]
|
||||
output_tensors = ctx.run_function(*shallow_copies)
|
||||
input_grads = torch.autograd.grad(
|
||||
output_tensors,
|
||||
ctx.input_tensors + ctx.input_params,
|
||||
output_grads,
|
||||
allow_unused=True,
|
||||
)
|
||||
del ctx.input_tensors
|
||||
del ctx.input_params
|
||||
del output_tensors
|
||||
return (None, None) + input_grads
|
||||
|
||||
|
||||
def init_linear(l, stddev):
|
||||
nn.init.normal_(l.weight, std=stddev)
|
||||
if l.bias is not None:
|
||||
nn.init.constant_(l.bias, 0.0)
|
||||
|
||||
class MLP(nn.Module):
|
||||
def __init__(self, *, device: torch.device, dtype: torch.dtype, width: int, init_scale: float):
|
||||
super().__init__()
|
||||
self.width = width
|
||||
self.c_fc = nn.Linear(width, width * 4, device=device, dtype=dtype)
|
||||
self.c_proj = nn.Linear(width * 4, width, device=device, dtype=dtype)
|
||||
self.gelu = nn.GELU()
|
||||
init_linear(self.c_fc, init_scale)
|
||||
init_linear(self.c_proj, init_scale)
|
||||
|
||||
def forward(self, x):
|
||||
return self.c_proj(self.gelu(self.c_fc(x)))
|
||||
|
||||
class QKVMultiheadCrossAttention(nn.Module):
|
||||
def __init__(self, *, device: torch.device, dtype: torch.dtype, heads: int, n_data: int):
|
||||
super().__init__()
|
||||
self.device = device
|
||||
self.dtype = dtype
|
||||
self.heads = heads
|
||||
self.n_data = n_data
|
||||
|
||||
def forward(self, q, kv):
|
||||
_, n_ctx, _ = q.shape
|
||||
bs, n_data, width = kv.shape
|
||||
attn_ch = width // self.heads // 2
|
||||
scale = 1 / math.sqrt(math.sqrt(attn_ch))
|
||||
q = q.view(bs, n_ctx, self.heads, -1)
|
||||
kv = kv.view(bs, n_data, self.heads, -1)
|
||||
k, v = torch.split(kv, attn_ch, dim=-1)
|
||||
weight = torch.einsum(
|
||||
"bthc,bshc->bhts", q * scale, k * scale
|
||||
) # More stable with f16 than dividing afterwards
|
||||
wdtype = weight.dtype
|
||||
weight = torch.softmax(weight.float(), dim=-1).type(wdtype)
|
||||
return torch.einsum("bhts,bshc->bthc", weight, v).reshape(bs, n_ctx, -1)
|
||||
|
||||
|
||||
|
||||
class QKVMultiheadAttention(nn.Module):
|
||||
def __init__(self, *, device: torch.device, dtype: torch.dtype, heads: int, n_ctx: int):
|
||||
super().__init__()
|
||||
self.device = device
|
||||
self.dtype = dtype
|
||||
self.heads = heads
|
||||
self.n_ctx = n_ctx
|
||||
|
||||
def forward(self, qkv):
|
||||
bs, n_ctx, width = qkv.shape
|
||||
attn_ch = width // self.heads // 3
|
||||
scale = 1 / math.sqrt(math.sqrt(attn_ch))
|
||||
qkv = qkv.view(bs, n_ctx, self.heads, -1)
|
||||
q, k, v = torch.split(qkv, attn_ch, dim=-1)
|
||||
weight = torch.einsum(
|
||||
"bthc,bshc->bhts", q * scale, k * scale
|
||||
) # More stable with f16 than dividing afterwards
|
||||
wdtype = weight.dtype
|
||||
weight = torch.softmax(weight.float(), dim=-1).type(wdtype)
|
||||
return torch.einsum("bhts,bshc->bthc", weight, v).reshape(bs, n_ctx, -1)
|
||||
|
||||
|
||||
|
||||
class MultiheadCrossAttention(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
device: torch.device,
|
||||
dtype: torch.dtype,
|
||||
n_data: int,
|
||||
width: int,
|
||||
heads: int,
|
||||
init_scale: float,
|
||||
data_width: Optional[int] = None,
|
||||
):
|
||||
super().__init__()
|
||||
self.n_data = n_data
|
||||
self.width = width
|
||||
self.heads = heads
|
||||
self.data_width = width if data_width is None else data_width
|
||||
self.c_q = nn.Linear(width, width, device=device, dtype=dtype)
|
||||
self.c_kv = nn.Linear(self.data_width, width * 2,
|
||||
device=device, dtype=dtype)
|
||||
self.c_proj = nn.Linear(width, width, device=device, dtype=dtype)
|
||||
self.attention = QKVMultiheadCrossAttention(
|
||||
device=device, dtype=dtype, heads=heads, n_data=n_data
|
||||
)
|
||||
init_linear(self.c_q, init_scale)
|
||||
init_linear(self.c_kv, init_scale)
|
||||
init_linear(self.c_proj, init_scale)
|
||||
|
||||
def forward(self, x, data):
|
||||
x = self.c_q(x)
|
||||
data = self.c_kv(data)
|
||||
x = checkpoint(self.attention, (x, data), (), True)
|
||||
x = self.c_proj(x)
|
||||
return x
|
||||
|
||||
|
||||
class MultiheadAttention(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
device: torch.device,
|
||||
dtype: torch.dtype,
|
||||
n_ctx: int,
|
||||
width: int,
|
||||
heads: int,
|
||||
init_scale: float,
|
||||
):
|
||||
super().__init__()
|
||||
self.n_ctx = n_ctx
|
||||
self.width = width
|
||||
self.heads = heads
|
||||
self.c_qkv = nn.Linear(width, width * 3, device=device, dtype=dtype)
|
||||
self.c_proj = nn.Linear(width, width, device=device, dtype=dtype)
|
||||
self.attention = QKVMultiheadAttention(device=device, dtype=dtype, heads=heads, n_ctx=n_ctx)
|
||||
init_linear(self.c_qkv, init_scale)
|
||||
init_linear(self.c_proj, init_scale)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.c_qkv(x)
|
||||
x = checkpoint(self.attention, (x,), (), True)
|
||||
x = self.c_proj(x)
|
||||
return x
|
||||
|
||||
|
||||
class ResidualTransformerBlock(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
device: torch.device,
|
||||
dtype: torch.dtype,
|
||||
n_data: int,
|
||||
width: int,
|
||||
heads: int,
|
||||
data_width: Optional[int] = None,
|
||||
init_scale: float = 1.0,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
if data_width is None:
|
||||
data_width = width
|
||||
|
||||
self.attn_cross = MultiheadCrossAttention(
|
||||
device=device,
|
||||
dtype=dtype,
|
||||
n_data=n_data,
|
||||
width=width,
|
||||
heads=heads,
|
||||
data_width=data_width,
|
||||
init_scale=init_scale,
|
||||
)
|
||||
self.attn_self = MultiheadAttention(
|
||||
device=device,
|
||||
dtype=dtype,
|
||||
n_ctx=n_data,
|
||||
width=width,
|
||||
heads=heads,
|
||||
init_scale=init_scale,
|
||||
)
|
||||
self.ln_1 = nn.LayerNorm(width, device=device, dtype=dtype)
|
||||
self.ln_2 = nn.LayerNorm(data_width, device=device, dtype=dtype)
|
||||
self.ln_3 = nn.LayerNorm(width, device=device, dtype=dtype)
|
||||
self.mlp = MLP(device=device, dtype=dtype,
|
||||
width=width, init_scale=init_scale)
|
||||
self.ln_4 = nn.LayerNorm(width, device=device, dtype=dtype)
|
||||
|
||||
def forward(self, x: torch.Tensor, data: torch.Tensor):
|
||||
x = x + self.attn_cross(self.ln_1(x), self.ln_2(data))
|
||||
x = x + self.attn_self(self.ln_3(x))
|
||||
x = x + self.mlp(self.ln_4(x))
|
||||
return x
|
||||
@@ -0,0 +1,188 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from tgs.utils.base import BaseModule
|
||||
from tgs.utils.typing import *
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from pytorch3d.renderer import (
|
||||
AlphaCompositor,
|
||||
NormWeightedCompositor,
|
||||
PointsRasterizationSettings,
|
||||
PointsRasterizer,
|
||||
PointsRenderer)
|
||||
from pytorch3d.renderer.cameras import CamerasBase
|
||||
from pytorch3d.structures import Pointclouds
|
||||
from pytorch3d.utils.camera_conversions import cameras_from_opencv_projection
|
||||
|
||||
from .utils import fps_subsample
|
||||
from einops import rearrange
|
||||
|
||||
from .utils import MLP_CONV
|
||||
from .SPD import SPD
|
||||
from .SPD_crossattn import SPD_crossattn
|
||||
from .SPD_pp import SPD_pp
|
||||
|
||||
SPD_BLOCK = {
|
||||
'SPD': SPD,
|
||||
'SPD_crossattn': SPD_crossattn,
|
||||
'SPD_PP': SPD_pp,
|
||||
}
|
||||
|
||||
|
||||
def points_projection(points: Float[Tensor, "B Np 3"],
|
||||
c2ws: Float[Tensor, "B 4 4"],
|
||||
intrinsics: Float[Tensor, "B 3 3"],
|
||||
local_features: Float[Tensor, "B C H W"],
|
||||
raster_point_radius: float = 0.0075, # point size
|
||||
raster_points_per_pixel: int = 1, # a single point per pixel, for now
|
||||
bin_size: int = 0):
|
||||
"""
|
||||
points: (B, Np, 3)
|
||||
"""
|
||||
|
||||
B, C, H, W = local_features.shape
|
||||
device = local_features.device
|
||||
raster_settings = PointsRasterizationSettings(
|
||||
image_size=(H, W),
|
||||
radius=raster_point_radius,
|
||||
points_per_pixel=raster_points_per_pixel,
|
||||
bin_size=bin_size,
|
||||
)
|
||||
Np = points.shape[1]
|
||||
c2ws = c2ws.transpose(0, 1).flatten(0, 1)
|
||||
intrinsics = intrinsics.transpose(0, 1).flatten(0, 1)
|
||||
R = raster_settings.points_per_pixel
|
||||
w2cs = torch.inverse(c2ws)
|
||||
image_size = torch.as_tensor([H, W]).view(
|
||||
1, 2).expand(w2cs.shape[0], -1).to(device)
|
||||
cameras = cameras_from_opencv_projection(
|
||||
w2cs[:, :3, :3], w2cs[:, :3, 3], intrinsics, image_size)
|
||||
rasterize = PointsRasterizer(
|
||||
cameras=cameras, raster_settings=raster_settings)
|
||||
fragments = rasterize(Pointclouds(points))
|
||||
fragments_idx: Tensor = fragments.idx.long()
|
||||
visible_pixels = (fragments_idx > -1) # (B, H, W, R)
|
||||
points_to_visible_pixels = fragments_idx[visible_pixels]
|
||||
# Reshape local features to (B, H, W, R, C)
|
||||
local_features = local_features.permute(
|
||||
0, 2, 3, 1).unsqueeze(-2).expand(-1, -1, -1, R, -1) # (B, H, W, R, C)
|
||||
# Get local features corresponding to visible points
|
||||
local_features_proj = torch.zeros(B * Np, C, device=device)
|
||||
local_features_proj[points_to_visible_pixels] = local_features[visible_pixels]
|
||||
local_features_proj = local_features_proj.reshape(B, Np, C)
|
||||
return local_features_proj
|
||||
|
||||
|
||||
class Decoder(nn.Module):
|
||||
def __init__(self, input_channels=1152, dim_feat=512, num_p0=512,
|
||||
radius=1, bounding=True, up_factors=None,
|
||||
SPD_type='SPD',
|
||||
token_type='image_token'
|
||||
):
|
||||
super(Decoder, self).__init__()
|
||||
# self.decoder_coarse = SeedGenerator(dim_feat=dim_feat, num_pc=num_p0)
|
||||
if up_factors is None:
|
||||
up_factors = [1]
|
||||
else:
|
||||
up_factors = up_factors
|
||||
uppers = []
|
||||
self.num_p0 = num_p0
|
||||
self.mlp_feat_cond = MLP_CONV(in_channel=input_channels,
|
||||
layer_dims=[dim_feat*2, dim_feat])
|
||||
|
||||
for i, factor in enumerate(up_factors):
|
||||
uppers.append(
|
||||
SPD_BLOCK[SPD_type](dim_feat=dim_feat, up_factor=factor,
|
||||
i=i, bounding=bounding, radius=radius))
|
||||
self.uppers = nn.ModuleList(uppers)
|
||||
self.token_type = token_type
|
||||
|
||||
def calculate_pcl_token(self, pcl_token, up_factor):
|
||||
up_token = F.interpolate(pcl_token, scale_factor=up_factor, mode='nearest')
|
||||
return up_token
|
||||
|
||||
def calculate_image_token(self, pcd, input_image_tokens, batch):
|
||||
"""
|
||||
Args:
|
||||
"""
|
||||
batch_size, n_input_views = batch["rgb_cond"].shape[:2]
|
||||
h_cond, w_cond = batch["rgb_cond"].shape[2:4]
|
||||
input_image_tokens = rearrange(
|
||||
input_image_tokens, '(B Nv) C Nt -> B (Nv Nt) C', Nv=n_input_views)
|
||||
local_features = input_image_tokens[:, 1:].reshape(
|
||||
batch_size, h_cond // 14, w_cond // 14, -1).permute(0, 3, 1, 2)
|
||||
local_features = F.interpolate(local_features, size=(
|
||||
h_cond, w_cond), mode='bilinear', align_corners=False)
|
||||
batch['c2w_cond'][..., :3, 1:3] *= -1
|
||||
local_features_proj = points_projection(
|
||||
pcd,
|
||||
batch['c2w_cond'],
|
||||
batch['intrinsic_cond'],
|
||||
local_features,
|
||||
)
|
||||
local_features_proj = local_features_proj.permute(0, 2, 1).contiguous()
|
||||
return local_features_proj
|
||||
|
||||
def forward(self, x):
|
||||
"""
|
||||
Args:
|
||||
points: Tensor, (b, num_p0, 3)
|
||||
feat_cond: Tensor, (b, dim_feat) dinov2: 325x768
|
||||
# partial_coarse: Tensor, (b, n_coarse, 3)
|
||||
"""
|
||||
points = x['points']
|
||||
if self.token_type == 'pcl_token':
|
||||
feat_cond = x['pcl_token']
|
||||
elif self.token_type == 'image_token':
|
||||
feat_cond = x['input_image_tokens']
|
||||
feat_cond = self.mlp_feat_cond(feat_cond)
|
||||
arr_pcd = []
|
||||
feat_prev = None
|
||||
|
||||
pcd = torch.permute(points, (0, 2, 1)).contiguous()
|
||||
pcl_up_scale = 1
|
||||
for upper in self.uppers:
|
||||
if self.token_type == 'pcl_token':
|
||||
up_cond = self.calculate_pcl_token(
|
||||
feat_cond, pcl_up_scale)
|
||||
pcl_up_scale *= upper.up_factor
|
||||
elif self.token_type == 'image_token':
|
||||
up_cond = self.calculate_image_token(points, feat_cond, x)
|
||||
pcd, feat_prev = upper(pcd, up_cond, feat_prev)
|
||||
points = torch.permute(pcd, (0, 2, 1)).contiguous()
|
||||
arr_pcd.append(points)
|
||||
return arr_pcd
|
||||
|
||||
|
||||
class SnowflakeModelSPDPP(BaseModule):
|
||||
"""
|
||||
apply PC^2 / PCL token to decoder
|
||||
"""
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
input_channels: int = 1152
|
||||
dim_feat: int = 128
|
||||
num_p0: int = 512
|
||||
radius: float = 1
|
||||
bounding: bool = True
|
||||
use_fps: bool = True
|
||||
up_factors: List[int] = field(default_factory=lambda: [2, 2])
|
||||
image_full_token_cond: bool = False
|
||||
SPD_type: str = 'SPD_PP'
|
||||
token_type: str = 'pcl_token'
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
self.decoder = Decoder(input_channels=self.cfg.input_channels,
|
||||
dim_feat=self.cfg.dim_feat, num_p0=self.cfg.num_p0,
|
||||
radius=self.cfg.radius, up_factors=self.cfg.up_factors, bounding=self.cfg.bounding,
|
||||
SPD_type=self.cfg.SPD_type,
|
||||
token_type=self.cfg.token_type
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
results = self.decoder(x)
|
||||
return results
|
||||
@@ -0,0 +1,3 @@
|
||||
import pointnet2_ops.pointnet2_modules
|
||||
import pointnet2_ops.pointnet2_utils
|
||||
from pointnet2_ops._version import __version__
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
#include <torch/extension.h>
|
||||
|
||||
at::Tensor ball_query(at::Tensor new_xyz, at::Tensor xyz, const float radius,
|
||||
const int nsample);
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifndef _CUDA_UTILS_H
|
||||
#define _CUDA_UTILS_H
|
||||
|
||||
#include <ATen/ATen.h>
|
||||
#include <ATen/cuda/CUDAContext.h>
|
||||
#include <cmath>
|
||||
|
||||
#include <cuda.h>
|
||||
#include <cuda_runtime.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#define TOTAL_THREADS 512
|
||||
|
||||
inline int opt_n_threads(int work_size) {
|
||||
const int pow_2 = std::log(static_cast<double>(work_size)) / std::log(2.0);
|
||||
|
||||
return max(min(1 << pow_2, TOTAL_THREADS), 1);
|
||||
}
|
||||
|
||||
inline dim3 opt_block_config(int x, int y) {
|
||||
const int x_threads = opt_n_threads(x);
|
||||
const int y_threads =
|
||||
max(min(opt_n_threads(y), TOTAL_THREADS / x_threads), 1);
|
||||
dim3 block_config(x_threads, y_threads, 1);
|
||||
|
||||
return block_config;
|
||||
}
|
||||
|
||||
#define CUDA_CHECK_ERRORS() \
|
||||
do { \
|
||||
cudaError_t err = cudaGetLastError(); \
|
||||
if (cudaSuccess != err) { \
|
||||
fprintf(stderr, "CUDA kernel failed : %s\n%s at L:%d in %s\n", \
|
||||
cudaGetErrorString(err), __PRETTY_FUNCTION__, __LINE__, \
|
||||
__FILE__); \
|
||||
exit(-1); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
#include <torch/extension.h>
|
||||
|
||||
at::Tensor group_points(at::Tensor points, at::Tensor idx);
|
||||
at::Tensor group_points_grad(at::Tensor grad_out, at::Tensor idx, const int n);
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <torch/extension.h>
|
||||
#include <vector>
|
||||
|
||||
std::vector<at::Tensor> three_nn(at::Tensor unknowns, at::Tensor knows);
|
||||
at::Tensor three_interpolate(at::Tensor points, at::Tensor idx,
|
||||
at::Tensor weight);
|
||||
at::Tensor three_interpolate_grad(at::Tensor grad_out, at::Tensor idx,
|
||||
at::Tensor weight, const int m);
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
#include <torch/extension.h>
|
||||
|
||||
at::Tensor gather_points(at::Tensor points, at::Tensor idx);
|
||||
at::Tensor gather_points_grad(at::Tensor grad_out, at::Tensor idx, const int n);
|
||||
at::Tensor furthest_point_sampling(at::Tensor points, const int nsamples);
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include <ATen/cuda/CUDAContext.h>
|
||||
#include <torch/extension.h>
|
||||
|
||||
#define CHECK_CUDA(x) \
|
||||
do { \
|
||||
AT_ASSERT(x.is_cuda(), #x " must be a CUDA tensor"); \
|
||||
} while (0)
|
||||
|
||||
#define CHECK_CONTIGUOUS(x) \
|
||||
do { \
|
||||
AT_ASSERT(x.is_contiguous(), #x " must be a contiguous tensor"); \
|
||||
} while (0)
|
||||
|
||||
#define CHECK_IS_INT(x) \
|
||||
do { \
|
||||
AT_ASSERT(x.scalar_type() == at::ScalarType::Int, \
|
||||
#x " must be an int tensor"); \
|
||||
} while (0)
|
||||
|
||||
#define CHECK_IS_FLOAT(x) \
|
||||
do { \
|
||||
AT_ASSERT(x.scalar_type() == at::ScalarType::Float, \
|
||||
#x " must be a float tensor"); \
|
||||
} while (0)
|
||||
@@ -0,0 +1,32 @@
|
||||
#include "ball_query.h"
|
||||
#include "utils.h"
|
||||
|
||||
void query_ball_point_kernel_wrapper(int b, int n, int m, float radius,
|
||||
int nsample, const float *new_xyz,
|
||||
const float *xyz, int *idx);
|
||||
|
||||
at::Tensor ball_query(at::Tensor new_xyz, at::Tensor xyz, const float radius,
|
||||
const int nsample) {
|
||||
CHECK_CONTIGUOUS(new_xyz);
|
||||
CHECK_CONTIGUOUS(xyz);
|
||||
CHECK_IS_FLOAT(new_xyz);
|
||||
CHECK_IS_FLOAT(xyz);
|
||||
|
||||
if (new_xyz.is_cuda()) {
|
||||
CHECK_CUDA(xyz);
|
||||
}
|
||||
|
||||
at::Tensor idx =
|
||||
torch::zeros({new_xyz.size(0), new_xyz.size(1), nsample},
|
||||
at::device(new_xyz.device()).dtype(at::ScalarType::Int));
|
||||
|
||||
if (new_xyz.is_cuda()) {
|
||||
query_ball_point_kernel_wrapper(xyz.size(0), xyz.size(1), new_xyz.size(1),
|
||||
radius, nsample, new_xyz.data_ptr<float>(),
|
||||
xyz.data_ptr<float>(), idx.data_ptr<int>());
|
||||
} else {
|
||||
AT_ASSERT(false, "CPU not supported");
|
||||
}
|
||||
|
||||
return idx;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "cuda_utils.h"
|
||||
|
||||
// input: new_xyz(b, m, 3) xyz(b, n, 3)
|
||||
// output: idx(b, m, nsample)
|
||||
__global__ void query_ball_point_kernel(int b, int n, int m, float radius,
|
||||
int nsample,
|
||||
const float *__restrict__ new_xyz,
|
||||
const float *__restrict__ xyz,
|
||||
int *__restrict__ idx) {
|
||||
int batch_index = blockIdx.x;
|
||||
xyz += batch_index * n * 3;
|
||||
new_xyz += batch_index * m * 3;
|
||||
idx += m * nsample * batch_index;
|
||||
|
||||
int index = threadIdx.x;
|
||||
int stride = blockDim.x;
|
||||
|
||||
float radius2 = radius * radius;
|
||||
for (int j = index; j < m; j += stride) {
|
||||
float new_x = new_xyz[j * 3 + 0];
|
||||
float new_y = new_xyz[j * 3 + 1];
|
||||
float new_z = new_xyz[j * 3 + 2];
|
||||
for (int k = 0, cnt = 0; k < n && cnt < nsample; ++k) {
|
||||
float x = xyz[k * 3 + 0];
|
||||
float y = xyz[k * 3 + 1];
|
||||
float z = xyz[k * 3 + 2];
|
||||
float d2 = (new_x - x) * (new_x - x) + (new_y - y) * (new_y - y) +
|
||||
(new_z - z) * (new_z - z);
|
||||
if (d2 < radius2) {
|
||||
if (cnt == 0) {
|
||||
for (int l = 0; l < nsample; ++l) {
|
||||
idx[j * nsample + l] = k;
|
||||
}
|
||||
}
|
||||
idx[j * nsample + cnt] = k;
|
||||
++cnt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void query_ball_point_kernel_wrapper(int b, int n, int m, float radius,
|
||||
int nsample, const float *new_xyz,
|
||||
const float *xyz, int *idx) {
|
||||
cudaStream_t stream = at::cuda::getCurrentCUDAStream();
|
||||
query_ball_point_kernel<<<b, opt_n_threads(m), 0, stream>>>(
|
||||
b, n, m, radius, nsample, new_xyz, xyz, idx);
|
||||
|
||||
CUDA_CHECK_ERRORS();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "ball_query.h"
|
||||
#include "group_points.h"
|
||||
#include "interpolate.h"
|
||||
#include "sampling.h"
|
||||
|
||||
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
|
||||
m.def("gather_points", &gather_points);
|
||||
m.def("gather_points_grad", &gather_points_grad);
|
||||
m.def("furthest_point_sampling", &furthest_point_sampling);
|
||||
|
||||
m.def("three_nn", &three_nn);
|
||||
m.def("three_interpolate", &three_interpolate);
|
||||
m.def("three_interpolate_grad", &three_interpolate_grad);
|
||||
|
||||
m.def("ball_query", &ball_query);
|
||||
|
||||
m.def("group_points", &group_points);
|
||||
m.def("group_points_grad", &group_points_grad);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
#include "group_points.h"
|
||||
#include "utils.h"
|
||||
|
||||
void group_points_kernel_wrapper(int b, int c, int n, int npoints, int nsample,
|
||||
const float *points, const int *idx,
|
||||
float *out);
|
||||
|
||||
void group_points_grad_kernel_wrapper(int b, int c, int n, int npoints,
|
||||
int nsample, const float *grad_out,
|
||||
const int *idx, float *grad_points);
|
||||
|
||||
at::Tensor group_points(at::Tensor points, at::Tensor idx) {
|
||||
CHECK_CONTIGUOUS(points);
|
||||
CHECK_CONTIGUOUS(idx);
|
||||
CHECK_IS_FLOAT(points);
|
||||
CHECK_IS_INT(idx);
|
||||
|
||||
if (points.is_cuda()) {
|
||||
CHECK_CUDA(idx);
|
||||
}
|
||||
|
||||
at::Tensor output =
|
||||
torch::zeros({points.size(0), points.size(1), idx.size(1), idx.size(2)},
|
||||
at::device(points.device()).dtype(at::ScalarType::Float));
|
||||
|
||||
if (points.is_cuda()) {
|
||||
group_points_kernel_wrapper(points.size(0), points.size(1), points.size(2),
|
||||
idx.size(1), idx.size(2),
|
||||
points.data_ptr<float>(), idx.data_ptr<int>(),
|
||||
output.data_ptr<float>());
|
||||
} else {
|
||||
AT_ASSERT(false, "CPU not supported");
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
at::Tensor group_points_grad(at::Tensor grad_out, at::Tensor idx, const int n) {
|
||||
CHECK_CONTIGUOUS(grad_out);
|
||||
CHECK_CONTIGUOUS(idx);
|
||||
CHECK_IS_FLOAT(grad_out);
|
||||
CHECK_IS_INT(idx);
|
||||
|
||||
if (grad_out.is_cuda()) {
|
||||
CHECK_CUDA(idx);
|
||||
}
|
||||
|
||||
at::Tensor output =
|
||||
torch::zeros({grad_out.size(0), grad_out.size(1), n},
|
||||
at::device(grad_out.device()).dtype(at::ScalarType::Float));
|
||||
|
||||
if (grad_out.is_cuda()) {
|
||||
group_points_grad_kernel_wrapper(
|
||||
grad_out.size(0), grad_out.size(1), n, idx.size(1), idx.size(2),
|
||||
grad_out.data_ptr<float>(), idx.data_ptr<int>(),
|
||||
output.data_ptr<float>());
|
||||
} else {
|
||||
AT_ASSERT(false, "CPU not supported");
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "cuda_utils.h"
|
||||
|
||||
// input: points(b, c, n) idx(b, npoints, nsample)
|
||||
// output: out(b, c, npoints, nsample)
|
||||
__global__ void group_points_kernel(int b, int c, int n, int npoints,
|
||||
int nsample,
|
||||
const float *__restrict__ points,
|
||||
const int *__restrict__ idx,
|
||||
float *__restrict__ out) {
|
||||
int batch_index = blockIdx.x;
|
||||
points += batch_index * n * c;
|
||||
idx += batch_index * npoints * nsample;
|
||||
out += batch_index * npoints * nsample * c;
|
||||
|
||||
const int index = threadIdx.y * blockDim.x + threadIdx.x;
|
||||
const int stride = blockDim.y * blockDim.x;
|
||||
for (int i = index; i < c * npoints; i += stride) {
|
||||
const int l = i / npoints;
|
||||
const int j = i % npoints;
|
||||
for (int k = 0; k < nsample; ++k) {
|
||||
int ii = idx[j * nsample + k];
|
||||
out[(l * npoints + j) * nsample + k] = points[l * n + ii];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void group_points_kernel_wrapper(int b, int c, int n, int npoints, int nsample,
|
||||
const float *points, const int *idx,
|
||||
float *out) {
|
||||
cudaStream_t stream = at::cuda::getCurrentCUDAStream();
|
||||
|
||||
group_points_kernel<<<b, opt_block_config(npoints, c), 0, stream>>>(
|
||||
b, c, n, npoints, nsample, points, idx, out);
|
||||
|
||||
CUDA_CHECK_ERRORS();
|
||||
}
|
||||
|
||||
// input: grad_out(b, c, npoints, nsample), idx(b, npoints, nsample)
|
||||
// output: grad_points(b, c, n)
|
||||
__global__ void group_points_grad_kernel(int b, int c, int n, int npoints,
|
||||
int nsample,
|
||||
const float *__restrict__ grad_out,
|
||||
const int *__restrict__ idx,
|
||||
float *__restrict__ grad_points) {
|
||||
int batch_index = blockIdx.x;
|
||||
grad_out += batch_index * npoints * nsample * c;
|
||||
idx += batch_index * npoints * nsample;
|
||||
grad_points += batch_index * n * c;
|
||||
|
||||
const int index = threadIdx.y * blockDim.x + threadIdx.x;
|
||||
const int stride = blockDim.y * blockDim.x;
|
||||
for (int i = index; i < c * npoints; i += stride) {
|
||||
const int l = i / npoints;
|
||||
const int j = i % npoints;
|
||||
for (int k = 0; k < nsample; ++k) {
|
||||
int ii = idx[j * nsample + k];
|
||||
atomicAdd(grad_points + l * n + ii,
|
||||
grad_out[(l * npoints + j) * nsample + k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void group_points_grad_kernel_wrapper(int b, int c, int n, int npoints,
|
||||
int nsample, const float *grad_out,
|
||||
const int *idx, float *grad_points) {
|
||||
cudaStream_t stream = at::cuda::getCurrentCUDAStream();
|
||||
|
||||
group_points_grad_kernel<<<b, opt_block_config(npoints, c), 0, stream>>>(
|
||||
b, c, n, npoints, nsample, grad_out, idx, grad_points);
|
||||
|
||||
CUDA_CHECK_ERRORS();
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
#include "interpolate.h"
|
||||
#include "utils.h"
|
||||
|
||||
void three_nn_kernel_wrapper(int b, int n, int m, const float *unknown,
|
||||
const float *known, float *dist2, int *idx);
|
||||
void three_interpolate_kernel_wrapper(int b, int c, int m, int n,
|
||||
const float *points, const int *idx,
|
||||
const float *weight, float *out);
|
||||
void three_interpolate_grad_kernel_wrapper(int b, int c, int n, int m,
|
||||
const float *grad_out,
|
||||
const int *idx, const float *weight,
|
||||
float *grad_points);
|
||||
|
||||
std::vector<at::Tensor> three_nn(at::Tensor unknowns, at::Tensor knows) {
|
||||
CHECK_CONTIGUOUS(unknowns);
|
||||
CHECK_CONTIGUOUS(knows);
|
||||
CHECK_IS_FLOAT(unknowns);
|
||||
CHECK_IS_FLOAT(knows);
|
||||
|
||||
if (unknowns.is_cuda()) {
|
||||
CHECK_CUDA(knows);
|
||||
}
|
||||
|
||||
at::Tensor idx =
|
||||
torch::zeros({unknowns.size(0), unknowns.size(1), 3},
|
||||
at::device(unknowns.device()).dtype(at::ScalarType::Int));
|
||||
at::Tensor dist2 =
|
||||
torch::zeros({unknowns.size(0), unknowns.size(1), 3},
|
||||
at::device(unknowns.device()).dtype(at::ScalarType::Float));
|
||||
|
||||
if (unknowns.is_cuda()) {
|
||||
three_nn_kernel_wrapper(unknowns.size(0), unknowns.size(1), knows.size(1),
|
||||
unknowns.data_ptr<float>(), knows.data_ptr<float>(),
|
||||
dist2.data_ptr<float>(), idx.data_ptr<int>());
|
||||
} else {
|
||||
AT_ASSERT(false, "CPU not supported");
|
||||
}
|
||||
|
||||
return {dist2, idx};
|
||||
}
|
||||
|
||||
at::Tensor three_interpolate(at::Tensor points, at::Tensor idx,
|
||||
at::Tensor weight) {
|
||||
CHECK_CONTIGUOUS(points);
|
||||
CHECK_CONTIGUOUS(idx);
|
||||
CHECK_CONTIGUOUS(weight);
|
||||
CHECK_IS_FLOAT(points);
|
||||
CHECK_IS_INT(idx);
|
||||
CHECK_IS_FLOAT(weight);
|
||||
|
||||
if (points.is_cuda()) {
|
||||
CHECK_CUDA(idx);
|
||||
CHECK_CUDA(weight);
|
||||
}
|
||||
|
||||
at::Tensor output =
|
||||
torch::zeros({points.size(0), points.size(1), idx.size(1)},
|
||||
at::device(points.device()).dtype(at::ScalarType::Float));
|
||||
|
||||
if (points.is_cuda()) {
|
||||
three_interpolate_kernel_wrapper(
|
||||
points.size(0), points.size(1), points.size(2), idx.size(1),
|
||||
points.data_ptr<float>(), idx.data_ptr<int>(), weight.data_ptr<float>(),
|
||||
output.data_ptr<float>());
|
||||
} else {
|
||||
AT_ASSERT(false, "CPU not supported");
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
at::Tensor three_interpolate_grad(at::Tensor grad_out, at::Tensor idx,
|
||||
at::Tensor weight, const int m) {
|
||||
CHECK_CONTIGUOUS(grad_out);
|
||||
CHECK_CONTIGUOUS(idx);
|
||||
CHECK_CONTIGUOUS(weight);
|
||||
CHECK_IS_FLOAT(grad_out);
|
||||
CHECK_IS_INT(idx);
|
||||
CHECK_IS_FLOAT(weight);
|
||||
|
||||
if (grad_out.is_cuda()) {
|
||||
CHECK_CUDA(idx);
|
||||
CHECK_CUDA(weight);
|
||||
}
|
||||
|
||||
at::Tensor output =
|
||||
torch::zeros({grad_out.size(0), grad_out.size(1), m},
|
||||
at::device(grad_out.device()).dtype(at::ScalarType::Float));
|
||||
|
||||
if (grad_out.is_cuda()) {
|
||||
three_interpolate_grad_kernel_wrapper(
|
||||
grad_out.size(0), grad_out.size(1), grad_out.size(2), m,
|
||||
grad_out.data_ptr<float>(), idx.data_ptr<int>(),
|
||||
weight.data_ptr<float>(), output.data_ptr<float>());
|
||||
} else {
|
||||
AT_ASSERT(false, "CPU not supported");
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "cuda_utils.h"
|
||||
|
||||
// input: unknown(b, n, 3) known(b, m, 3)
|
||||
// output: dist2(b, n, 3), idx(b, n, 3)
|
||||
__global__ void three_nn_kernel(int b, int n, int m,
|
||||
const float *__restrict__ unknown,
|
||||
const float *__restrict__ known,
|
||||
float *__restrict__ dist2,
|
||||
int *__restrict__ idx) {
|
||||
int batch_index = blockIdx.x;
|
||||
unknown += batch_index * n * 3;
|
||||
known += batch_index * m * 3;
|
||||
dist2 += batch_index * n * 3;
|
||||
idx += batch_index * n * 3;
|
||||
|
||||
int index = threadIdx.x;
|
||||
int stride = blockDim.x;
|
||||
for (int j = index; j < n; j += stride) {
|
||||
float ux = unknown[j * 3 + 0];
|
||||
float uy = unknown[j * 3 + 1];
|
||||
float uz = unknown[j * 3 + 2];
|
||||
|
||||
double best1 = 1e40, best2 = 1e40, best3 = 1e40;
|
||||
int besti1 = 0, besti2 = 0, besti3 = 0;
|
||||
for (int k = 0; k < m; ++k) {
|
||||
float x = known[k * 3 + 0];
|
||||
float y = known[k * 3 + 1];
|
||||
float z = known[k * 3 + 2];
|
||||
float d = (ux - x) * (ux - x) + (uy - y) * (uy - y) + (uz - z) * (uz - z);
|
||||
if (d < best1) {
|
||||
best3 = best2;
|
||||
besti3 = besti2;
|
||||
best2 = best1;
|
||||
besti2 = besti1;
|
||||
best1 = d;
|
||||
besti1 = k;
|
||||
} else if (d < best2) {
|
||||
best3 = best2;
|
||||
besti3 = besti2;
|
||||
best2 = d;
|
||||
besti2 = k;
|
||||
} else if (d < best3) {
|
||||
best3 = d;
|
||||
besti3 = k;
|
||||
}
|
||||
}
|
||||
dist2[j * 3 + 0] = best1;
|
||||
dist2[j * 3 + 1] = best2;
|
||||
dist2[j * 3 + 2] = best3;
|
||||
|
||||
idx[j * 3 + 0] = besti1;
|
||||
idx[j * 3 + 1] = besti2;
|
||||
idx[j * 3 + 2] = besti3;
|
||||
}
|
||||
}
|
||||
|
||||
void three_nn_kernel_wrapper(int b, int n, int m, const float *unknown,
|
||||
const float *known, float *dist2, int *idx) {
|
||||
cudaStream_t stream = at::cuda::getCurrentCUDAStream();
|
||||
three_nn_kernel<<<b, opt_n_threads(n), 0, stream>>>(b, n, m, unknown, known,
|
||||
dist2, idx);
|
||||
|
||||
CUDA_CHECK_ERRORS();
|
||||
}
|
||||
|
||||
// input: points(b, c, m), idx(b, n, 3), weight(b, n, 3)
|
||||
// output: out(b, c, n)
|
||||
__global__ void three_interpolate_kernel(int b, int c, int m, int n,
|
||||
const float *__restrict__ points,
|
||||
const int *__restrict__ idx,
|
||||
const float *__restrict__ weight,
|
||||
float *__restrict__ out) {
|
||||
int batch_index = blockIdx.x;
|
||||
points += batch_index * m * c;
|
||||
|
||||
idx += batch_index * n * 3;
|
||||
weight += batch_index * n * 3;
|
||||
|
||||
out += batch_index * n * c;
|
||||
|
||||
const int index = threadIdx.y * blockDim.x + threadIdx.x;
|
||||
const int stride = blockDim.y * blockDim.x;
|
||||
for (int i = index; i < c * n; i += stride) {
|
||||
const int l = i / n;
|
||||
const int j = i % n;
|
||||
float w1 = weight[j * 3 + 0];
|
||||
float w2 = weight[j * 3 + 1];
|
||||
float w3 = weight[j * 3 + 2];
|
||||
|
||||
int i1 = idx[j * 3 + 0];
|
||||
int i2 = idx[j * 3 + 1];
|
||||
int i3 = idx[j * 3 + 2];
|
||||
|
||||
out[i] = points[l * m + i1] * w1 + points[l * m + i2] * w2 +
|
||||
points[l * m + i3] * w3;
|
||||
}
|
||||
}
|
||||
|
||||
void three_interpolate_kernel_wrapper(int b, int c, int m, int n,
|
||||
const float *points, const int *idx,
|
||||
const float *weight, float *out) {
|
||||
cudaStream_t stream = at::cuda::getCurrentCUDAStream();
|
||||
three_interpolate_kernel<<<b, opt_block_config(n, c), 0, stream>>>(
|
||||
b, c, m, n, points, idx, weight, out);
|
||||
|
||||
CUDA_CHECK_ERRORS();
|
||||
}
|
||||
|
||||
// input: grad_out(b, c, n), idx(b, n, 3), weight(b, n, 3)
|
||||
// output: grad_points(b, c, m)
|
||||
|
||||
__global__ void three_interpolate_grad_kernel(
|
||||
int b, int c, int n, int m, const float *__restrict__ grad_out,
|
||||
const int *__restrict__ idx, const float *__restrict__ weight,
|
||||
float *__restrict__ grad_points) {
|
||||
int batch_index = blockIdx.x;
|
||||
grad_out += batch_index * n * c;
|
||||
idx += batch_index * n * 3;
|
||||
weight += batch_index * n * 3;
|
||||
grad_points += batch_index * m * c;
|
||||
|
||||
const int index = threadIdx.y * blockDim.x + threadIdx.x;
|
||||
const int stride = blockDim.y * blockDim.x;
|
||||
for (int i = index; i < c * n; i += stride) {
|
||||
const int l = i / n;
|
||||
const int j = i % n;
|
||||
float w1 = weight[j * 3 + 0];
|
||||
float w2 = weight[j * 3 + 1];
|
||||
float w3 = weight[j * 3 + 2];
|
||||
|
||||
int i1 = idx[j * 3 + 0];
|
||||
int i2 = idx[j * 3 + 1];
|
||||
int i3 = idx[j * 3 + 2];
|
||||
|
||||
atomicAdd(grad_points + l * m + i1, grad_out[i] * w1);
|
||||
atomicAdd(grad_points + l * m + i2, grad_out[i] * w2);
|
||||
atomicAdd(grad_points + l * m + i3, grad_out[i] * w3);
|
||||
}
|
||||
}
|
||||
|
||||
void three_interpolate_grad_kernel_wrapper(int b, int c, int n, int m,
|
||||
const float *grad_out,
|
||||
const int *idx, const float *weight,
|
||||
float *grad_points) {
|
||||
cudaStream_t stream = at::cuda::getCurrentCUDAStream();
|
||||
three_interpolate_grad_kernel<<<b, opt_block_config(n, c), 0, stream>>>(
|
||||
b, c, n, m, grad_out, idx, weight, grad_points);
|
||||
|
||||
CUDA_CHECK_ERRORS();
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
#include "sampling.h"
|
||||
#include "utils.h"
|
||||
|
||||
void gather_points_kernel_wrapper(int b, int c, int n, int npoints,
|
||||
const float *points, const int *idx,
|
||||
float *out);
|
||||
void gather_points_grad_kernel_wrapper(int b, int c, int n, int npoints,
|
||||
const float *grad_out, const int *idx,
|
||||
float *grad_points);
|
||||
|
||||
void furthest_point_sampling_kernel_wrapper(int b, int n, int m,
|
||||
const float *dataset, float *temp,
|
||||
int *idxs);
|
||||
|
||||
at::Tensor gather_points(at::Tensor points, at::Tensor idx) {
|
||||
CHECK_CONTIGUOUS(points);
|
||||
CHECK_CONTIGUOUS(idx);
|
||||
CHECK_IS_FLOAT(points);
|
||||
CHECK_IS_INT(idx);
|
||||
|
||||
if (points.is_cuda()) {
|
||||
CHECK_CUDA(idx);
|
||||
}
|
||||
|
||||
at::Tensor output =
|
||||
torch::zeros({points.size(0), points.size(1), idx.size(1)},
|
||||
at::device(points.device()).dtype(at::ScalarType::Float));
|
||||
|
||||
if (points.is_cuda()) {
|
||||
gather_points_kernel_wrapper(points.size(0), points.size(1), points.size(2),
|
||||
idx.size(1), points.data_ptr<float>(),
|
||||
idx.data_ptr<int>(), output.data_ptr<float>());
|
||||
} else {
|
||||
AT_ASSERT(false, "CPU not supported");
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
at::Tensor gather_points_grad(at::Tensor grad_out, at::Tensor idx,
|
||||
const int n) {
|
||||
CHECK_CONTIGUOUS(grad_out);
|
||||
CHECK_CONTIGUOUS(idx);
|
||||
CHECK_IS_FLOAT(grad_out);
|
||||
CHECK_IS_INT(idx);
|
||||
|
||||
if (grad_out.is_cuda()) {
|
||||
CHECK_CUDA(idx);
|
||||
}
|
||||
|
||||
at::Tensor output =
|
||||
torch::zeros({grad_out.size(0), grad_out.size(1), n},
|
||||
at::device(grad_out.device()).dtype(at::ScalarType::Float));
|
||||
|
||||
if (grad_out.is_cuda()) {
|
||||
gather_points_grad_kernel_wrapper(grad_out.size(0), grad_out.size(1), n,
|
||||
idx.size(1), grad_out.data_ptr<float>(),
|
||||
idx.data_ptr<int>(),
|
||||
output.data_ptr<float>());
|
||||
} else {
|
||||
AT_ASSERT(false, "CPU not supported");
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
at::Tensor furthest_point_sampling(at::Tensor points, const int nsamples) {
|
||||
CHECK_CONTIGUOUS(points);
|
||||
CHECK_IS_FLOAT(points);
|
||||
|
||||
at::Tensor output =
|
||||
torch::zeros({points.size(0), nsamples},
|
||||
at::device(points.device()).dtype(at::ScalarType::Int));
|
||||
|
||||
at::Tensor tmp =
|
||||
torch::full({points.size(0), points.size(1)}, 1e10,
|
||||
at::device(points.device()).dtype(at::ScalarType::Float));
|
||||
|
||||
if (points.is_cuda()) {
|
||||
furthest_point_sampling_kernel_wrapper(
|
||||
points.size(0), points.size(1), nsamples, points.data_ptr<float>(),
|
||||
tmp.data_ptr<float>(), output.data_ptr<int>());
|
||||
} else {
|
||||
AT_ASSERT(false, "CPU not supported");
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "cuda_utils.h"
|
||||
|
||||
// input: points(b, c, n) idx(b, m)
|
||||
// output: out(b, c, m)
|
||||
__global__ void gather_points_kernel(int b, int c, int n, int m,
|
||||
const float *__restrict__ points,
|
||||
const int *__restrict__ idx,
|
||||
float *__restrict__ out) {
|
||||
for (int i = blockIdx.x; i < b; i += gridDim.x) {
|
||||
for (int l = blockIdx.y; l < c; l += gridDim.y) {
|
||||
for (int j = threadIdx.x; j < m; j += blockDim.x) {
|
||||
int a = idx[i * m + j];
|
||||
out[(i * c + l) * m + j] = points[(i * c + l) * n + a];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void gather_points_kernel_wrapper(int b, int c, int n, int npoints,
|
||||
const float *points, const int *idx,
|
||||
float *out) {
|
||||
gather_points_kernel<<<dim3(b, c, 1), opt_n_threads(npoints), 0,
|
||||
at::cuda::getCurrentCUDAStream()>>>(b, c, n, npoints,
|
||||
points, idx, out);
|
||||
|
||||
CUDA_CHECK_ERRORS();
|
||||
}
|
||||
|
||||
// input: grad_out(b, c, m) idx(b, m)
|
||||
// output: grad_points(b, c, n)
|
||||
__global__ void gather_points_grad_kernel(int b, int c, int n, int m,
|
||||
const float *__restrict__ grad_out,
|
||||
const int *__restrict__ idx,
|
||||
float *__restrict__ grad_points) {
|
||||
for (int i = blockIdx.x; i < b; i += gridDim.x) {
|
||||
for (int l = blockIdx.y; l < c; l += gridDim.y) {
|
||||
for (int j = threadIdx.x; j < m; j += blockDim.x) {
|
||||
int a = idx[i * m + j];
|
||||
atomicAdd(grad_points + (i * c + l) * n + a,
|
||||
grad_out[(i * c + l) * m + j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void gather_points_grad_kernel_wrapper(int b, int c, int n, int npoints,
|
||||
const float *grad_out, const int *idx,
|
||||
float *grad_points) {
|
||||
gather_points_grad_kernel<<<dim3(b, c, 1), opt_n_threads(npoints), 0,
|
||||
at::cuda::getCurrentCUDAStream()>>>(
|
||||
b, c, n, npoints, grad_out, idx, grad_points);
|
||||
|
||||
CUDA_CHECK_ERRORS();
|
||||
}
|
||||
|
||||
__device__ void __update(float *__restrict__ dists, int *__restrict__ dists_i,
|
||||
int idx1, int idx2) {
|
||||
const float v1 = dists[idx1], v2 = dists[idx2];
|
||||
const int i1 = dists_i[idx1], i2 = dists_i[idx2];
|
||||
dists[idx1] = max(v1, v2);
|
||||
dists_i[idx1] = v2 > v1 ? i2 : i1;
|
||||
}
|
||||
|
||||
// Input dataset: (b, n, 3), tmp: (b, n)
|
||||
// Ouput idxs (b, m)
|
||||
template <unsigned int block_size>
|
||||
__global__ void furthest_point_sampling_kernel(
|
||||
int b, int n, int m, const float *__restrict__ dataset,
|
||||
float *__restrict__ temp, int *__restrict__ idxs) {
|
||||
if (m <= 0) return;
|
||||
__shared__ float dists[block_size];
|
||||
__shared__ int dists_i[block_size];
|
||||
|
||||
int batch_index = blockIdx.x;
|
||||
dataset += batch_index * n * 3;
|
||||
temp += batch_index * n;
|
||||
idxs += batch_index * m;
|
||||
|
||||
int tid = threadIdx.x;
|
||||
const int stride = block_size;
|
||||
|
||||
int old = 0;
|
||||
if (threadIdx.x == 0) idxs[0] = old;
|
||||
|
||||
__syncthreads();
|
||||
for (int j = 1; j < m; j++) {
|
||||
int besti = 0;
|
||||
float best = -1;
|
||||
float x1 = dataset[old * 3 + 0];
|
||||
float y1 = dataset[old * 3 + 1];
|
||||
float z1 = dataset[old * 3 + 2];
|
||||
for (int k = tid; k < n; k += stride) {
|
||||
float x2, y2, z2;
|
||||
x2 = dataset[k * 3 + 0];
|
||||
y2 = dataset[k * 3 + 1];
|
||||
z2 = dataset[k * 3 + 2];
|
||||
float mag = (x2 * x2) + (y2 * y2) + (z2 * z2);
|
||||
if (mag <= 1e-3) continue;
|
||||
|
||||
float d =
|
||||
(x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) + (z2 - z1) * (z2 - z1);
|
||||
|
||||
float d2 = min(d, temp[k]);
|
||||
temp[k] = d2;
|
||||
besti = d2 > best ? k : besti;
|
||||
best = d2 > best ? d2 : best;
|
||||
}
|
||||
dists[tid] = best;
|
||||
dists_i[tid] = besti;
|
||||
__syncthreads();
|
||||
|
||||
if (block_size >= 512) {
|
||||
if (tid < 256) {
|
||||
__update(dists, dists_i, tid, tid + 256);
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
if (block_size >= 256) {
|
||||
if (tid < 128) {
|
||||
__update(dists, dists_i, tid, tid + 128);
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
if (block_size >= 128) {
|
||||
if (tid < 64) {
|
||||
__update(dists, dists_i, tid, tid + 64);
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
if (block_size >= 64) {
|
||||
if (tid < 32) {
|
||||
__update(dists, dists_i, tid, tid + 32);
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
if (block_size >= 32) {
|
||||
if (tid < 16) {
|
||||
__update(dists, dists_i, tid, tid + 16);
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
if (block_size >= 16) {
|
||||
if (tid < 8) {
|
||||
__update(dists, dists_i, tid, tid + 8);
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
if (block_size >= 8) {
|
||||
if (tid < 4) {
|
||||
__update(dists, dists_i, tid, tid + 4);
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
if (block_size >= 4) {
|
||||
if (tid < 2) {
|
||||
__update(dists, dists_i, tid, tid + 2);
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
if (block_size >= 2) {
|
||||
if (tid < 1) {
|
||||
__update(dists, dists_i, tid, tid + 1);
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
|
||||
old = dists_i[0];
|
||||
if (tid == 0) idxs[j] = old;
|
||||
}
|
||||
}
|
||||
|
||||
void furthest_point_sampling_kernel_wrapper(int b, int n, int m,
|
||||
const float *dataset, float *temp,
|
||||
int *idxs) {
|
||||
unsigned int n_threads = opt_n_threads(n);
|
||||
|
||||
cudaStream_t stream = at::cuda::getCurrentCUDAStream();
|
||||
|
||||
switch (n_threads) {
|
||||
case 512:
|
||||
furthest_point_sampling_kernel<512>
|
||||
<<<b, n_threads, 0, stream>>>(b, n, m, dataset, temp, idxs);
|
||||
break;
|
||||
case 256:
|
||||
furthest_point_sampling_kernel<256>
|
||||
<<<b, n_threads, 0, stream>>>(b, n, m, dataset, temp, idxs);
|
||||
break;
|
||||
case 128:
|
||||
furthest_point_sampling_kernel<128>
|
||||
<<<b, n_threads, 0, stream>>>(b, n, m, dataset, temp, idxs);
|
||||
break;
|
||||
case 64:
|
||||
furthest_point_sampling_kernel<64>
|
||||
<<<b, n_threads, 0, stream>>>(b, n, m, dataset, temp, idxs);
|
||||
break;
|
||||
case 32:
|
||||
furthest_point_sampling_kernel<32>
|
||||
<<<b, n_threads, 0, stream>>>(b, n, m, dataset, temp, idxs);
|
||||
break;
|
||||
case 16:
|
||||
furthest_point_sampling_kernel<16>
|
||||
<<<b, n_threads, 0, stream>>>(b, n, m, dataset, temp, idxs);
|
||||
break;
|
||||
case 8:
|
||||
furthest_point_sampling_kernel<8>
|
||||
<<<b, n_threads, 0, stream>>>(b, n, m, dataset, temp, idxs);
|
||||
break;
|
||||
case 4:
|
||||
furthest_point_sampling_kernel<4>
|
||||
<<<b, n_threads, 0, stream>>>(b, n, m, dataset, temp, idxs);
|
||||
break;
|
||||
case 2:
|
||||
furthest_point_sampling_kernel<2>
|
||||
<<<b, n_threads, 0, stream>>>(b, n, m, dataset, temp, idxs);
|
||||
break;
|
||||
case 1:
|
||||
furthest_point_sampling_kernel<1>
|
||||
<<<b, n_threads, 0, stream>>>(b, n, m, dataset, temp, idxs);
|
||||
break;
|
||||
default:
|
||||
furthest_point_sampling_kernel<512>
|
||||
<<<b, n_threads, 0, stream>>>(b, n, m, dataset, temp, idxs);
|
||||
}
|
||||
|
||||
CUDA_CHECK_ERRORS();
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
__version__ = "3.0.0"
|
||||
@@ -0,0 +1,209 @@
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from pointnet2_ops import pointnet2_utils
|
||||
|
||||
|
||||
def build_shared_mlp(mlp_spec: List[int], bn: bool = True):
|
||||
layers = []
|
||||
for i in range(1, len(mlp_spec)):
|
||||
layers.append(
|
||||
nn.Conv2d(mlp_spec[i - 1], mlp_spec[i], kernel_size=1, bias=not bn)
|
||||
)
|
||||
if bn:
|
||||
layers.append(nn.BatchNorm2d(mlp_spec[i]))
|
||||
layers.append(nn.ReLU(True))
|
||||
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
|
||||
class _PointnetSAModuleBase(nn.Module):
|
||||
def __init__(self):
|
||||
super(_PointnetSAModuleBase, self).__init__()
|
||||
self.npoint = None
|
||||
self.groupers = None
|
||||
self.mlps = None
|
||||
|
||||
def forward(
|
||||
self, xyz: torch.Tensor, features: Optional[torch.Tensor]
|
||||
) -> Tuple[torch.Tensor, torch.Tensor]:
|
||||
r"""
|
||||
Parameters
|
||||
----------
|
||||
xyz : torch.Tensor
|
||||
(B, N, 3) tensor of the xyz coordinates of the features
|
||||
features : torch.Tensor
|
||||
(B, C, N) tensor of the descriptors of the the features
|
||||
|
||||
Returns
|
||||
-------
|
||||
new_xyz : torch.Tensor
|
||||
(B, npoint, 3) tensor of the new features' xyz
|
||||
new_features : torch.Tensor
|
||||
(B, \sum_k(mlps[k][-1]), npoint) tensor of the new_features descriptors
|
||||
"""
|
||||
|
||||
new_features_list = []
|
||||
|
||||
xyz_flipped = xyz.transpose(1, 2).contiguous()
|
||||
new_xyz = (
|
||||
pointnet2_utils.gather_operation(
|
||||
xyz_flipped, pointnet2_utils.furthest_point_sample(xyz, self.npoint)
|
||||
)
|
||||
.transpose(1, 2)
|
||||
.contiguous()
|
||||
if self.npoint is not None
|
||||
else None
|
||||
)
|
||||
|
||||
for i in range(len(self.groupers)):
|
||||
new_features = self.groupers[i](
|
||||
xyz, new_xyz, features
|
||||
) # (B, C, npoint, nsample)
|
||||
|
||||
new_features = self.mlps[i](new_features) # (B, mlp[-1], npoint, nsample)
|
||||
new_features = F.max_pool2d(
|
||||
new_features, kernel_size=[1, new_features.size(3)]
|
||||
) # (B, mlp[-1], npoint, 1)
|
||||
new_features = new_features.squeeze(-1) # (B, mlp[-1], npoint)
|
||||
|
||||
new_features_list.append(new_features)
|
||||
|
||||
return new_xyz, torch.cat(new_features_list, dim=1)
|
||||
|
||||
|
||||
class PointnetSAModuleMSG(_PointnetSAModuleBase):
|
||||
r"""Pointnet set abstrction layer with multiscale grouping
|
||||
|
||||
Parameters
|
||||
----------
|
||||
npoint : int
|
||||
Number of features
|
||||
radii : list of float32
|
||||
list of radii to group with
|
||||
nsamples : list of int32
|
||||
Number of samples in each ball query
|
||||
mlps : list of list of int32
|
||||
Spec of the pointnet before the global max_pool for each scale
|
||||
bn : bool
|
||||
Use batchnorm
|
||||
"""
|
||||
|
||||
def __init__(self, npoint, radii, nsamples, mlps, bn=True, use_xyz=True):
|
||||
# type: (PointnetSAModuleMSG, int, List[float], List[int], List[List[int]], bool, bool) -> None
|
||||
super(PointnetSAModuleMSG, self).__init__()
|
||||
|
||||
assert len(radii) == len(nsamples) == len(mlps)
|
||||
|
||||
self.npoint = npoint
|
||||
self.groupers = nn.ModuleList()
|
||||
self.mlps = nn.ModuleList()
|
||||
for i in range(len(radii)):
|
||||
radius = radii[i]
|
||||
nsample = nsamples[i]
|
||||
self.groupers.append(
|
||||
pointnet2_utils.QueryAndGroup(radius, nsample, use_xyz=use_xyz)
|
||||
if npoint is not None
|
||||
else pointnet2_utils.GroupAll(use_xyz)
|
||||
)
|
||||
mlp_spec = mlps[i]
|
||||
if use_xyz:
|
||||
mlp_spec[0] += 3
|
||||
|
||||
self.mlps.append(build_shared_mlp(mlp_spec, bn))
|
||||
|
||||
|
||||
class PointnetSAModule(PointnetSAModuleMSG):
|
||||
r"""Pointnet set abstrction layer
|
||||
|
||||
Parameters
|
||||
----------
|
||||
npoint : int
|
||||
Number of features
|
||||
radius : float
|
||||
Radius of ball
|
||||
nsample : int
|
||||
Number of samples in the ball query
|
||||
mlp : list
|
||||
Spec of the pointnet before the global max_pool
|
||||
bn : bool
|
||||
Use batchnorm
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, mlp, npoint=None, radius=None, nsample=None, bn=True, use_xyz=True
|
||||
):
|
||||
# type: (PointnetSAModule, List[int], int, float, int, bool, bool) -> None
|
||||
super(PointnetSAModule, self).__init__(
|
||||
mlps=[mlp],
|
||||
npoint=npoint,
|
||||
radii=[radius],
|
||||
nsamples=[nsample],
|
||||
bn=bn,
|
||||
use_xyz=use_xyz,
|
||||
)
|
||||
|
||||
|
||||
class PointnetFPModule(nn.Module):
|
||||
r"""Propigates the features of one set to another
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mlp : list
|
||||
Pointnet module parameters
|
||||
bn : bool
|
||||
Use batchnorm
|
||||
"""
|
||||
|
||||
def __init__(self, mlp, bn=True):
|
||||
# type: (PointnetFPModule, List[int], bool) -> None
|
||||
super(PointnetFPModule, self).__init__()
|
||||
self.mlp = build_shared_mlp(mlp, bn=bn)
|
||||
|
||||
def forward(self, unknown, known, unknow_feats, known_feats):
|
||||
# type: (PointnetFPModule, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor) -> torch.Tensor
|
||||
r"""
|
||||
Parameters
|
||||
----------
|
||||
unknown : torch.Tensor
|
||||
(B, n, 3) tensor of the xyz positions of the unknown features
|
||||
known : torch.Tensor
|
||||
(B, m, 3) tensor of the xyz positions of the known features
|
||||
unknow_feats : torch.Tensor
|
||||
(B, C1, n) tensor of the features to be propigated to
|
||||
known_feats : torch.Tensor
|
||||
(B, C2, m) tensor of features to be propigated
|
||||
|
||||
Returns
|
||||
-------
|
||||
new_features : torch.Tensor
|
||||
(B, mlp[-1], n) tensor of the features of the unknown features
|
||||
"""
|
||||
|
||||
if known is not None:
|
||||
dist, idx = pointnet2_utils.three_nn(unknown, known)
|
||||
dist_recip = 1.0 / (dist + 1e-8)
|
||||
norm = torch.sum(dist_recip, dim=2, keepdim=True)
|
||||
weight = dist_recip / norm
|
||||
|
||||
interpolated_feats = pointnet2_utils.three_interpolate(
|
||||
known_feats, idx, weight
|
||||
)
|
||||
else:
|
||||
interpolated_feats = known_feats.expand(
|
||||
*(known_feats.size()[0:2] + [unknown.size(1)])
|
||||
)
|
||||
|
||||
if unknow_feats is not None:
|
||||
new_features = torch.cat(
|
||||
[interpolated_feats, unknow_feats], dim=1
|
||||
) # (B, C2 + C1, n)
|
||||
else:
|
||||
new_features = interpolated_feats
|
||||
|
||||
new_features = new_features.unsqueeze(-1)
|
||||
new_features = self.mlp(new_features)
|
||||
|
||||
return new_features.squeeze(-1)
|
||||
@@ -0,0 +1,379 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import warnings
|
||||
from torch.autograd import Function
|
||||
from typing import *
|
||||
|
||||
try:
|
||||
import pointnet2_ops._ext as _ext
|
||||
except ImportError:
|
||||
from torch.utils.cpp_extension import load
|
||||
import glob
|
||||
import os.path as osp
|
||||
import os
|
||||
|
||||
warnings.warn("Unable to load pointnet2_ops cpp extension. JIT Compiling.")
|
||||
|
||||
_ext_src_root = osp.join(osp.dirname(__file__), "_ext-src")
|
||||
_ext_sources = glob.glob(osp.join(_ext_src_root, "src", "*.cpp")) + glob.glob(
|
||||
osp.join(_ext_src_root, "src", "*.cu")
|
||||
)
|
||||
_ext_headers = glob.glob(osp.join(_ext_src_root, "include", "*"))
|
||||
|
||||
os.environ["TORCH_CUDA_ARCH_LIST"] = "3.7+PTX;5.0;6.0;6.1;6.2;7.0;7.5"
|
||||
_ext = load(
|
||||
"_ext",
|
||||
sources=_ext_sources,
|
||||
extra_include_paths=[osp.join(_ext_src_root, "include")],
|
||||
extra_cflags=["-O3"],
|
||||
extra_cuda_cflags=["-O3", "-Xfatbin", "-compress-all"],
|
||||
with_cuda=True,
|
||||
)
|
||||
|
||||
|
||||
class FurthestPointSampling(Function):
|
||||
@staticmethod
|
||||
def forward(ctx, xyz, npoint):
|
||||
# type: (Any, torch.Tensor, int) -> torch.Tensor
|
||||
r"""
|
||||
Uses iterative furthest point sampling to select a set of npoint features that have the largest
|
||||
minimum distance
|
||||
|
||||
Parameters
|
||||
----------
|
||||
xyz : torch.Tensor
|
||||
(B, N, 3) tensor where N > npoint
|
||||
npoint : int32
|
||||
number of features in the sampled set
|
||||
|
||||
Returns
|
||||
-------
|
||||
torch.Tensor
|
||||
(B, npoint) tensor containing the set
|
||||
"""
|
||||
out = _ext.furthest_point_sampling(xyz, npoint)
|
||||
|
||||
ctx.mark_non_differentiable(out)
|
||||
|
||||
return out
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, grad_out):
|
||||
return ()
|
||||
|
||||
|
||||
furthest_point_sample = FurthestPointSampling.apply
|
||||
|
||||
|
||||
class GatherOperation(Function):
|
||||
@staticmethod
|
||||
def forward(ctx, features, idx):
|
||||
# type: (Any, torch.Tensor, torch.Tensor) -> torch.Tensor
|
||||
r"""
|
||||
|
||||
Parameters
|
||||
----------
|
||||
features : torch.Tensor
|
||||
(B, C, N) tensor
|
||||
|
||||
idx : torch.Tensor
|
||||
(B, npoint) tensor of the features to gather
|
||||
|
||||
Returns
|
||||
-------
|
||||
torch.Tensor
|
||||
(B, C, npoint) tensor
|
||||
"""
|
||||
|
||||
ctx.save_for_backward(idx, features)
|
||||
|
||||
return _ext.gather_points(features, idx)
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, grad_out):
|
||||
idx, features = ctx.saved_tensors
|
||||
N = features.size(2)
|
||||
|
||||
grad_features = _ext.gather_points_grad(grad_out.contiguous(), idx, N)
|
||||
return grad_features, None
|
||||
|
||||
|
||||
gather_operation = GatherOperation.apply
|
||||
|
||||
|
||||
class ThreeNN(Function):
|
||||
@staticmethod
|
||||
def forward(ctx, unknown, known):
|
||||
# type: (Any, torch.Tensor, torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]
|
||||
r"""
|
||||
Find the three nearest neighbors of unknown in known
|
||||
Parameters
|
||||
----------
|
||||
unknown : torch.Tensor
|
||||
(B, n, 3) tensor of known features
|
||||
known : torch.Tensor
|
||||
(B, m, 3) tensor of unknown features
|
||||
|
||||
Returns
|
||||
-------
|
||||
dist : torch.Tensor
|
||||
(B, n, 3) l2 distance to the three nearest neighbors
|
||||
idx : torch.Tensor
|
||||
(B, n, 3) index of 3 nearest neighbors
|
||||
"""
|
||||
dist2, idx = _ext.three_nn(unknown, known)
|
||||
dist = torch.sqrt(dist2)
|
||||
|
||||
ctx.mark_non_differentiable(dist, idx)
|
||||
|
||||
return dist, idx
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, grad_dist, grad_idx):
|
||||
return ()
|
||||
|
||||
|
||||
three_nn = ThreeNN.apply
|
||||
|
||||
|
||||
class ThreeInterpolate(Function):
|
||||
@staticmethod
|
||||
def forward(ctx, features, idx, weight):
|
||||
# type(Any, torch.Tensor, torch.Tensor, torch.Tensor) -> Torch.Tensor
|
||||
r"""
|
||||
Performs weight linear interpolation on 3 features
|
||||
Parameters
|
||||
----------
|
||||
features : torch.Tensor
|
||||
(B, c, m) Features descriptors to be interpolated from
|
||||
idx : torch.Tensor
|
||||
(B, n, 3) three nearest neighbors of the target features in features
|
||||
weight : torch.Tensor
|
||||
(B, n, 3) weights
|
||||
|
||||
Returns
|
||||
-------
|
||||
torch.Tensor
|
||||
(B, c, n) tensor of the interpolated features
|
||||
"""
|
||||
ctx.save_for_backward(idx, weight, features)
|
||||
|
||||
return _ext.three_interpolate(features, idx, weight)
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, grad_out):
|
||||
# type: (Any, torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]
|
||||
r"""
|
||||
Parameters
|
||||
----------
|
||||
grad_out : torch.Tensor
|
||||
(B, c, n) tensor with gradients of ouputs
|
||||
|
||||
Returns
|
||||
-------
|
||||
grad_features : torch.Tensor
|
||||
(B, c, m) tensor with gradients of features
|
||||
|
||||
None
|
||||
|
||||
None
|
||||
"""
|
||||
idx, weight, features = ctx.saved_tensors
|
||||
m = features.size(2)
|
||||
|
||||
grad_features = _ext.three_interpolate_grad(
|
||||
grad_out.contiguous(), idx, weight, m
|
||||
)
|
||||
|
||||
return grad_features, torch.zeros_like(idx), torch.zeros_like(weight)
|
||||
|
||||
|
||||
three_interpolate = ThreeInterpolate.apply
|
||||
|
||||
|
||||
class GroupingOperation(Function):
|
||||
@staticmethod
|
||||
def forward(ctx, features, idx):
|
||||
# type: (Any, torch.Tensor, torch.Tensor) -> torch.Tensor
|
||||
r"""
|
||||
|
||||
Parameters
|
||||
----------
|
||||
features : torch.Tensor
|
||||
(B, C, N) tensor of features to group
|
||||
idx : torch.Tensor
|
||||
(B, npoint, nsample) tensor containing the indicies of features to group with
|
||||
|
||||
Returns
|
||||
-------
|
||||
torch.Tensor
|
||||
(B, C, npoint, nsample) tensor
|
||||
"""
|
||||
ctx.save_for_backward(idx, features)
|
||||
|
||||
return _ext.group_points(features, idx)
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, grad_out):
|
||||
# type: (Any, torch.tensor) -> Tuple[torch.Tensor, torch.Tensor]
|
||||
r"""
|
||||
|
||||
Parameters
|
||||
----------
|
||||
grad_out : torch.Tensor
|
||||
(B, C, npoint, nsample) tensor of the gradients of the output from forward
|
||||
|
||||
Returns
|
||||
-------
|
||||
torch.Tensor
|
||||
(B, C, N) gradient of the features
|
||||
None
|
||||
"""
|
||||
idx, features = ctx.saved_tensors
|
||||
N = features.size(2)
|
||||
|
||||
grad_features = _ext.group_points_grad(grad_out.contiguous(), idx, N)
|
||||
|
||||
return grad_features, torch.zeros_like(idx)
|
||||
|
||||
|
||||
grouping_operation = GroupingOperation.apply
|
||||
|
||||
|
||||
class BallQuery(Function):
|
||||
@staticmethod
|
||||
def forward(ctx, radius, nsample, xyz, new_xyz):
|
||||
# type: (Any, float, int, torch.Tensor, torch.Tensor) -> torch.Tensor
|
||||
r"""
|
||||
|
||||
Parameters
|
||||
----------
|
||||
radius : float
|
||||
radius of the balls
|
||||
nsample : int
|
||||
maximum number of features in the balls
|
||||
xyz : torch.Tensor
|
||||
(B, N, 3) xyz coordinates of the features
|
||||
new_xyz : torch.Tensor
|
||||
(B, npoint, 3) centers of the ball query
|
||||
|
||||
Returns
|
||||
-------
|
||||
torch.Tensor
|
||||
(B, npoint, nsample) tensor with the indicies of the features that form the query balls
|
||||
"""
|
||||
output = _ext.ball_query(new_xyz, xyz, radius, nsample)
|
||||
|
||||
ctx.mark_non_differentiable(output)
|
||||
|
||||
return output
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, grad_out):
|
||||
return ()
|
||||
|
||||
|
||||
ball_query = BallQuery.apply
|
||||
|
||||
|
||||
class QueryAndGroup(nn.Module):
|
||||
r"""
|
||||
Groups with a ball query of radius
|
||||
|
||||
Parameters
|
||||
---------
|
||||
radius : float32
|
||||
Radius of ball
|
||||
nsample : int32
|
||||
Maximum number of features to gather in the ball
|
||||
"""
|
||||
|
||||
def __init__(self, radius, nsample, use_xyz=True):
|
||||
# type: (QueryAndGroup, float, int, bool) -> None
|
||||
super(QueryAndGroup, self).__init__()
|
||||
self.radius, self.nsample, self.use_xyz = radius, nsample, use_xyz
|
||||
|
||||
def forward(self, xyz, new_xyz, features=None):
|
||||
# type: (QueryAndGroup, torch.Tensor. torch.Tensor, torch.Tensor) -> Tuple[Torch.Tensor]
|
||||
r"""
|
||||
Parameters
|
||||
----------
|
||||
xyz : torch.Tensor
|
||||
xyz coordinates of the features (B, N, 3)
|
||||
new_xyz : torch.Tensor
|
||||
centriods (B, npoint, 3)
|
||||
features : torch.Tensor
|
||||
Descriptors of the features (B, C, N)
|
||||
|
||||
Returns
|
||||
-------
|
||||
new_features : torch.Tensor
|
||||
(B, 3 + C, npoint, nsample) tensor
|
||||
"""
|
||||
|
||||
idx = ball_query(self.radius, self.nsample, xyz, new_xyz)
|
||||
xyz_trans = xyz.transpose(1, 2).contiguous()
|
||||
grouped_xyz = grouping_operation(xyz_trans, idx) # (B, 3, npoint, nsample)
|
||||
grouped_xyz -= new_xyz.transpose(1, 2).unsqueeze(-1)
|
||||
|
||||
if features is not None:
|
||||
grouped_features = grouping_operation(features, idx)
|
||||
if self.use_xyz:
|
||||
new_features = torch.cat(
|
||||
[grouped_xyz, grouped_features], dim=1
|
||||
) # (B, C + 3, npoint, nsample)
|
||||
else:
|
||||
new_features = grouped_features
|
||||
else:
|
||||
assert (
|
||||
self.use_xyz
|
||||
), "Cannot have not features and not use xyz as a feature!"
|
||||
new_features = grouped_xyz
|
||||
|
||||
return new_features
|
||||
|
||||
|
||||
class GroupAll(nn.Module):
|
||||
r"""
|
||||
Groups all features
|
||||
|
||||
Parameters
|
||||
---------
|
||||
"""
|
||||
|
||||
def __init__(self, use_xyz=True):
|
||||
# type: (GroupAll, bool) -> None
|
||||
super(GroupAll, self).__init__()
|
||||
self.use_xyz = use_xyz
|
||||
|
||||
def forward(self, xyz, new_xyz, features=None):
|
||||
# type: (GroupAll, torch.Tensor, torch.Tensor, torch.Tensor) -> Tuple[torch.Tensor]
|
||||
r"""
|
||||
Parameters
|
||||
----------
|
||||
xyz : torch.Tensor
|
||||
xyz coordinates of the features (B, N, 3)
|
||||
new_xyz : torch.Tensor
|
||||
Ignored
|
||||
features : torch.Tensor
|
||||
Descriptors of the features (B, C, N)
|
||||
|
||||
Returns
|
||||
-------
|
||||
new_features : torch.Tensor
|
||||
(B, C + 3, 1, N) tensor
|
||||
"""
|
||||
|
||||
grouped_xyz = xyz.transpose(1, 2).unsqueeze(2)
|
||||
if features is not None:
|
||||
grouped_features = features.unsqueeze(2)
|
||||
if self.use_xyz:
|
||||
new_features = torch.cat(
|
||||
[grouped_xyz, grouped_features], dim=1
|
||||
) # (B, 3 + C, 1, N)
|
||||
else:
|
||||
new_features = grouped_features
|
||||
else:
|
||||
new_features = grouped_xyz
|
||||
|
||||
return new_features
|
||||
@@ -0,0 +1,41 @@
|
||||
import glob
|
||||
import os
|
||||
import os.path as osp
|
||||
|
||||
from setuptools import find_packages, setup
|
||||
import torch
|
||||
from torch.utils.cpp_extension import BuildExtension, CUDAExtension
|
||||
|
||||
this_dir = osp.dirname(osp.abspath(__file__))
|
||||
_ext_src_root = osp.join("pointnet2_ops", "_ext-src")
|
||||
_ext_sources = glob.glob(osp.join(_ext_src_root, "src", "*.cpp")) + glob.glob(
|
||||
osp.join(_ext_src_root, "src", "*.cu")
|
||||
)
|
||||
_ext_headers = glob.glob(osp.join(_ext_src_root, "include", "*"))
|
||||
|
||||
requirements = ["torch>=1.4"]
|
||||
|
||||
exec(open(osp.join("pointnet2_ops", "_version.py")).read())
|
||||
|
||||
os.environ["TORCH_CUDA_ARCH_LIST"] = ".".join(map(str, torch.cuda.get_device_capability()))
|
||||
# os.environ["TORCH_CUDA_ARCH_LIST"] = "5.0;6.0;6.1;6.2;7.0;7.5;8.0;8.6"
|
||||
setup(
|
||||
name="pointnet2_ops",
|
||||
version=__version__,
|
||||
author="Erik Wijmans",
|
||||
packages=find_packages(),
|
||||
install_requires=requirements,
|
||||
ext_modules=[
|
||||
CUDAExtension(
|
||||
name="pointnet2_ops._ext",
|
||||
sources=_ext_sources,
|
||||
extra_compile_args={
|
||||
"cxx": ["-O3"],
|
||||
"nvcc": ["-O3", "-Xfatbin", "-compress-all"],
|
||||
},
|
||||
include_dirs=[osp.join(this_dir, _ext_src_root, "include")],
|
||||
)
|
||||
],
|
||||
cmdclass={"build_ext": BuildExtension},
|
||||
include_package_data=True,
|
||||
)
|
||||
@@ -0,0 +1,69 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Author: Peng Xiang
|
||||
|
||||
import torch
|
||||
from torch import nn, einsum
|
||||
from .utils import MLP_Res, grouping_operation, query_knn
|
||||
|
||||
|
||||
class SkipTransformer(nn.Module):
|
||||
def __init__(self, in_channel, dim=256, n_knn=16, pos_hidden_dim=64, attn_hidden_multiplier=4):
|
||||
super(SkipTransformer, self).__init__()
|
||||
self.mlp_v = MLP_Res(in_dim=in_channel*2, hidden_dim=in_channel, out_dim=in_channel)
|
||||
self.n_knn = n_knn
|
||||
self.conv_key = nn.Conv1d(in_channel, dim, 1)
|
||||
self.conv_query = nn.Conv1d(in_channel, dim, 1)
|
||||
self.conv_value = nn.Conv1d(in_channel, dim, 1)
|
||||
|
||||
self.pos_mlp = nn.Sequential(
|
||||
nn.Conv2d(3, pos_hidden_dim, 1),
|
||||
nn.BatchNorm2d(pos_hidden_dim),
|
||||
nn.ReLU(),
|
||||
nn.Conv2d(pos_hidden_dim, dim, 1)
|
||||
)
|
||||
|
||||
self.attn_mlp = nn.Sequential(
|
||||
nn.Conv2d(dim, dim * attn_hidden_multiplier, 1),
|
||||
nn.BatchNorm2d(dim * attn_hidden_multiplier),
|
||||
nn.ReLU(),
|
||||
nn.Conv2d(dim * attn_hidden_multiplier, dim, 1)
|
||||
)
|
||||
|
||||
self.conv_end = nn.Conv1d(dim, in_channel, 1)
|
||||
|
||||
def forward(self, pos, key, query, include_self=True):
|
||||
"""
|
||||
Args:
|
||||
pos: (B, 3, N)
|
||||
key: (B, in_channel, N)
|
||||
query: (B, in_channel, N)
|
||||
include_self: boolean
|
||||
|
||||
Returns:
|
||||
Tensor: (B, in_channel, N), shape context feature
|
||||
"""
|
||||
value = self.mlp_v(torch.cat([key, query], 1))
|
||||
identity = value
|
||||
key = self.conv_key(key)
|
||||
query = self.conv_query(query)
|
||||
value = self.conv_value(value)
|
||||
b, dim, n = value.shape
|
||||
|
||||
pos_flipped = pos.permute(0, 2, 1).contiguous()
|
||||
idx_knn = query_knn(self.n_knn, pos_flipped, pos_flipped, include_self=include_self)
|
||||
|
||||
key = grouping_operation(key, idx_knn) # b, dim, n, n_knn
|
||||
qk_rel = query.reshape((b, -1, n, 1)) - key
|
||||
|
||||
pos_rel = pos.reshape((b, -1, n, 1)) - grouping_operation(pos, idx_knn) # b, 3, n, n_knn
|
||||
pos_embedding = self.pos_mlp(pos_rel)
|
||||
|
||||
attention = self.attn_mlp(qk_rel + pos_embedding) # b, dim, n, n_knn
|
||||
attention = torch.softmax(attention, -1)
|
||||
|
||||
value = value.reshape((b, -1, n, 1)) + pos_embedding #
|
||||
|
||||
agg = einsum('b c i j, b c i j -> b c i', attention, value) # b, dim, n
|
||||
y = self.conv_end(agg)
|
||||
|
||||
return y + identity
|
||||
@@ -0,0 +1,741 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Author: Peng Xiang
|
||||
|
||||
import types
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
import numpy as np
|
||||
from torch import nn, einsum
|
||||
from pointnet2_ops.pointnet2_utils import furthest_point_sample, \
|
||||
gather_operation, ball_query, three_nn, three_interpolate, grouping_operation
|
||||
|
||||
class Conv1d(nn.Module):
|
||||
def __init__(self, in_channel, out_channel, kernel_size=1, stride=1, if_bn=True, activation_fn=torch.relu):
|
||||
super(Conv1d, self).__init__()
|
||||
self.conv = nn.Conv1d(in_channel, out_channel, kernel_size, stride=stride)
|
||||
self.if_bn = if_bn
|
||||
self.bn = nn.BatchNorm1d(out_channel)
|
||||
self.activation_fn = activation_fn
|
||||
|
||||
def forward(self, input):
|
||||
out = self.conv(input)
|
||||
if self.if_bn:
|
||||
out = self.bn(out)
|
||||
|
||||
if self.activation_fn is not None:
|
||||
out = self.activation_fn(out)
|
||||
|
||||
return out
|
||||
|
||||
class Conv2d(nn.Module):
|
||||
def __init__(self, in_channel, out_channel, kernel_size=(1, 1), stride=(1, 1), if_bn=True, activation_fn=torch.relu):
|
||||
super(Conv2d, self).__init__()
|
||||
self.conv = nn.Conv2d(in_channel, out_channel, kernel_size, stride=stride)
|
||||
self.if_bn = if_bn
|
||||
self.bn = nn.BatchNorm2d(out_channel)
|
||||
self.activation_fn = activation_fn
|
||||
|
||||
def forward(self, input):
|
||||
out = self.conv(input)
|
||||
if self.if_bn:
|
||||
out = self.bn(out)
|
||||
|
||||
if self.activation_fn is not None:
|
||||
out = self.activation_fn(out)
|
||||
|
||||
return out
|
||||
|
||||
class MLP(nn.Module):
|
||||
def __init__(self, in_channel, layer_dims, bn=None):
|
||||
super(MLP, self).__init__()
|
||||
layers = []
|
||||
last_channel = in_channel
|
||||
for out_channel in layer_dims[:-1]:
|
||||
layers.append(nn.Linear(last_channel, out_channel))
|
||||
if bn:
|
||||
layers.append(nn.BatchNorm1d(out_channel))
|
||||
layers.append(nn.ReLU())
|
||||
last_channel = out_channel
|
||||
layers.append(nn.Linear(last_channel, layer_dims[-1]))
|
||||
self.mlp = nn.Sequential(*layers)
|
||||
|
||||
def forward(self, inputs):
|
||||
return self.mlp(inputs)
|
||||
|
||||
class MLP_CONV(nn.Module):
|
||||
def __init__(self, in_channel, layer_dims, bn=None):
|
||||
super(MLP_CONV, self).__init__()
|
||||
layers = []
|
||||
last_channel = in_channel
|
||||
for out_channel in layer_dims[:-1]:
|
||||
layers.append(nn.Conv1d(last_channel, out_channel, 1))
|
||||
if bn:
|
||||
layers.append(nn.BatchNorm1d(out_channel))
|
||||
layers.append(nn.ReLU())
|
||||
last_channel = out_channel
|
||||
layers.append(nn.Conv1d(last_channel, layer_dims[-1], 1))
|
||||
self.mlp = nn.Sequential(*layers)
|
||||
|
||||
def forward(self, inputs):
|
||||
return self.mlp(inputs)
|
||||
|
||||
class MLP_Res(nn.Module):
|
||||
def __init__(self, in_dim=128, hidden_dim=None, out_dim=128):
|
||||
super(MLP_Res, self).__init__()
|
||||
if hidden_dim is None:
|
||||
hidden_dim = in_dim
|
||||
self.conv_1 = nn.Conv1d(in_dim, hidden_dim, 1)
|
||||
self.conv_2 = nn.Conv1d(hidden_dim, out_dim, 1)
|
||||
self.conv_shortcut = nn.Conv1d(in_dim, out_dim, 1)
|
||||
|
||||
def forward(self, x):
|
||||
"""
|
||||
Args:
|
||||
x: (B, out_dim, n)
|
||||
"""
|
||||
shortcut = self.conv_shortcut(x)
|
||||
out = self.conv_2(torch.relu(self.conv_1(x))) + shortcut
|
||||
return out
|
||||
|
||||
|
||||
def sample_and_group(xyz, points, npoint, nsample, radius, use_xyz=True):
|
||||
"""
|
||||
Args:
|
||||
xyz: Tensor, (B, 3, N)
|
||||
points: Tensor, (B, f, N)
|
||||
npoint: int
|
||||
nsample: int
|
||||
radius: float
|
||||
use_xyz: boolean
|
||||
|
||||
Returns:
|
||||
new_xyz: Tensor, (B, 3, npoint)
|
||||
new_points: Tensor, (B, 3 | f+3 | f, npoint, nsample)
|
||||
idx_local: Tensor, (B, npoint, nsample)
|
||||
grouped_xyz: Tensor, (B, 3, npoint, nsample)
|
||||
|
||||
"""
|
||||
xyz_flipped = xyz.permute(0, 2, 1).contiguous() # (B, N, 3)
|
||||
new_xyz = gather_operation(xyz, furthest_point_sample(xyz_flipped, npoint)) # (B, 3, npoint)
|
||||
|
||||
idx = ball_query(radius, nsample, xyz_flipped, new_xyz.permute(0, 2, 1).contiguous()) # (B, npoint, nsample)
|
||||
grouped_xyz = grouping_operation(xyz, idx) # (B, 3, npoint, nsample)
|
||||
grouped_xyz -= new_xyz.unsqueeze(3).repeat(1, 1, 1, nsample)
|
||||
|
||||
if points is not None:
|
||||
grouped_points = grouping_operation(points, idx) # (B, f, npoint, nsample)
|
||||
if use_xyz:
|
||||
new_points = torch.cat([grouped_xyz, grouped_points], 1)
|
||||
else:
|
||||
new_points = grouped_points
|
||||
else:
|
||||
new_points = grouped_xyz
|
||||
|
||||
return new_xyz, new_points, idx, grouped_xyz
|
||||
|
||||
|
||||
def sample_and_group_all(xyz, points, use_xyz=True):
|
||||
"""
|
||||
Args:
|
||||
xyz: Tensor, (B, 3, nsample)
|
||||
points: Tensor, (B, f, nsample)
|
||||
use_xyz: boolean
|
||||
|
||||
Returns:
|
||||
new_xyz: Tensor, (B, 3, 1)
|
||||
new_points: Tensor, (B, f|f+3|3, 1, nsample)
|
||||
idx: Tensor, (B, 1, nsample)
|
||||
grouped_xyz: Tensor, (B, 3, 1, nsample)
|
||||
"""
|
||||
b, _, nsample = xyz.shape
|
||||
device = xyz.device
|
||||
new_xyz = torch.zeros((1, 3, 1), dtype=torch.float, device=device).repeat(b, 1, 1)
|
||||
grouped_xyz = xyz.reshape((b, 3, 1, nsample))
|
||||
idx = torch.arange(nsample, device=device).reshape(1, 1, nsample).repeat(b, 1, 1)
|
||||
if points is not None:
|
||||
if use_xyz:
|
||||
new_points = torch.cat([xyz, points], 1)
|
||||
else:
|
||||
new_points = points
|
||||
new_points = new_points.unsqueeze(2)
|
||||
else:
|
||||
new_points = grouped_xyz
|
||||
|
||||
return new_xyz, new_points, idx, grouped_xyz
|
||||
|
||||
|
||||
class PointNet_SA_Module(nn.Module):
|
||||
def __init__(self, npoint, nsample, radius, in_channel, mlp, if_bn=True, group_all=False, use_xyz=True):
|
||||
"""
|
||||
Args:
|
||||
npoint: int, number of points to sample
|
||||
nsample: int, number of points in each local region
|
||||
radius: float
|
||||
in_channel: int, input channel of features(points)
|
||||
mlp: list of int,
|
||||
"""
|
||||
super(PointNet_SA_Module, self).__init__()
|
||||
self.npoint = npoint
|
||||
self.nsample = nsample
|
||||
self.radius = radius
|
||||
self.mlp = mlp
|
||||
self.group_all = group_all
|
||||
self.use_xyz = use_xyz
|
||||
if use_xyz:
|
||||
in_channel += 3
|
||||
|
||||
last_channel = in_channel
|
||||
self.mlp_conv = []
|
||||
for out_channel in mlp:
|
||||
self.mlp_conv.append(Conv2d(last_channel, out_channel, if_bn=if_bn))
|
||||
last_channel = out_channel
|
||||
|
||||
self.mlp_conv = nn.Sequential(*self.mlp_conv)
|
||||
|
||||
def forward(self, xyz, points):
|
||||
"""
|
||||
Args:
|
||||
xyz: Tensor, (B, 3, N)
|
||||
points: Tensor, (B, f, N)
|
||||
|
||||
Returns:
|
||||
new_xyz: Tensor, (B, 3, npoint)
|
||||
new_points: Tensor, (B, mlp[-1], npoint)
|
||||
"""
|
||||
if self.group_all:
|
||||
new_xyz, new_points, idx, grouped_xyz = sample_and_group_all(xyz, points, self.use_xyz)
|
||||
else:
|
||||
new_xyz, new_points, idx, grouped_xyz = sample_and_group(xyz, points, self.npoint, self.nsample, self.radius, self.use_xyz)
|
||||
|
||||
new_points = self.mlp_conv(new_points)
|
||||
new_points = torch.max(new_points, 3)[0]
|
||||
|
||||
return new_xyz, new_points
|
||||
|
||||
|
||||
class PointNet_FP_Module(nn.Module):
|
||||
def __init__(self, in_channel, mlp, use_points1=False, in_channel_points1=None, if_bn=True):
|
||||
"""
|
||||
Args:
|
||||
in_channel: int, input channel of points2
|
||||
mlp: list of int
|
||||
use_points1: boolean, if use points
|
||||
in_channel_points1: int, input channel of points1
|
||||
"""
|
||||
super(PointNet_FP_Module, self).__init__()
|
||||
self.use_points1 = use_points1
|
||||
|
||||
if use_points1:
|
||||
in_channel += in_channel_points1
|
||||
|
||||
last_channel = in_channel
|
||||
self.mlp_conv = []
|
||||
for out_channel in mlp:
|
||||
self.mlp_conv.append(Conv1d(last_channel, out_channel, if_bn=if_bn))
|
||||
last_channel = out_channel
|
||||
|
||||
self.mlp_conv = nn.Sequential(*self.mlp_conv)
|
||||
|
||||
def forward(self, xyz1, xyz2, points1, points2):
|
||||
"""
|
||||
Args:
|
||||
xyz1: Tensor, (B, 3, N)
|
||||
xyz2: Tensor, (B, 3, M)
|
||||
points1: Tensor, (B, in_channel, N)
|
||||
points2: Tensor, (B, in_channel, M)
|
||||
|
||||
Returns:MLP_CONV
|
||||
new_points: Tensor, (B, mlp[-1], N)
|
||||
"""
|
||||
dist, idx = three_nn(xyz1.permute(0, 2, 1).contiguous(), xyz2.permute(0, 2, 1).contiguous())
|
||||
dist = torch.clamp_min(dist, 1e-10) # (B, N, 3)
|
||||
recip_dist = 1.0/dist
|
||||
norm = torch.sum(recip_dist, 2, keepdim=True).repeat((1, 1, 3))
|
||||
weight = recip_dist / norm
|
||||
interpolated_points = three_interpolate(points2, idx, weight) # B, in_channel, N
|
||||
|
||||
if self.use_points1:
|
||||
new_points = torch.cat([interpolated_points, points1], 1)
|
||||
else:
|
||||
new_points = interpolated_points
|
||||
|
||||
new_points = self.mlp_conv(new_points)
|
||||
return new_points
|
||||
|
||||
|
||||
def square_distance(src, dst):
|
||||
"""
|
||||
Calculate Euclid distance between each two points.
|
||||
|
||||
src^T * dst = xn * xm + yn * ym + zn * zm;
|
||||
sum(src^2, dim=-1) = xn*xn + yn*yn + zn*zn;
|
||||
sum(dst^2, dim=-1) = xm*xm + ym*ym + zm*zm;
|
||||
dist = (xn - xm)^2 + (yn - ym)^2 + (zn - zm)^2
|
||||
= sum(src**2,dim=-1)+sum(dst**2,dim=-1)-2*src^T*dst
|
||||
|
||||
Input:
|
||||
src: source points, [B, N, C]
|
||||
dst: target points, [B, M, C]
|
||||
Output:
|
||||
dist: per-point square distance, [B, N, M]
|
||||
"""
|
||||
B, N, _ = src.shape
|
||||
_, M, _ = dst.shape
|
||||
dist = -2 * torch.matmul(src, dst.permute(0, 2, 1)) # B, N, M
|
||||
dist += torch.sum(src ** 2, -1).view(B, N, 1)
|
||||
dist += torch.sum(dst ** 2, -1).view(B, 1, M)
|
||||
return dist
|
||||
|
||||
|
||||
def query_knn(nsample, xyz, new_xyz, include_self=True):
|
||||
"""Find k-NN of new_xyz in xyz"""
|
||||
pad = 0 if include_self else 1
|
||||
sqrdists = square_distance(new_xyz, xyz) # B, S, N
|
||||
idx = torch.argsort(sqrdists, dim=-1, descending=False)[:, :, pad: nsample+pad]
|
||||
return idx.int()
|
||||
|
||||
|
||||
def sample_and_group_knn(xyz, points, npoint, k, use_xyz=True, idx=None):
|
||||
"""
|
||||
Args:
|
||||
xyz: Tensor, (B, 3, N)
|
||||
points: Tensor, (B, f, N)
|
||||
npoint: int
|
||||
nsample: int
|
||||
radius: float
|
||||
use_xyz: boolean
|
||||
|
||||
Returns:
|
||||
new_xyz: Tensor, (B, 3, npoint)
|
||||
new_points: Tensor, (B, 3 | f+3 | f, npoint, nsample)
|
||||
idx_local: Tensor, (B, npoint, nsample)
|
||||
grouped_xyz: Tensor, (B, 3, npoint, nsample)
|
||||
|
||||
"""
|
||||
xyz_flipped = xyz.permute(0, 2, 1).contiguous() # (B, N, 3)
|
||||
new_xyz = gather_operation(xyz, furthest_point_sample(xyz_flipped, npoint)) # (B, 3, npoint)
|
||||
if idx is None:
|
||||
idx = query_knn(k, xyz_flipped, new_xyz.permute(0, 2, 1).contiguous())
|
||||
grouped_xyz = grouping_operation(xyz, idx) # (B, 3, npoint, nsample)
|
||||
grouped_xyz -= new_xyz.unsqueeze(3).repeat(1, 1, 1, k)
|
||||
|
||||
if points is not None:
|
||||
grouped_points = grouping_operation(points, idx) # (B, f, npoint, nsample)
|
||||
if use_xyz:
|
||||
new_points = torch.cat([grouped_xyz, grouped_points], 1)
|
||||
else:
|
||||
new_points = grouped_points
|
||||
else:
|
||||
new_points = grouped_xyz
|
||||
|
||||
return new_xyz, new_points, idx, grouped_xyz
|
||||
|
||||
|
||||
class PointNet_SA_Module_KNN(nn.Module):
|
||||
def __init__(self, npoint, nsample, in_channel, mlp, if_bn=True, group_all=False, use_xyz=True, if_idx=False):
|
||||
"""
|
||||
Args:
|
||||
npoint: int, number of points to sample
|
||||
nsample: int, number of points in each local region
|
||||
radius: float
|
||||
in_channel: int, input channel of features(points)
|
||||
mlp: list of int,
|
||||
"""
|
||||
super(PointNet_SA_Module_KNN, self).__init__()
|
||||
self.npoint = npoint
|
||||
self.nsample = nsample
|
||||
self.mlp = mlp
|
||||
self.group_all = group_all
|
||||
self.use_xyz = use_xyz
|
||||
self.if_idx = if_idx
|
||||
if use_xyz:
|
||||
in_channel += 3
|
||||
|
||||
last_channel = in_channel
|
||||
self.mlp_conv = []
|
||||
for out_channel in mlp[:-1]:
|
||||
self.mlp_conv.append(Conv2d(last_channel, out_channel, if_bn=if_bn))
|
||||
last_channel = out_channel
|
||||
self.mlp_conv.append(Conv2d(last_channel, mlp[-1], if_bn=False, activation_fn=None))
|
||||
self.mlp_conv = nn.Sequential(*self.mlp_conv)
|
||||
|
||||
def forward(self, xyz, points, idx=None):
|
||||
"""
|
||||
Args:
|
||||
xyz: Tensor, (B, 3, N)
|
||||
points: Tensor, (B, f, N)
|
||||
|
||||
Returns:
|
||||
new_xyz: Tensor, (B, 3, npoint)
|
||||
new_points: Tensor, (B, mlp[-1], npoint)
|
||||
"""
|
||||
if self.group_all:
|
||||
new_xyz, new_points, idx, grouped_xyz = sample_and_group_all(xyz, points, self.use_xyz)
|
||||
else:
|
||||
new_xyz, new_points, idx, grouped_xyz = sample_and_group_knn(xyz, points, self.npoint, self.nsample, self.use_xyz, idx=idx)
|
||||
|
||||
new_points = self.mlp_conv(new_points)
|
||||
new_points = torch.max(new_points, 3)[0]
|
||||
|
||||
if self.if_idx:
|
||||
return new_xyz, new_points, idx
|
||||
else:
|
||||
return new_xyz, new_points
|
||||
|
||||
|
||||
def fps_subsample(pcd, n_points=2048):
|
||||
"""
|
||||
Args
|
||||
pcd: (b, 16384, 3)
|
||||
|
||||
returns
|
||||
new_pcd: (b, n_points, 3)
|
||||
"""
|
||||
new_pcd = gather_operation(pcd.permute(0, 2, 1).contiguous(), furthest_point_sample(pcd, n_points))
|
||||
new_pcd = new_pcd.permute(0, 2, 1).contiguous()
|
||||
return new_pcd
|
||||
|
||||
|
||||
class Transformer(nn.Module):
|
||||
def __init__(self, in_channel, dim=256, n_knn=16, pos_hidden_dim=64, attn_hidden_multiplier=4):
|
||||
super(Transformer, self).__init__()
|
||||
self.n_knn = n_knn
|
||||
self.conv_key = nn.Conv1d(dim, dim, 1)
|
||||
self.conv_query = nn.Conv1d(dim, dim, 1)
|
||||
self.conv_value = nn.Conv1d(dim, dim, 1)
|
||||
|
||||
self.pos_mlp = nn.Sequential(
|
||||
nn.Conv2d(3, pos_hidden_dim, 1),
|
||||
nn.BatchNorm2d(pos_hidden_dim),
|
||||
nn.ReLU(),
|
||||
nn.Conv2d(pos_hidden_dim, dim, 1)
|
||||
)
|
||||
|
||||
self.attn_mlp = nn.Sequential(
|
||||
nn.Conv2d(dim, dim * attn_hidden_multiplier, 1),
|
||||
nn.BatchNorm2d(dim * attn_hidden_multiplier),
|
||||
nn.ReLU(),
|
||||
nn.Conv2d(dim * attn_hidden_multiplier, dim, 1)
|
||||
)
|
||||
|
||||
self.linear_start = nn.Conv1d(in_channel, dim, 1)
|
||||
self.linear_end = nn.Conv1d(dim, in_channel, 1)
|
||||
|
||||
def forward(self, x, pos):
|
||||
"""feed forward of transformer
|
||||
Args:
|
||||
x: Tensor of features, (B, in_channel, n)
|
||||
pos: Tensor of positions, (B, 3, n)
|
||||
|
||||
Returns:
|
||||
y: Tensor of features with attention, (B, in_channel, n)
|
||||
"""
|
||||
|
||||
identity = x
|
||||
|
||||
x = self.linear_start(x)
|
||||
b, dim, n = x.shape
|
||||
|
||||
pos_flipped = pos.permute(0, 2, 1).contiguous()
|
||||
idx_knn = query_knn(self.n_knn, pos_flipped, pos_flipped)
|
||||
key = self.conv_key(x)
|
||||
value = self.conv_value(x)
|
||||
query = self.conv_query(x)
|
||||
|
||||
key = grouping_operation(key, idx_knn) # b, dim, n, n_knn
|
||||
qk_rel = query.reshape((b, -1, n, 1)) - key
|
||||
|
||||
pos_rel = pos.reshape((b, -1, n, 1)) - grouping_operation(pos, idx_knn) # b, 3, n, n_knn
|
||||
pos_embedding = self.pos_mlp(pos_rel) # b, dim, n, n_knn
|
||||
|
||||
attention = self.attn_mlp(qk_rel + pos_embedding)
|
||||
attention = torch.softmax(attention, -1)
|
||||
|
||||
value = value.reshape((b, -1, n, 1)) + pos_embedding
|
||||
|
||||
agg = einsum('b c i j, b c i j -> b c i', attention, value) # b, dim, n
|
||||
y = self.linear_end(agg)
|
||||
|
||||
return y+identity
|
||||
|
||||
|
||||
class CouplingLayer(nn.Module):
|
||||
|
||||
def __init__(self, d, intermediate_dim, swap=False):
|
||||
nn.Module.__init__(self)
|
||||
self.d = d - (d // 2)
|
||||
self.swap = swap
|
||||
self.net_s_t = nn.Sequential(
|
||||
nn.Linear(self.d, intermediate_dim),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Linear(intermediate_dim, intermediate_dim),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Linear(intermediate_dim, (d - self.d) * 2),
|
||||
)
|
||||
|
||||
def forward(self, x, logpx=None, reverse=False):
|
||||
|
||||
if self.swap:
|
||||
x = torch.cat([x[:, self.d:], x[:, :self.d]], 1)
|
||||
|
||||
in_dim = self.d
|
||||
out_dim = x.shape[1] - self.d
|
||||
|
||||
s_t = self.net_s_t(x[:, :in_dim])
|
||||
scale = torch.sigmoid(s_t[:, :out_dim] + 2.)
|
||||
shift = s_t[:, out_dim:]
|
||||
|
||||
logdetjac = torch.sum(torch.log(scale).view(scale.shape[0], -1), 1, keepdim=True)
|
||||
|
||||
if not reverse:
|
||||
y1 = x[:, self.d:] * scale + shift
|
||||
delta_logp = -logdetjac
|
||||
else:
|
||||
y1 = (x[:, self.d:] - shift) / scale
|
||||
delta_logp = logdetjac
|
||||
|
||||
y = torch.cat([x[:, :self.d], y1], 1) if not self.swap else torch.cat([y1, x[:, :self.d]], 1)
|
||||
|
||||
if logpx is None:
|
||||
return y
|
||||
else:
|
||||
return y, logpx + delta_logp
|
||||
|
||||
|
||||
class SequentialFlow(nn.Module):
|
||||
"""A generalized nn.Sequential container for normalizing flows.
|
||||
"""
|
||||
|
||||
def __init__(self, layersList):
|
||||
super(SequentialFlow, self).__init__()
|
||||
self.chain = nn.ModuleList(layersList)
|
||||
|
||||
def forward(self, x, logpx=None, reverse=False, inds=None):
|
||||
if inds is None:
|
||||
if reverse:
|
||||
inds = range(len(self.chain) - 1, -1, -1)
|
||||
else:
|
||||
inds = range(len(self.chain))
|
||||
|
||||
if logpx is None:
|
||||
for i in inds:
|
||||
x = self.chain[i](x, reverse=reverse)
|
||||
return x
|
||||
else:
|
||||
for i in inds:
|
||||
x, logpx = self.chain[i](x, logpx, reverse=reverse)
|
||||
return x, logpx
|
||||
|
||||
|
||||
def build_latent_flow(args):
|
||||
chain = []
|
||||
for i in range(args.latent_flow_depth):
|
||||
chain.append(CouplingLayer(args.latent_dim, args.latent_flow_hidden_dim, swap=(i % 2 == 0)))
|
||||
return SequentialFlow(chain)
|
||||
|
||||
|
||||
##################
|
||||
## SpectralNorm ##
|
||||
##################
|
||||
|
||||
POWER_ITERATION_FN = "spectral_norm_power_iteration"
|
||||
|
||||
|
||||
class SpectralNorm(object):
|
||||
def __init__(self, name='weight', dim=0, eps=1e-12):
|
||||
self.name = name
|
||||
self.dim = dim
|
||||
self.eps = eps
|
||||
|
||||
def compute_weight(self, module, n_power_iterations):
|
||||
if n_power_iterations < 0:
|
||||
raise ValueError(
|
||||
'Expected n_power_iterations to be non-negative, but '
|
||||
'got n_power_iterations={}'.format(n_power_iterations)
|
||||
)
|
||||
|
||||
weight = getattr(module, self.name + '_orig')
|
||||
u = getattr(module, self.name + '_u')
|
||||
v = getattr(module, self.name + '_v')
|
||||
weight_mat = weight
|
||||
if self.dim != 0:
|
||||
# permute dim to front
|
||||
weight_mat = weight_mat.permute(self.dim, * [d for d in range(weight_mat.dim()) if d != self.dim])
|
||||
height = weight_mat.size(0)
|
||||
weight_mat = weight_mat.reshape(height, -1)
|
||||
with torch.no_grad():
|
||||
for _ in range(n_power_iterations):
|
||||
# Spectral norm of weight equals to `u^T W v`, where `u` and `v`
|
||||
# are the first left and right singular vectors.
|
||||
# This power iteration produces approximations of `u` and `v`.
|
||||
v = F.normalize(torch.matmul(weight_mat.t(), u), dim=0, eps=self.eps)
|
||||
u = F.normalize(torch.matmul(weight_mat, v), dim=0, eps=self.eps)
|
||||
setattr(module, self.name + '_u', u)
|
||||
setattr(module, self.name + '_v', v)
|
||||
|
||||
sigma = torch.dot(u, torch.matmul(weight_mat, v))
|
||||
weight = weight / sigma
|
||||
setattr(module, self.name, weight)
|
||||
|
||||
def remove(self, module):
|
||||
weight = getattr(module, self.name)
|
||||
delattr(module, self.name)
|
||||
delattr(module, self.name + '_u')
|
||||
delattr(module, self.name + '_orig')
|
||||
module.register_parameter(self.name, torch.nn.Parameter(weight))
|
||||
|
||||
def get_update_method(self, module):
|
||||
def update_fn(module, n_power_iterations):
|
||||
self.compute_weight(module, n_power_iterations)
|
||||
|
||||
return update_fn
|
||||
|
||||
def __call__(self, module, unused_inputs):
|
||||
del unused_inputs
|
||||
self.compute_weight(module, n_power_iterations=0)
|
||||
|
||||
# requires_grad might be either True or False during inference.
|
||||
if not module.training:
|
||||
r_g = getattr(module, self.name + '_orig').requires_grad
|
||||
setattr(module, self.name, getattr(module, self.name).detach().requires_grad_(r_g))
|
||||
|
||||
@staticmethod
|
||||
def apply(module, name, dim, eps):
|
||||
fn = SpectralNorm(name, dim, eps)
|
||||
weight = module._parameters[name]
|
||||
height = weight.size(dim)
|
||||
|
||||
u = F.normalize(weight.new_empty(height).normal_(0, 1), dim=0, eps=fn.eps)
|
||||
v = F.normalize(weight.new_empty(int(weight.numel() / height)).normal_(0, 1), dim=0, eps=fn.eps)
|
||||
delattr(module, fn.name)
|
||||
module.register_parameter(fn.name + "_orig", weight)
|
||||
# We still need to assign weight back as fn.name because all sorts of
|
||||
# things may assume that it exists, e.g., when initializing weights.
|
||||
# However, we can't directly assign as it could be an nn.Parameter and
|
||||
# gets added as a parameter. Instead, we register weight.data as a
|
||||
# buffer, which will cause weight to be included in the state dict
|
||||
# and also supports nn.init due to shared storage.
|
||||
module.register_buffer(fn.name, weight.data)
|
||||
module.register_buffer(fn.name + "_u", u)
|
||||
module.register_buffer(fn.name + "_v", v)
|
||||
|
||||
setattr(module, POWER_ITERATION_FN, types.MethodType(fn.get_update_method(module), module))
|
||||
|
||||
module.register_forward_pre_hook(fn)
|
||||
return fn
|
||||
|
||||
|
||||
def inplace_spectral_norm(module, name='weight', dim=None, eps=1e-12):
|
||||
r"""Applies spectral normalization to a parameter in the given module.
|
||||
.. math::
|
||||
\mathbf{W} = \dfrac{\mathbf{W}}{\sigma(\mathbf{W})} \\
|
||||
\sigma(\mathbf{W}) = \max_{\mathbf{h}: \mathbf{h} \ne 0} \dfrac{\|\mathbf{W} \mathbf{h}\|_2}{\|\mathbf{h}\|_2}
|
||||
Spectral normalization stabilizes the training of discriminators (critics)
|
||||
in Generaive Adversarial Networks (GANs) by rescaling the weight tensor
|
||||
with spectral norm :math:`\sigma` of the weight matrix calculated using
|
||||
power iteration method. If the dimension of the weight tensor is greater
|
||||
than 2, it is reshaped to 2D in power iteration method to get spectral
|
||||
norm. This is implemented via a hook that calculates spectral norm and
|
||||
rescales weight before every :meth:`~Module.forward` call.
|
||||
See `Spectral Normalization for Generative Adversarial Networks`_ .
|
||||
.. _`Spectral Normalization for Generative Adversarial Networks`: https://arxiv.org/abs/1802.05957
|
||||
Args:
|
||||
module (nn.Module): containing module
|
||||
name (str, optional): name of weight parameter
|
||||
n_power_iterations (int, optional): number of power iterations to
|
||||
calculate spectal norm
|
||||
dim (int, optional): dimension corresponding to number of outputs,
|
||||
the default is 0, except for modules that are instances of
|
||||
ConvTranspose1/2/3d, when it is 1
|
||||
eps (float, optional): epsilon for numerical stability in
|
||||
calculating norms
|
||||
Returns:
|
||||
The original module with the spectal norm hook
|
||||
Example::
|
||||
>>> m = spectral_norm(nn.Linear(20, 40))
|
||||
Linear (20 -> 40)
|
||||
>>> m.weight_u.size()
|
||||
torch.Size([20])
|
||||
"""
|
||||
if dim is None:
|
||||
if isinstance(module, (torch.nn.ConvTranspose1d, torch.nn.ConvTranspose2d, torch.nn.ConvTranspose3d)):
|
||||
dim = 1
|
||||
else:
|
||||
dim = 0
|
||||
SpectralNorm.apply(module, name, dim=dim, eps=eps)
|
||||
return module
|
||||
|
||||
|
||||
def remove_spectral_norm(module, name='weight'):
|
||||
r"""Removes the spectral normalization reparameterization from a module.
|
||||
Args:
|
||||
module (nn.Module): containing module
|
||||
name (str, optional): name of weight parameter
|
||||
Example:
|
||||
>>> m = spectral_norm(nn.Linear(40, 10))
|
||||
>>> remove_spectral_norm(m)
|
||||
"""
|
||||
for k, hook in module._forward_pre_hooks.items():
|
||||
if isinstance(hook, SpectralNorm) and hook.name == name:
|
||||
hook.remove(module)
|
||||
del module._forward_pre_hooks[k]
|
||||
return module
|
||||
|
||||
raise ValueError("spectral_norm of '{}' not found in {}".format(name, module))
|
||||
|
||||
|
||||
def add_spectral_norm(model, logger=None):
|
||||
"""Applies spectral norm to all modules within the scope of a CNF."""
|
||||
|
||||
def apply_spectral_norm(module):
|
||||
if 'weight' in module._parameters:
|
||||
if logger: logger.info("Adding spectral norm to {}".format(module))
|
||||
inplace_spectral_norm(module, 'weight')
|
||||
|
||||
def find_coupling_layer(module):
|
||||
if isinstance(module, CouplingLayer):
|
||||
module.apply(apply_spectral_norm)
|
||||
else:
|
||||
for child in module.children():
|
||||
find_coupling_layer(child)
|
||||
|
||||
find_coupling_layer(model)
|
||||
|
||||
|
||||
def spectral_norm_power_iteration(model, n_power_iterations=1):
|
||||
|
||||
def recursive_power_iteration(module):
|
||||
if hasattr(module, POWER_ITERATION_FN):
|
||||
getattr(module, POWER_ITERATION_FN)(n_power_iterations)
|
||||
|
||||
model.apply(recursive_power_iteration)
|
||||
|
||||
def reparameterize_gaussian(mean, logvar):
|
||||
std = torch.exp(0.5 * logvar)
|
||||
eps = torch.randn(std.size()).to(mean)
|
||||
return mean + std * eps
|
||||
|
||||
|
||||
|
||||
def gaussian_entropy(logvar):
|
||||
const = 0.5 * float(logvar.size(1)) * (1. + np.log(np.pi * 2))
|
||||
ent = 0.5 * logvar.sum(dim=1, keepdim=False) + const
|
||||
return ent
|
||||
|
||||
|
||||
def standard_normal_logprob(z):
|
||||
dim = z.size(-1)
|
||||
log_z = -0.5 * dim * np.log(2 * np.pi)
|
||||
return log_z - z.pow(2) / 2
|
||||
|
||||
def truncated_normal_(tensor, mean=0, std=1, trunc_std=2):
|
||||
"""
|
||||
Taken from https://discuss.pytorch.org/t/implementing-truncated-normal-initializer/4778/15
|
||||
"""
|
||||
size = tensor.shape
|
||||
tmp = tensor.new_empty(size + (4,)).normal_()
|
||||
valid = (tmp < trunc_std) & (tmp > -trunc_std)
|
||||
ind = valid.max(-1, keepdim=True)[1]
|
||||
tensor.data.copy_(tmp.gather(-1, ind).squeeze(-1))
|
||||
tensor.data.mul_(std).add_(mean)
|
||||
return tensor
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,135 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from einops import rearrange
|
||||
|
||||
from tgs.utils.base import BaseModule
|
||||
from tgs.models.tokenizers.dinov2 import Dinov2Model
|
||||
from tgs.models.transformers import Modulation
|
||||
from tgs.utils.typing import *
|
||||
|
||||
class DINOV2SingleImageTokenizer(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
pretrained_model_name_or_path: str = "facebook/dinov2-base"
|
||||
width: int = 224
|
||||
height: int = 224
|
||||
modulation: bool = False
|
||||
modulation_zero_init: bool = False
|
||||
modulation_single_layer: bool = False
|
||||
modulation_cond_dim: int = 16
|
||||
freeze_backbone_params: bool = True
|
||||
enable_memory_efficient_attention: bool = False
|
||||
enable_gradient_checkpointing: bool = False
|
||||
use_patch_embeddings: bool = False
|
||||
patch_embeddings_aggr_method: str = 'concat'
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
model: Dinov2Model
|
||||
|
||||
if self.cfg.freeze_backbone_params:
|
||||
# freeze dino backbone parameters
|
||||
self.register_non_module(
|
||||
"model",
|
||||
Dinov2Model.from_pretrained(self.cfg.pretrained_model_name_or_path).to(
|
||||
self.device
|
||||
),
|
||||
)
|
||||
|
||||
model = self.non_module("model")
|
||||
for p in model.parameters():
|
||||
p.requires_grad_(False)
|
||||
model.eval()
|
||||
else:
|
||||
self.model = Dinov2Model.from_pretrained(
|
||||
self.cfg.pretrained_model_name_or_path
|
||||
).to(self.device)
|
||||
model = self.model
|
||||
|
||||
model.set_use_memory_efficient_attention_xformers(
|
||||
self.cfg.enable_memory_efficient_attention
|
||||
)
|
||||
model.set_gradient_checkpointing(self.cfg.enable_gradient_checkpointing)
|
||||
|
||||
# add modulation
|
||||
if self.cfg.modulation:
|
||||
modulations = []
|
||||
for layer in model.encoder.layer:
|
||||
norm1_modulation = Modulation(
|
||||
model.config.hidden_size,
|
||||
self.cfg.modulation_cond_dim,
|
||||
zero_init=self.cfg.modulation_zero_init,
|
||||
single_layer=self.cfg.modulation_single_layer,
|
||||
)
|
||||
norm2_modulation = Modulation(
|
||||
model.config.hidden_size,
|
||||
self.cfg.modulation_cond_dim,
|
||||
zero_init=self.cfg.modulation_zero_init,
|
||||
single_layer=self.cfg.modulation_single_layer,
|
||||
)
|
||||
layer.register_ada_norm_modulation(norm1_modulation, norm2_modulation)
|
||||
modulations += [norm1_modulation, norm2_modulation]
|
||||
self.modulations = nn.ModuleList(modulations)
|
||||
|
||||
self.register_buffer(
|
||||
"image_mean",
|
||||
torch.as_tensor([0.485, 0.456, 0.406]).reshape(1, 1, 3, 1, 1),
|
||||
persistent=False,
|
||||
)
|
||||
self.register_buffer(
|
||||
"image_std",
|
||||
torch.as_tensor([0.229, 0.224, 0.225]).reshape(1, 1, 3, 1, 1),
|
||||
persistent=False,
|
||||
)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
images: Float[Tensor, "B *N C H W"],
|
||||
modulation_cond: Optional[Float[Tensor, "B *N Cc"]],
|
||||
) -> Float[Tensor, "B *N Ct Nt"]:
|
||||
model: Dinov2Model
|
||||
if self.cfg.freeze_backbone_params:
|
||||
model = self.non_module("model")
|
||||
else:
|
||||
model = self.model
|
||||
|
||||
packed = False
|
||||
if images.ndim == 4:
|
||||
packed = True
|
||||
images = images.unsqueeze(1)
|
||||
if modulation_cond is not None:
|
||||
assert modulation_cond.ndim == 2
|
||||
modulation_cond = modulation_cond.unsqueeze(1)
|
||||
|
||||
batch_size, n_input_views = images.shape[:2]
|
||||
images = (images - self.image_mean) / self.image_std
|
||||
out = model(
|
||||
rearrange(images, "B N C H W -> (B N) C H W"),
|
||||
modulation_cond=rearrange(modulation_cond, "B N Cc -> (B N) Cc")
|
||||
if modulation_cond is not None
|
||||
else None,
|
||||
)
|
||||
local_features, global_features = out.last_hidden_state, out.pooler_output
|
||||
if self.cfg.use_patch_embeddings:
|
||||
patch_embeddings = out.patch_embeddings
|
||||
if self.cfg.patch_embeddings_aggr_method == 'concat':
|
||||
local_features = torch.cat([local_features, patch_embeddings], dim=1)
|
||||
elif self.cfg.patch_embeddings_aggr_method == 'add':
|
||||
local_features = local_features + patch_embeddings
|
||||
else:
|
||||
raise NotImplementedError
|
||||
local_features = local_features.permute(0, 2, 1)
|
||||
local_features = rearrange(
|
||||
local_features, "(B N) Ct Nt -> B N Ct Nt", B=batch_size
|
||||
)
|
||||
if packed:
|
||||
local_features = local_features.squeeze(1)
|
||||
|
||||
return local_features
|
||||
|
||||
def detokenize(self, *args, **kwargs):
|
||||
raise NotImplementedError
|
||||
@@ -0,0 +1,29 @@
|
||||
from dataclasses import dataclass
|
||||
import torch.nn as nn
|
||||
from tgs.utils.base import BaseModule
|
||||
from tgs.utils.typing import *
|
||||
import torch
|
||||
|
||||
class PointLearnablePositionalEmbedding(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
num_pcl: int = 2048
|
||||
num_channels: int = 512
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
self.pcl_embeddings = nn.Embedding(
|
||||
self.cfg.num_pcl , self.cfg.num_channels
|
||||
)
|
||||
|
||||
def forward(self, batch_size: int) -> Float[Tensor, "B Ct Nt"]:
|
||||
range_ = torch.arange(self.cfg.num_pcl, device=self.device)
|
||||
embeddings = self.pcl_embeddings(range_).unsqueeze(0).repeat((batch_size,1,1))
|
||||
return torch.permute(embeddings, (0,2,1))
|
||||
|
||||
def detokenize(
|
||||
self, tokens: Float[Tensor, "B Ct Nt"]
|
||||
) -> Float[Tensor, "B 3 Ct Hp Wp"]:
|
||||
return torch.permute(tokens, (0,2,1))
|
||||
@@ -0,0 +1,52 @@
|
||||
from dataclasses import dataclass
|
||||
import math
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from einops import rearrange, repeat
|
||||
|
||||
from tgs.utils.base import BaseModule
|
||||
from tgs.utils.typing import *
|
||||
|
||||
|
||||
class TriplaneLearnablePositionalEmbedding(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
plane_size: int = 32
|
||||
num_channels: int = 1024
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
self.embeddings = nn.Parameter(
|
||||
torch.randn(
|
||||
(3, self.cfg.num_channels, self.cfg.plane_size, self.cfg.plane_size),
|
||||
dtype=torch.float32,
|
||||
)
|
||||
* 1
|
||||
/ math.sqrt(self.cfg.num_channels)
|
||||
)
|
||||
|
||||
def forward(self, batch_size: int, cond_embeddings: Float[Tensor, "B Ct"] = None) -> Float[Tensor, "B Ct Nt"]:
|
||||
embeddings = repeat(self.embeddings, "Np Ct Hp Wp -> B Np Ct Hp Wp", B=batch_size)
|
||||
if cond_embeddings is not None:
|
||||
embeddings = embeddings + cond_embeddings
|
||||
return rearrange(
|
||||
embeddings,
|
||||
"B Np Ct Hp Wp -> B Ct (Np Hp Wp)",
|
||||
)
|
||||
|
||||
def detokenize(
|
||||
self, tokens: Float[Tensor, "B Ct Nt"]
|
||||
) -> Float[Tensor, "B 3 Ct Hp Wp"]:
|
||||
batch_size, Ct, Nt = tokens.shape
|
||||
assert Nt == self.cfg.plane_size**2 * 3
|
||||
assert Ct == self.cfg.num_channels
|
||||
return rearrange(
|
||||
tokens,
|
||||
"B Ct (Np Hp Wp) -> B Np Ct Hp Wp",
|
||||
Np=3,
|
||||
Hp=self.cfg.plane_size,
|
||||
Wp=self.cfg.plane_size,
|
||||
)
|
||||
@@ -0,0 +1,908 @@
|
||||
# Copyright 2023 The HuggingFace Team. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torch import nn
|
||||
|
||||
from diffusers.utils.torch_utils import maybe_allow_in_graph
|
||||
from diffusers.models.activations import get_activation
|
||||
from diffusers.models.attention_processor import Attention
|
||||
from diffusers.models.embeddings import CombinedTimestepLabelEmbeddings
|
||||
|
||||
from dataclasses import dataclass
|
||||
from ..utils.base import BaseModule
|
||||
from ..utils.typing import *
|
||||
|
||||
|
||||
class MemoryEfficientAttentionMixin:
|
||||
def enable_xformers_memory_efficient_attention(
|
||||
self, attention_op: Optional[Callable] = None
|
||||
):
|
||||
r"""
|
||||
Enable memory efficient attention from [xFormers](https://facebookresearch.github.io/xformers/). When this
|
||||
option is enabled, you should observe lower GPU memory usage and a potential speed up during inference. Speed
|
||||
up during training is not guaranteed.
|
||||
|
||||
<Tip warning={true}>
|
||||
|
||||
⚠️ When memory efficient attention and sliced attention are both enabled, memory efficient attention takes
|
||||
precedent.
|
||||
|
||||
</Tip>
|
||||
|
||||
Parameters:
|
||||
attention_op (`Callable`, *optional*):
|
||||
Override the default `None` operator for use as `op` argument to the
|
||||
[`memory_efficient_attention()`](https://facebookresearch.github.io/xformers/components/ops.html#xformers.ops.memory_efficient_attention)
|
||||
function of xFormers.
|
||||
|
||||
Examples:
|
||||
|
||||
```py
|
||||
>>> import torch
|
||||
>>> from diffusers import DiffusionPipeline
|
||||
>>> from xformers.ops import MemoryEfficientAttentionFlashAttentionOp
|
||||
|
||||
>>> pipe = DiffusionPipeline.from_pretrained("stabilityai/stable-diffusion-2-1", torch_dtype=torch.float16)
|
||||
>>> pipe = pipe.to("cuda")
|
||||
>>> pipe.enable_xformers_memory_efficient_attention(attention_op=MemoryEfficientAttentionFlashAttentionOp)
|
||||
>>> # Workaround for not accepting attention shape using VAE for Flash Attention
|
||||
>>> pipe.vae.enable_xformers_memory_efficient_attention(attention_op=None)
|
||||
```
|
||||
"""
|
||||
self.set_use_memory_efficient_attention_xformers(True, attention_op)
|
||||
|
||||
def disable_xformers_memory_efficient_attention(self):
|
||||
r"""
|
||||
Disable memory efficient attention from [xFormers](https://facebookresearch.github.io/xformers/).
|
||||
"""
|
||||
self.set_use_memory_efficient_attention_xformers(False)
|
||||
|
||||
def set_use_memory_efficient_attention_xformers(
|
||||
self, valid: bool, attention_op: Optional[Callable] = None
|
||||
) -> None:
|
||||
# Recursively walk through all the children.
|
||||
# Any children which exposes the set_use_memory_efficient_attention_xformers method
|
||||
# gets the message
|
||||
def fn_recursive_set_mem_eff(module: torch.nn.Module):
|
||||
if hasattr(module, "set_use_memory_efficient_attention_xformers"):
|
||||
module.set_use_memory_efficient_attention_xformers(valid, attention_op)
|
||||
|
||||
for child in module.children():
|
||||
fn_recursive_set_mem_eff(child)
|
||||
|
||||
for module in self.children():
|
||||
if isinstance(module, torch.nn.Module):
|
||||
fn_recursive_set_mem_eff(module)
|
||||
|
||||
|
||||
@maybe_allow_in_graph
|
||||
class GatedSelfAttentionDense(nn.Module):
|
||||
r"""
|
||||
A gated self-attention dense layer that combines visual features and object features.
|
||||
|
||||
Parameters:
|
||||
query_dim (`int`): The number of channels in the query.
|
||||
context_dim (`int`): The number of channels in the context.
|
||||
n_heads (`int`): The number of heads to use for attention.
|
||||
d_head (`int`): The number of channels in each head.
|
||||
"""
|
||||
|
||||
def __init__(self, query_dim: int, context_dim: int, n_heads: int, d_head: int):
|
||||
super().__init__()
|
||||
|
||||
# we need a linear projection since we need cat visual feature and obj feature
|
||||
self.linear = nn.Linear(context_dim, query_dim)
|
||||
|
||||
self.attn = Attention(query_dim=query_dim, heads=n_heads, dim_head=d_head)
|
||||
self.ff = FeedForward(query_dim, activation_fn="geglu")
|
||||
|
||||
self.norm1 = nn.LayerNorm(query_dim)
|
||||
self.norm2 = nn.LayerNorm(query_dim)
|
||||
|
||||
self.register_parameter("alpha_attn", nn.Parameter(torch.tensor(0.0)))
|
||||
self.register_parameter("alpha_dense", nn.Parameter(torch.tensor(0.0)))
|
||||
|
||||
self.enabled = True
|
||||
|
||||
def forward(self, x: torch.Tensor, objs: torch.Tensor) -> torch.Tensor:
|
||||
if not self.enabled:
|
||||
return x
|
||||
|
||||
n_visual = x.shape[1]
|
||||
objs = self.linear(objs)
|
||||
|
||||
x = (
|
||||
x
|
||||
+ self.alpha_attn.tanh()
|
||||
* self.attn(self.norm1(torch.cat([x, objs], dim=1)))[:, :n_visual, :]
|
||||
)
|
||||
x = x + self.alpha_dense.tanh() * self.ff(self.norm2(x))
|
||||
|
||||
return x
|
||||
|
||||
|
||||
@maybe_allow_in_graph
|
||||
class BasicTransformerBlock(nn.Module, MemoryEfficientAttentionMixin):
|
||||
r"""
|
||||
A basic Transformer block.
|
||||
|
||||
Parameters:
|
||||
dim (`int`): The number of channels in the input and output.
|
||||
num_attention_heads (`int`): The number of heads to use for multi-head attention.
|
||||
attention_head_dim (`int`): The number of channels in each head.
|
||||
dropout (`float`, *optional*, defaults to 0.0): The dropout probability to use.
|
||||
cross_attention_dim (`int`, *optional*): The size of the encoder_hidden_states vector for cross attention.
|
||||
activation_fn (`str`, *optional*, defaults to `"geglu"`): Activation function to be used in feed-forward.
|
||||
num_embeds_ada_norm (:
|
||||
obj: `int`, *optional*): The number of diffusion steps used during training. See `Transformer2DModel`.
|
||||
attention_bias (:
|
||||
obj: `bool`, *optional*, defaults to `False`): Configure if the attentions should contain a bias parameter.
|
||||
only_cross_attention (`bool`, *optional*):
|
||||
Whether to use only cross-attention layers. In this case two cross attention layers are used.
|
||||
double_self_attention (`bool`, *optional*):
|
||||
Whether to use two self-attention layers. In this case no cross attention layers are used.
|
||||
upcast_attention (`bool`, *optional*):
|
||||
Whether to upcast the attention computation to float32. This is useful for mixed precision training.
|
||||
norm_elementwise_affine (`bool`, *optional*, defaults to `True`):
|
||||
Whether to use learnable elementwise affine parameters for normalization.
|
||||
norm_type (`str`, *optional*, defaults to `"layer_norm"`):
|
||||
The normalization layer to use. Can be `"layer_norm"`, `"ada_norm"` or `"ada_norm_zero"`.
|
||||
final_dropout (`bool` *optional*, defaults to False):
|
||||
Whether to apply a final dropout after the last feed-forward layer.
|
||||
attention_type (`str`, *optional*, defaults to `"default"`):
|
||||
The type of attention to use. Can be `"default"` or `"gated"` or `"gated-text-image"`.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dim: int,
|
||||
num_attention_heads: int,
|
||||
attention_head_dim: int,
|
||||
dropout=0.0,
|
||||
cross_attention_dim: Optional[int] = None,
|
||||
activation_fn: str = "geglu",
|
||||
num_embeds_ada_norm: Optional[int] = None,
|
||||
cond_dim_ada_norm_continuous: Optional[int] = None,
|
||||
attention_bias: bool = False,
|
||||
only_cross_attention: bool = False,
|
||||
double_self_attention: bool = False,
|
||||
upcast_attention: bool = False,
|
||||
norm_elementwise_affine: bool = True,
|
||||
norm_type: str = "layer_norm",
|
||||
final_dropout: bool = False,
|
||||
attention_type: str = "default",
|
||||
):
|
||||
super().__init__()
|
||||
self.only_cross_attention = only_cross_attention
|
||||
|
||||
self.use_ada_layer_norm_zero = (
|
||||
num_embeds_ada_norm is not None
|
||||
) and norm_type == "ada_norm_zero"
|
||||
self.use_ada_layer_norm = (
|
||||
num_embeds_ada_norm is not None
|
||||
) and norm_type == "ada_norm"
|
||||
self.use_ada_layer_norm_continuous = (
|
||||
cond_dim_ada_norm_continuous is not None
|
||||
) and norm_type == "ada_norm_continuous"
|
||||
|
||||
assert (
|
||||
int(self.use_ada_layer_norm)
|
||||
+ int(self.use_ada_layer_norm_continuous)
|
||||
+ int(self.use_ada_layer_norm_zero)
|
||||
<= 1
|
||||
)
|
||||
|
||||
if norm_type in ("ada_norm", "ada_norm_zero") and num_embeds_ada_norm is None:
|
||||
raise ValueError(
|
||||
f"`norm_type` is set to {norm_type}, but `num_embeds_ada_norm` is not defined. Please make sure to"
|
||||
f" define `num_embeds_ada_norm` if setting `norm_type` to {norm_type}."
|
||||
)
|
||||
|
||||
# Define 3 blocks. Each block has its own normalization layer.
|
||||
# 1. Self-Attn
|
||||
if self.use_ada_layer_norm:
|
||||
self.norm1 = AdaLayerNorm(dim, num_embeds_ada_norm)
|
||||
elif self.use_ada_layer_norm_continuous:
|
||||
self.norm1 = AdaLayerNormContinuous(dim, cond_dim_ada_norm_continuous)
|
||||
elif self.use_ada_layer_norm_zero:
|
||||
self.norm1 = AdaLayerNormZero(dim, num_embeds_ada_norm)
|
||||
else:
|
||||
self.norm1 = nn.LayerNorm(dim, elementwise_affine=norm_elementwise_affine)
|
||||
self.attn1 = Attention(
|
||||
query_dim=dim,
|
||||
heads=num_attention_heads,
|
||||
dim_head=attention_head_dim,
|
||||
dropout=dropout,
|
||||
bias=attention_bias,
|
||||
cross_attention_dim=cross_attention_dim if only_cross_attention else None,
|
||||
upcast_attention=upcast_attention,
|
||||
)
|
||||
|
||||
# 2. Cross-Attn
|
||||
if cross_attention_dim is not None or double_self_attention:
|
||||
# We currently only use AdaLayerNormZero for self attention where there will only be one attention block.
|
||||
# I.e. the number of returned modulation chunks from AdaLayerZero would not make sense if returned during
|
||||
# the second cross attention block.
|
||||
if self.use_ada_layer_norm:
|
||||
self.norm2 = AdaLayerNorm(dim, num_embeds_ada_norm)
|
||||
elif self.use_ada_layer_norm_continuous:
|
||||
self.norm2 = AdaLayerNormContinuous(dim, cond_dim_ada_norm_continuous)
|
||||
else:
|
||||
self.norm2 = nn.LayerNorm(
|
||||
dim, elementwise_affine=norm_elementwise_affine
|
||||
)
|
||||
|
||||
self.attn2 = Attention(
|
||||
query_dim=dim,
|
||||
cross_attention_dim=cross_attention_dim
|
||||
if not double_self_attention
|
||||
else None,
|
||||
heads=num_attention_heads,
|
||||
dim_head=attention_head_dim,
|
||||
dropout=dropout,
|
||||
bias=attention_bias,
|
||||
upcast_attention=upcast_attention,
|
||||
) # is self-attn if encoder_hidden_states is none
|
||||
else:
|
||||
self.norm2 = None
|
||||
self.attn2 = None
|
||||
|
||||
# 3. Feed-forward
|
||||
if self.use_ada_layer_norm_continuous:
|
||||
self.norm3 = AdaLayerNormContinuous(dim, cond_dim_ada_norm_continuous)
|
||||
else:
|
||||
self.norm3 = nn.LayerNorm(dim, elementwise_affine=norm_elementwise_affine)
|
||||
self.ff = FeedForward(
|
||||
dim,
|
||||
dropout=dropout,
|
||||
activation_fn=activation_fn,
|
||||
final_dropout=final_dropout,
|
||||
)
|
||||
|
||||
# 4. Fuser
|
||||
if attention_type == "gated" or attention_type == "gated-text-image":
|
||||
self.fuser = GatedSelfAttentionDense(
|
||||
dim, cross_attention_dim, num_attention_heads, attention_head_dim
|
||||
)
|
||||
|
||||
# let chunk size default to None
|
||||
self._chunk_size = None
|
||||
self._chunk_dim = 0
|
||||
|
||||
def set_chunk_feed_forward(self, chunk_size: Optional[int], dim: int):
|
||||
# Sets chunk feed-forward
|
||||
self._chunk_size = chunk_size
|
||||
self._chunk_dim = dim
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states: torch.FloatTensor,
|
||||
attention_mask: Optional[torch.FloatTensor] = None,
|
||||
encoder_hidden_states: Optional[torch.FloatTensor] = None,
|
||||
encoder_attention_mask: Optional[torch.FloatTensor] = None,
|
||||
timestep: Optional[torch.LongTensor] = None,
|
||||
modulation_cond: Optional[torch.FloatTensor] = None,
|
||||
cross_attention_kwargs: Dict[str, Any] = None,
|
||||
class_labels: Optional[torch.LongTensor] = None,
|
||||
) -> torch.FloatTensor:
|
||||
# Notice that normalization is always applied before the real computation in the following blocks.
|
||||
# 0. Self-Attention
|
||||
if self.use_ada_layer_norm:
|
||||
norm_hidden_states = self.norm1(hidden_states, timestep)
|
||||
elif self.use_ada_layer_norm_continuous:
|
||||
norm_hidden_states = self.norm1(hidden_states, modulation_cond)
|
||||
elif self.use_ada_layer_norm_zero:
|
||||
norm_hidden_states, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.norm1(
|
||||
hidden_states, timestep, class_labels, hidden_dtype=hidden_states.dtype
|
||||
)
|
||||
else:
|
||||
norm_hidden_states = self.norm1(hidden_states)
|
||||
|
||||
# 1. Retrieve lora scale.
|
||||
lora_scale = (
|
||||
cross_attention_kwargs.get("scale", 1.0)
|
||||
if cross_attention_kwargs is not None
|
||||
else 1.0
|
||||
)
|
||||
|
||||
# 2. Prepare GLIGEN inputs
|
||||
cross_attention_kwargs = (
|
||||
cross_attention_kwargs.copy() if cross_attention_kwargs is not None else {}
|
||||
)
|
||||
gligen_kwargs = cross_attention_kwargs.pop("gligen", None)
|
||||
|
||||
attn_output = self.attn1(
|
||||
norm_hidden_states,
|
||||
encoder_hidden_states=encoder_hidden_states
|
||||
if self.only_cross_attention
|
||||
else None,
|
||||
attention_mask=attention_mask,
|
||||
**cross_attention_kwargs,
|
||||
)
|
||||
if self.use_ada_layer_norm_zero:
|
||||
attn_output = gate_msa.unsqueeze(1) * attn_output
|
||||
hidden_states = attn_output + hidden_states
|
||||
|
||||
# 2.5 GLIGEN Control
|
||||
if gligen_kwargs is not None:
|
||||
hidden_states = self.fuser(hidden_states, gligen_kwargs["objs"])
|
||||
# 2.5 ends
|
||||
|
||||
# 3. Cross-Attention
|
||||
if self.attn2 is not None:
|
||||
if self.use_ada_layer_norm:
|
||||
norm_hidden_states = self.norm2(hidden_states, timestep)
|
||||
elif self.use_ada_layer_norm_continuous:
|
||||
norm_hidden_states = self.norm2(hidden_states, modulation_cond)
|
||||
else:
|
||||
norm_hidden_states = self.norm2(hidden_states)
|
||||
|
||||
attn_output = self.attn2(
|
||||
norm_hidden_states,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
attention_mask=encoder_attention_mask,
|
||||
**cross_attention_kwargs,
|
||||
)
|
||||
hidden_states = attn_output + hidden_states
|
||||
|
||||
# 4. Feed-forward
|
||||
if self.use_ada_layer_norm_continuous:
|
||||
norm_hidden_states = self.norm3(hidden_states, modulation_cond)
|
||||
else:
|
||||
norm_hidden_states = self.norm3(hidden_states)
|
||||
|
||||
if self.use_ada_layer_norm_zero:
|
||||
norm_hidden_states = (
|
||||
norm_hidden_states * (1 + scale_mlp[:, None]) + shift_mlp[:, None]
|
||||
)
|
||||
|
||||
if self._chunk_size is not None:
|
||||
# "feed_forward_chunk_size" can be used to save memory
|
||||
if norm_hidden_states.shape[self._chunk_dim] % self._chunk_size != 0:
|
||||
raise ValueError(
|
||||
f"`hidden_states` dimension to be chunked: {norm_hidden_states.shape[self._chunk_dim]} has to be divisible by chunk size: {self._chunk_size}. Make sure to set an appropriate `chunk_size` when calling `unet.enable_forward_chunking`."
|
||||
)
|
||||
|
||||
num_chunks = norm_hidden_states.shape[self._chunk_dim] // self._chunk_size
|
||||
ff_output = torch.cat(
|
||||
[
|
||||
self.ff(hid_slice, scale=lora_scale)
|
||||
for hid_slice in norm_hidden_states.chunk(
|
||||
num_chunks, dim=self._chunk_dim
|
||||
)
|
||||
],
|
||||
dim=self._chunk_dim,
|
||||
)
|
||||
else:
|
||||
ff_output = self.ff(norm_hidden_states, scale=lora_scale)
|
||||
|
||||
if self.use_ada_layer_norm_zero:
|
||||
ff_output = gate_mlp.unsqueeze(1) * ff_output
|
||||
|
||||
hidden_states = ff_output + hidden_states
|
||||
|
||||
return hidden_states
|
||||
|
||||
|
||||
class FeedForward(nn.Module):
|
||||
r"""
|
||||
A feed-forward layer.
|
||||
|
||||
Parameters:
|
||||
dim (`int`): The number of channels in the input.
|
||||
dim_out (`int`, *optional*): The number of channels in the output. If not given, defaults to `dim`.
|
||||
mult (`int`, *optional*, defaults to 4): The multiplier to use for the hidden dimension.
|
||||
dropout (`float`, *optional*, defaults to 0.0): The dropout probability to use.
|
||||
activation_fn (`str`, *optional*, defaults to `"geglu"`): Activation function to be used in feed-forward.
|
||||
final_dropout (`bool` *optional*, defaults to False): Apply a final dropout.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dim: int,
|
||||
dim_out: Optional[int] = None,
|
||||
mult: int = 4,
|
||||
dropout: float = 0.0,
|
||||
activation_fn: str = "geglu",
|
||||
final_dropout: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
inner_dim = int(dim * mult)
|
||||
dim_out = dim_out if dim_out is not None else dim
|
||||
linear_cls = nn.Linear
|
||||
|
||||
if activation_fn == "gelu":
|
||||
act_fn = GELU(dim, inner_dim)
|
||||
if activation_fn == "gelu-approximate":
|
||||
act_fn = GELU(dim, inner_dim, approximate="tanh")
|
||||
elif activation_fn == "geglu":
|
||||
act_fn = GEGLU(dim, inner_dim)
|
||||
elif activation_fn == "geglu-approximate":
|
||||
act_fn = ApproximateGELU(dim, inner_dim)
|
||||
|
||||
self.net = nn.ModuleList([])
|
||||
# project in
|
||||
self.net.append(act_fn)
|
||||
# project dropout
|
||||
self.net.append(nn.Dropout(dropout))
|
||||
# project out
|
||||
self.net.append(linear_cls(inner_dim, dim_out))
|
||||
# FF as used in Vision Transformer, MLP-Mixer, etc. have a final dropout
|
||||
if final_dropout:
|
||||
self.net.append(nn.Dropout(dropout))
|
||||
|
||||
def forward(self, hidden_states: torch.Tensor, scale: float = 1.0) -> torch.Tensor:
|
||||
for module in self.net:
|
||||
hidden_states = module(hidden_states)
|
||||
return hidden_states
|
||||
|
||||
|
||||
class GELU(nn.Module):
|
||||
r"""
|
||||
GELU activation function with tanh approximation support with `approximate="tanh"`.
|
||||
|
||||
Parameters:
|
||||
dim_in (`int`): The number of channels in the input.
|
||||
dim_out (`int`): The number of channels in the output.
|
||||
approximate (`str`, *optional*, defaults to `"none"`): If `"tanh"`, use tanh approximation.
|
||||
"""
|
||||
|
||||
def __init__(self, dim_in: int, dim_out: int, approximate: str = "none"):
|
||||
super().__init__()
|
||||
self.proj = nn.Linear(dim_in, dim_out)
|
||||
self.approximate = approximate
|
||||
|
||||
def gelu(self, gate: torch.Tensor) -> torch.Tensor:
|
||||
if gate.device.type != "mps":
|
||||
return F.gelu(gate, approximate=self.approximate)
|
||||
# mps: gelu is not implemented for float16
|
||||
return F.gelu(gate.to(dtype=torch.float32), approximate=self.approximate).to(
|
||||
dtype=gate.dtype
|
||||
)
|
||||
|
||||
def forward(self, hidden_states):
|
||||
hidden_states = self.proj(hidden_states)
|
||||
hidden_states = self.gelu(hidden_states)
|
||||
return hidden_states
|
||||
|
||||
|
||||
class GEGLU(nn.Module):
|
||||
r"""
|
||||
A variant of the gated linear unit activation function from https://arxiv.org/abs/2002.05202.
|
||||
|
||||
Parameters:
|
||||
dim_in (`int`): The number of channels in the input.
|
||||
dim_out (`int`): The number of channels in the output.
|
||||
"""
|
||||
|
||||
def __init__(self, dim_in: int, dim_out: int):
|
||||
super().__init__()
|
||||
linear_cls = nn.Linear
|
||||
|
||||
self.proj = linear_cls(dim_in, dim_out * 2)
|
||||
|
||||
def gelu(self, gate: torch.Tensor) -> torch.Tensor:
|
||||
if gate.device.type != "mps":
|
||||
return F.gelu(gate)
|
||||
# mps: gelu is not implemented for float16
|
||||
return F.gelu(gate.to(dtype=torch.float32)).to(dtype=gate.dtype)
|
||||
|
||||
def forward(self, hidden_states, scale: float = 1.0):
|
||||
args = ()
|
||||
hidden_states, gate = self.proj(hidden_states, *args).chunk(2, dim=-1)
|
||||
return hidden_states * self.gelu(gate)
|
||||
|
||||
|
||||
class ApproximateGELU(nn.Module):
|
||||
r"""
|
||||
The approximate form of Gaussian Error Linear Unit (GELU). For more details, see section 2:
|
||||
https://arxiv.org/abs/1606.08415.
|
||||
|
||||
Parameters:
|
||||
dim_in (`int`): The number of channels in the input.
|
||||
dim_out (`int`): The number of channels in the output.
|
||||
"""
|
||||
|
||||
def __init__(self, dim_in: int, dim_out: int):
|
||||
super().__init__()
|
||||
self.proj = nn.Linear(dim_in, dim_out)
|
||||
|
||||
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||
x = self.proj(x)
|
||||
return x * torch.sigmoid(1.702 * x)
|
||||
|
||||
|
||||
class AdaLayerNorm(nn.Module):
|
||||
r"""
|
||||
Norm layer modified to incorporate timestep embeddings.
|
||||
|
||||
Parameters:
|
||||
embedding_dim (`int`): The size of each embedding vector.
|
||||
num_embeddings (`int`): The size of the dictionary of embeddings.
|
||||
"""
|
||||
|
||||
def __init__(self, embedding_dim: int, num_embeddings: int):
|
||||
super().__init__()
|
||||
self.emb = nn.Embedding(num_embeddings, embedding_dim)
|
||||
self.silu = nn.SiLU()
|
||||
self.linear = nn.Linear(embedding_dim, embedding_dim * 2)
|
||||
self.norm = nn.LayerNorm(embedding_dim, elementwise_affine=False)
|
||||
|
||||
def forward(self, x: torch.Tensor, timestep: torch.Tensor) -> torch.Tensor:
|
||||
emb = self.linear(self.silu(self.emb(timestep)))
|
||||
scale, shift = torch.chunk(emb, 2, dim=1)
|
||||
x = self.norm(x) * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)
|
||||
return x
|
||||
|
||||
|
||||
class AdaLayerNormContinuous(nn.Module):
|
||||
r"""
|
||||
Norm layer modified to incorporate arbitrary continuous embeddings.
|
||||
|
||||
Parameters:
|
||||
embedding_dim (`int`): The size of each embedding vector.
|
||||
"""
|
||||
|
||||
def __init__(self, embedding_dim: int, condition_dim: int):
|
||||
super().__init__()
|
||||
self.silu = nn.SiLU()
|
||||
self.linear1 = nn.Linear(condition_dim, condition_dim)
|
||||
self.linear2 = nn.Linear(condition_dim, embedding_dim * 2)
|
||||
self.norm = nn.LayerNorm(embedding_dim, elementwise_affine=False)
|
||||
|
||||
def forward(self, x: torch.Tensor, condition: torch.Tensor) -> torch.Tensor:
|
||||
emb = self.linear2(self.silu(self.linear1(condition)))
|
||||
scale, shift = torch.chunk(emb, 2, dim=1)
|
||||
x = self.norm(x) * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)
|
||||
return x
|
||||
|
||||
|
||||
class Modulation(nn.Module):
|
||||
def __init__(self, embedding_dim: int, condition_dim: int, zero_init: bool = False, single_layer: bool = False):
|
||||
super().__init__()
|
||||
self.silu = nn.SiLU()
|
||||
if single_layer:
|
||||
self.linear1 = nn.Identity()
|
||||
else:
|
||||
self.linear1 = nn.Linear(condition_dim, condition_dim)
|
||||
|
||||
self.linear2 = nn.Linear(condition_dim, embedding_dim * 2)
|
||||
|
||||
# Only zero init the last linear layer
|
||||
if zero_init:
|
||||
nn.init.zeros_(self.linear2.weight)
|
||||
nn.init.zeros_(self.linear2.bias)
|
||||
|
||||
def forward(self, x: torch.Tensor, condition: torch.Tensor) -> torch.Tensor:
|
||||
emb = self.linear2(self.silu(self.linear1(condition)))
|
||||
scale, shift = torch.chunk(emb, 2, dim=1)
|
||||
x = x * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)
|
||||
return x
|
||||
|
||||
|
||||
class AdaLayerNormZero(nn.Module):
|
||||
r"""
|
||||
Norm layer adaptive layer norm zero (adaLN-Zero).
|
||||
|
||||
Parameters:
|
||||
embedding_dim (`int`): The size of each embedding vector.
|
||||
num_embeddings (`int`): The size of the dictionary of embeddings.
|
||||
"""
|
||||
|
||||
def __init__(self, embedding_dim: int, num_embeddings: int):
|
||||
super().__init__()
|
||||
|
||||
self.emb = CombinedTimestepLabelEmbeddings(num_embeddings, embedding_dim)
|
||||
|
||||
self.silu = nn.SiLU()
|
||||
self.linear = nn.Linear(embedding_dim, 6 * embedding_dim, bias=True)
|
||||
self.norm = nn.LayerNorm(embedding_dim, elementwise_affine=False, eps=1e-6)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
x: torch.Tensor,
|
||||
timestep: torch.Tensor,
|
||||
class_labels: torch.LongTensor,
|
||||
hidden_dtype: Optional[torch.dtype] = None,
|
||||
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
|
||||
emb = self.linear(
|
||||
self.silu(self.emb(timestep, class_labels, hidden_dtype=hidden_dtype))
|
||||
)
|
||||
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = emb.chunk(
|
||||
6, dim=1
|
||||
)
|
||||
x = self.norm(x) * (1 + scale_msa[:, None]) + shift_msa[:, None]
|
||||
return x, gate_msa, shift_mlp, scale_mlp, gate_mlp
|
||||
|
||||
|
||||
class AdaGroupNorm(nn.Module):
|
||||
r"""
|
||||
GroupNorm layer modified to incorporate timestep embeddings.
|
||||
|
||||
Parameters:
|
||||
embedding_dim (`int`): The size of each embedding vector.
|
||||
num_embeddings (`int`): The size of the dictionary of embeddings.
|
||||
num_groups (`int`): The number of groups to separate the channels into.
|
||||
act_fn (`str`, *optional*, defaults to `None`): The activation function to use.
|
||||
eps (`float`, *optional*, defaults to `1e-5`): The epsilon value to use for numerical stability.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
embedding_dim: int,
|
||||
out_dim: int,
|
||||
num_groups: int,
|
||||
act_fn: Optional[str] = None,
|
||||
eps: float = 1e-5,
|
||||
):
|
||||
super().__init__()
|
||||
self.num_groups = num_groups
|
||||
self.eps = eps
|
||||
|
||||
if act_fn is None:
|
||||
self.act = None
|
||||
else:
|
||||
self.act = get_activation(act_fn)
|
||||
|
||||
self.linear = nn.Linear(embedding_dim, out_dim * 2)
|
||||
|
||||
def forward(self, x: torch.Tensor, emb: torch.Tensor) -> torch.Tensor:
|
||||
if self.act:
|
||||
emb = self.act(emb)
|
||||
emb = self.linear(emb)
|
||||
emb = emb[:, :, None, None]
|
||||
scale, shift = emb.chunk(2, dim=1)
|
||||
|
||||
x = F.group_norm(x, self.num_groups, eps=self.eps)
|
||||
x = x * (1 + scale) + shift
|
||||
return x
|
||||
|
||||
class Transformer1D(BaseModule, MemoryEfficientAttentionMixin):
|
||||
"""
|
||||
A 1D Transformer model for sequence data.
|
||||
|
||||
Parameters:
|
||||
num_attention_heads (`int`, *optional*, defaults to 16): The number of heads to use for multi-head attention.
|
||||
attention_head_dim (`int`, *optional*, defaults to 88): The number of channels in each head.
|
||||
in_channels (`int`, *optional*):
|
||||
The number of channels in the input and output (specify if the input is **continuous**).
|
||||
num_layers (`int`, *optional*, defaults to 1): The number of layers of Transformer blocks to use.
|
||||
dropout (`float`, *optional*, defaults to 0.0): The dropout probability to use.
|
||||
cross_attention_dim (`int`, *optional*): The number of `encoder_hidden_states` dimensions to use.
|
||||
activation_fn (`str`, *optional*, defaults to `"geglu"`): Activation function to use in feed-forward.
|
||||
num_embeds_ada_norm ( `int`, *optional*):
|
||||
The number of diffusion steps used during training. Pass if at least one of the norm_layers is
|
||||
`AdaLayerNorm`. This is fixed during training since it is used to learn a number of embeddings that are
|
||||
added to the hidden states.
|
||||
|
||||
During inference, you can denoise for up to but not more steps than `num_embeds_ada_norm`.
|
||||
attention_bias (`bool`, *optional*):
|
||||
Configure if the `TransformerBlocks` attention should contain a bias parameter.
|
||||
"""
|
||||
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
num_attention_heads: int = 16
|
||||
attention_head_dim: int = 88
|
||||
in_channels: Optional[int] = None
|
||||
out_channels: Optional[int] = None
|
||||
num_layers: int = 1
|
||||
dropout: float = 0.0
|
||||
norm_num_groups: int = 32
|
||||
cross_attention_dim: Optional[int] = None
|
||||
attention_bias: bool = False
|
||||
activation_fn: str = "geglu"
|
||||
num_embeds_ada_norm: Optional[int] = None
|
||||
cond_dim_ada_norm_continuous: Optional[int] = None
|
||||
only_cross_attention: bool = False
|
||||
double_self_attention: bool = False
|
||||
upcast_attention: bool = False
|
||||
norm_type: str = "layer_norm"
|
||||
norm_elementwise_affine: bool = True
|
||||
attention_type: str = "default"
|
||||
enable_memory_efficient_attention: bool = False
|
||||
gradient_checkpointing: bool = False
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
|
||||
self.num_attention_heads = self.cfg.num_attention_heads
|
||||
self.attention_head_dim = self.cfg.attention_head_dim
|
||||
inner_dim = self.num_attention_heads * self.attention_head_dim
|
||||
|
||||
linear_cls = nn.Linear
|
||||
|
||||
if self.cfg.norm_type == "layer_norm" and (
|
||||
self.cfg.num_embeds_ada_norm is not None
|
||||
or self.cfg.cond_dim_ada_norm_continuous is not None
|
||||
):
|
||||
raise ValueError("Incorrect norm_type.")
|
||||
|
||||
# 2. Define input layers
|
||||
self.in_channels = self.cfg.in_channels
|
||||
|
||||
self.norm = torch.nn.GroupNorm(
|
||||
num_groups=self.cfg.norm_num_groups,
|
||||
num_channels=self.cfg.in_channels,
|
||||
eps=1e-6,
|
||||
affine=True,
|
||||
)
|
||||
self.proj_in = linear_cls(self.cfg.in_channels, inner_dim)
|
||||
|
||||
# 3. Define transformers blocks
|
||||
self.transformer_blocks = nn.ModuleList(
|
||||
[
|
||||
BasicTransformerBlock(
|
||||
inner_dim,
|
||||
self.num_attention_heads,
|
||||
self.attention_head_dim,
|
||||
dropout=self.cfg.dropout,
|
||||
cross_attention_dim=self.cfg.cross_attention_dim,
|
||||
activation_fn=self.cfg.activation_fn,
|
||||
num_embeds_ada_norm=self.cfg.num_embeds_ada_norm,
|
||||
cond_dim_ada_norm_continuous=self.cfg.cond_dim_ada_norm_continuous,
|
||||
attention_bias=self.cfg.attention_bias,
|
||||
only_cross_attention=self.cfg.only_cross_attention,
|
||||
double_self_attention=self.cfg.double_self_attention,
|
||||
upcast_attention=self.cfg.upcast_attention,
|
||||
norm_type=self.cfg.norm_type,
|
||||
norm_elementwise_affine=self.cfg.norm_elementwise_affine,
|
||||
attention_type=self.cfg.attention_type,
|
||||
)
|
||||
for d in range(self.cfg.num_layers)
|
||||
]
|
||||
)
|
||||
|
||||
# 4. Define output layers
|
||||
self.out_channels = (
|
||||
self.cfg.in_channels
|
||||
if self.cfg.out_channels is None
|
||||
else self.cfg.out_channels
|
||||
)
|
||||
|
||||
self.proj_out = linear_cls(inner_dim, self.cfg.in_channels)
|
||||
|
||||
self.gradient_checkpointing = self.cfg.gradient_checkpointing
|
||||
|
||||
self.set_use_memory_efficient_attention_xformers(
|
||||
self.cfg.enable_memory_efficient_attention
|
||||
)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states: torch.Tensor,
|
||||
encoder_hidden_states: Optional[torch.Tensor] = None,
|
||||
timestep: Optional[torch.LongTensor] = None,
|
||||
modulation_cond: Optional[torch.FloatTensor] = None,
|
||||
class_labels: Optional[torch.LongTensor] = None,
|
||||
cross_attention_kwargs: Dict[str, Any] = None,
|
||||
attention_mask: Optional[torch.Tensor] = None,
|
||||
encoder_attention_mask: Optional[torch.Tensor] = None,
|
||||
):
|
||||
"""
|
||||
The [`Transformer1DModel`] forward method.
|
||||
|
||||
Args:
|
||||
hidden_states (`torch.LongTensor` of shape `(batch size, num latent pixels)` if discrete, `torch.FloatTensor` of shape `(batch size, channel, height, width)` if continuous):
|
||||
Input `hidden_states`.
|
||||
encoder_hidden_states ( `torch.FloatTensor` of shape `(batch size, sequence len, embed dims)`, *optional*):
|
||||
Conditional embeddings for cross attention layer. If not given, cross-attention defaults to
|
||||
self-attention.
|
||||
timestep ( `torch.LongTensor`, *optional*):
|
||||
Used to indicate denoising step. Optional timestep to be applied as an embedding in `AdaLayerNorm`.
|
||||
class_labels ( `torch.LongTensor` of shape `(batch size, num classes)`, *optional*):
|
||||
Used to indicate class labels conditioning. Optional class labels to be applied as an embedding in
|
||||
`AdaLayerZeroNorm`.
|
||||
cross_attention_kwargs ( `Dict[str, Any]`, *optional*):
|
||||
A kwargs dictionary that if specified is passed along to the `AttentionProcessor` as defined under
|
||||
`self.processor` in
|
||||
[diffusers.models.attention_processor](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py).
|
||||
attention_mask ( `torch.Tensor`, *optional*):
|
||||
An attention mask of shape `(batch, key_tokens)` is applied to `encoder_hidden_states`. If `1` the mask
|
||||
is kept, otherwise if `0` it is discarded. Mask will be converted into a bias, which adds large
|
||||
negative values to the attention scores corresponding to "discard" tokens.
|
||||
encoder_attention_mask ( `torch.Tensor`, *optional*):
|
||||
Cross-attention mask applied to `encoder_hidden_states`. Two formats supported:
|
||||
|
||||
* Mask `(batch, sequence_length)` True = keep, False = discard.
|
||||
* Bias `(batch, 1, sequence_length)` 0 = keep, -10000 = discard.
|
||||
|
||||
If `ndim == 2`: will be interpreted as a mask, then converted into a bias consistent with the format
|
||||
above. This bias will be added to the cross-attention scores.
|
||||
return_dict (`bool`, *optional*, defaults to `True`):
|
||||
Whether or not to return a [`~models.unet_2d_condition.UNet2DConditionOutput`] instead of a plain
|
||||
tuple.
|
||||
|
||||
Returns:
|
||||
If `return_dict` is True, an [`~models.transformer_2d.Transformer2DModelOutput`] is returned, otherwise a
|
||||
`tuple` where the first element is the sample tensor.
|
||||
"""
|
||||
# ensure attention_mask is a bias, and give it a singleton query_tokens dimension.
|
||||
# we may have done this conversion already, e.g. if we came here via UNet2DConditionModel#forward.
|
||||
# we can tell by counting dims; if ndim == 2: it's a mask rather than a bias.
|
||||
# expects mask of shape:
|
||||
# [batch, key_tokens]
|
||||
# adds singleton query_tokens dimension:
|
||||
# [batch, 1, key_tokens]
|
||||
# this helps to broadcast it as a bias over attention scores, which will be in one of the following shapes:
|
||||
# [batch, heads, query_tokens, key_tokens] (e.g. torch sdp attn)
|
||||
# [batch * heads, query_tokens, key_tokens] (e.g. xformers or classic attn)
|
||||
if attention_mask is not None and attention_mask.ndim == 2:
|
||||
# assume that mask is expressed as:
|
||||
# (1 = keep, 0 = discard)
|
||||
# convert mask into a bias that can be added to attention scores:
|
||||
# (keep = +0, discard = -10000.0)
|
||||
attention_mask = (1 - attention_mask.to(hidden_states.dtype)) * -10000.0
|
||||
attention_mask = attention_mask.unsqueeze(1)
|
||||
|
||||
# convert encoder_attention_mask to a bias the same way we do for attention_mask
|
||||
if encoder_attention_mask is not None and encoder_attention_mask.ndim == 2:
|
||||
encoder_attention_mask = (
|
||||
1 - encoder_attention_mask.to(hidden_states.dtype)
|
||||
) * -10000.0
|
||||
encoder_attention_mask = encoder_attention_mask.unsqueeze(1)
|
||||
|
||||
# 1. Input
|
||||
batch, _, seq_len = hidden_states.shape
|
||||
residual = hidden_states
|
||||
|
||||
hidden_states = self.norm(hidden_states)
|
||||
inner_dim = hidden_states.shape[1]
|
||||
hidden_states = hidden_states.permute(0, 2, 1).reshape(
|
||||
batch, seq_len, inner_dim
|
||||
)
|
||||
hidden_states = self.proj_in(hidden_states)
|
||||
|
||||
# 2. Blocks
|
||||
for block in self.transformer_blocks:
|
||||
if self.training and self.gradient_checkpointing:
|
||||
hidden_states = torch.utils.checkpoint.checkpoint(
|
||||
block,
|
||||
hidden_states,
|
||||
attention_mask,
|
||||
encoder_hidden_states,
|
||||
encoder_attention_mask,
|
||||
timestep,
|
||||
modulation_cond,
|
||||
cross_attention_kwargs,
|
||||
class_labels,
|
||||
use_reentrant=False,
|
||||
)
|
||||
else:
|
||||
hidden_states = block(
|
||||
hidden_states,
|
||||
attention_mask=attention_mask,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
encoder_attention_mask=encoder_attention_mask,
|
||||
timestep=timestep,
|
||||
modulation_cond=modulation_cond,
|
||||
cross_attention_kwargs=cross_attention_kwargs,
|
||||
class_labels=class_labels,
|
||||
)
|
||||
|
||||
# 3. Output
|
||||
hidden_states = self.proj_out(hidden_states)
|
||||
hidden_states = (
|
||||
hidden_states.reshape(batch, seq_len, inner_dim)
|
||||
.permute(0, 2, 1)
|
||||
.contiguous()
|
||||
)
|
||||
|
||||
output = hidden_states + residual
|
||||
|
||||
return output
|
||||
@@ -0,0 +1,129 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import torch.nn as nn
|
||||
|
||||
from .config import parse_structured
|
||||
from .misc import get_device, load_module_weights
|
||||
from .typing import *
|
||||
|
||||
|
||||
class Configurable:
|
||||
@dataclass
|
||||
class Config:
|
||||
pass
|
||||
|
||||
def __init__(self, cfg: Optional[dict] = None) -> None:
|
||||
super().__init__()
|
||||
self.cfg = parse_structured(self.Config, cfg)
|
||||
|
||||
|
||||
class Updateable:
|
||||
def do_update_step(
|
||||
self, epoch: int, global_step: int, on_load_weights: bool = False
|
||||
):
|
||||
for attr in self.__dir__():
|
||||
if attr.startswith("_"):
|
||||
continue
|
||||
try:
|
||||
module = getattr(self, attr)
|
||||
except:
|
||||
continue # ignore attributes like property, which can't be retrived using getattr?
|
||||
if isinstance(module, Updateable):
|
||||
module.do_update_step(
|
||||
epoch, global_step, on_load_weights=on_load_weights
|
||||
)
|
||||
self.update_step(epoch, global_step, on_load_weights=on_load_weights)
|
||||
|
||||
def do_update_step_end(self, epoch: int, global_step: int):
|
||||
for attr in self.__dir__():
|
||||
if attr.startswith("_"):
|
||||
continue
|
||||
try:
|
||||
module = getattr(self, attr)
|
||||
except:
|
||||
continue # ignore attributes like property, which can't be retrived using getattr?
|
||||
if isinstance(module, Updateable):
|
||||
module.do_update_step_end(epoch, global_step)
|
||||
self.update_step_end(epoch, global_step)
|
||||
|
||||
def update_step(self, epoch: int, global_step: int, on_load_weights: bool = False):
|
||||
# override this method to implement custom update logic
|
||||
# if on_load_weights is True, you should be careful doing things related to model evaluations,
|
||||
# as the models and tensors are not guarenteed to be on the same device
|
||||
pass
|
||||
|
||||
def update_step_end(self, epoch: int, global_step: int):
|
||||
pass
|
||||
|
||||
|
||||
def update_if_possible(module: Any, epoch: int, global_step: int) -> None:
|
||||
if isinstance(module, Updateable):
|
||||
module.do_update_step(epoch, global_step)
|
||||
|
||||
|
||||
def update_end_if_possible(module: Any, epoch: int, global_step: int) -> None:
|
||||
if isinstance(module, Updateable):
|
||||
module.do_update_step_end(epoch, global_step)
|
||||
|
||||
|
||||
class BaseObject(Updateable):
|
||||
@dataclass
|
||||
class Config:
|
||||
pass
|
||||
|
||||
cfg: Config # add this to every subclass of BaseObject to enable static type checking
|
||||
|
||||
def __init__(
|
||||
self, cfg: Optional[Union[dict, DictConfig]] = None, *args, **kwargs
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.cfg = parse_structured(self.Config, cfg)
|
||||
self.device = get_device()
|
||||
self.configure(*args, **kwargs)
|
||||
|
||||
def configure(self, *args, **kwargs) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class BaseModule(nn.Module, Updateable):
|
||||
@dataclass
|
||||
class Config:
|
||||
weights: Optional[str] = None
|
||||
freeze: Optional[bool] = False
|
||||
|
||||
cfg: Config # add this to every subclass of BaseModule to enable static type checking
|
||||
|
||||
def __init__(
|
||||
self, cfg: Optional[Union[dict, DictConfig]] = None, *args, **kwargs
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.cfg = parse_structured(self.Config, cfg)
|
||||
self.device = get_device()
|
||||
self._non_modules = {}
|
||||
self.configure(*args, **kwargs)
|
||||
if self.cfg.weights is not None:
|
||||
# format: path/to/weights:module_name
|
||||
weights_path, module_name = self.cfg.weights.split(":")
|
||||
state_dict = load_module_weights(
|
||||
weights_path, module_name=module_name, map_location="cpu"
|
||||
)
|
||||
self.load_state_dict(state_dict, strict=False)
|
||||
# self.do_update_step(
|
||||
# epoch, global_step, on_load_weights=True
|
||||
# ) # restore states
|
||||
|
||||
if self.cfg.freeze:
|
||||
for params in self.parameters():
|
||||
params.requires_grad = False
|
||||
|
||||
def configure(self, *args, **kwargs) -> None:
|
||||
pass
|
||||
|
||||
def register_non_module(self, name: str, module: nn.Module) -> None:
|
||||
# non-modules won't be treated as model parameters
|
||||
if name in self._non_modules:
|
||||
raise ValueError(f"Non-module {name} already exists!")
|
||||
self._non_modules[name] = module
|
||||
|
||||
def non_module(self, name: str):
|
||||
return self._non_modules.get(name, None)
|
||||
@@ -0,0 +1,74 @@
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from omegaconf import OmegaConf
|
||||
|
||||
from .typing import *
|
||||
|
||||
# ============ Register OmegaConf Recolvers ============= #
|
||||
OmegaConf.register_new_resolver(
|
||||
"calc_exp_lr_decay_rate", lambda factor, n: factor ** (1.0 / n), replace=True
|
||||
)
|
||||
OmegaConf.register_new_resolver("add", lambda a, b: a + b, replace=True)
|
||||
OmegaConf.register_new_resolver("sub", lambda a, b: a - b, replace=True)
|
||||
OmegaConf.register_new_resolver("mul", lambda a, b: a * b, replace=True)
|
||||
OmegaConf.register_new_resolver("div", lambda a, b: a / b, replace=True)
|
||||
OmegaConf.register_new_resolver("idiv", lambda a, b: a // b, replace=True)
|
||||
OmegaConf.register_new_resolver("basename", lambda p: os.path.basename(p), replace=True)
|
||||
OmegaConf.register_new_resolver("rmspace", lambda s, sub: s.replace(" ", sub), replace=True)
|
||||
OmegaConf.register_new_resolver("tuple2", lambda s: [float(s), float(s)], replace=True)
|
||||
OmegaConf.register_new_resolver("gt0", lambda s: s > 0, replace=True)
|
||||
OmegaConf.register_new_resolver("not", lambda s: not s, replace=True)
|
||||
OmegaConf.register_new_resolver("shsdim", lambda sh_degree: (sh_degree + 1) ** 2 * 3, replace=True)
|
||||
# ======================================================= #
|
||||
|
||||
# ============== Automatic Name Resolvers =============== #
|
||||
def get_naming_convention(cfg):
|
||||
# TODO
|
||||
name = f"tgs_{cfg.system.backbone.num_layers}"
|
||||
return name
|
||||
|
||||
# ======================================================= #
|
||||
|
||||
@dataclass
|
||||
class ExperimentConfig:
|
||||
n_gpus: int = 1
|
||||
data: dict = field(default_factory=dict)
|
||||
system: dict = field(default_factory=dict)
|
||||
|
||||
def load_config(
|
||||
*yamls: str, cli_args: list = [], from_string=False, makedirs=True, **kwargs
|
||||
) -> Any:
|
||||
if from_string:
|
||||
parse_func = OmegaConf.create
|
||||
else:
|
||||
parse_func = OmegaConf.load
|
||||
yaml_confs = []
|
||||
for y in yamls:
|
||||
conf = parse_func(y)
|
||||
extends = conf.pop("extends", None)
|
||||
if extends:
|
||||
assert os.path.exists(extends), f"File {extends} does not exist."
|
||||
yaml_confs.append(OmegaConf.load(extends))
|
||||
yaml_confs.append(conf)
|
||||
cli_conf = OmegaConf.from_cli(cli_args)
|
||||
cfg = OmegaConf.merge(*yaml_confs, cli_conf, kwargs)
|
||||
OmegaConf.resolve(cfg)
|
||||
assert isinstance(cfg, DictConfig)
|
||||
scfg: ExperimentConfig = parse_structured(ExperimentConfig, cfg)
|
||||
|
||||
return scfg
|
||||
|
||||
|
||||
def config_to_primitive(config, resolve: bool = True) -> Any:
|
||||
return OmegaConf.to_container(config, resolve=resolve)
|
||||
|
||||
|
||||
def dump_config(path: str, config) -> None:
|
||||
with open(path, "w") as fp:
|
||||
OmegaConf.save(config=config, f=fp)
|
||||
|
||||
|
||||
def parse_structured(fields: Any, cfg: Optional[Union[dict, DictConfig]] = None) -> Any:
|
||||
scfg = OmegaConf.structured(fields(**cfg))
|
||||
return scfg
|
||||
@@ -0,0 +1,88 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
import torch
|
||||
from packaging import version
|
||||
|
||||
from .typing import *
|
||||
|
||||
|
||||
def parse_version(ver: str):
|
||||
return version.parse(ver)
|
||||
|
||||
|
||||
def get_rank():
|
||||
# SLURM_PROCID can be set even if SLURM is not managing the multiprocessing,
|
||||
# therefore LOCAL_RANK needs to be checked first
|
||||
rank_keys = ("RANK", "LOCAL_RANK", "SLURM_PROCID", "JSM_NAMESPACE_RANK")
|
||||
for key in rank_keys:
|
||||
rank = os.environ.get(key)
|
||||
if rank is not None:
|
||||
return int(rank)
|
||||
return 0
|
||||
|
||||
|
||||
def get_device():
|
||||
return torch.device(f"cuda:{get_rank()}")
|
||||
|
||||
|
||||
def load_module_weights(
|
||||
path, module_name=None, ignore_modules=None, map_location=None
|
||||
) -> Tuple[dict, int, int]:
|
||||
if module_name is not None and ignore_modules is not None:
|
||||
raise ValueError("module_name and ignore_modules cannot be both set")
|
||||
if map_location is None:
|
||||
map_location = get_device()
|
||||
|
||||
ckpt = torch.load(path, map_location=map_location)
|
||||
state_dict = ckpt["state_dict"]
|
||||
state_dict_to_load = state_dict
|
||||
|
||||
if ignore_modules is not None:
|
||||
state_dict_to_load = {}
|
||||
for k, v in state_dict.items():
|
||||
ignore = any(
|
||||
[k.startswith(ignore_module + ".") for ignore_module in ignore_modules]
|
||||
)
|
||||
if ignore:
|
||||
continue
|
||||
state_dict_to_load[k] = v
|
||||
|
||||
if module_name is not None:
|
||||
state_dict_to_load = {}
|
||||
for k, v in state_dict.items():
|
||||
m = re.match(rf"^{module_name}\.(.*)$", k)
|
||||
if m is None:
|
||||
continue
|
||||
state_dict_to_load[m.group(1)] = v
|
||||
|
||||
return state_dict_to_load
|
||||
|
||||
# convert a function into recursive style to handle nested dict/list/tuple variables
|
||||
def make_recursive_func(func):
|
||||
def wrapper(vars, *args, **kwargs):
|
||||
if isinstance(vars, list):
|
||||
return [wrapper(x, *args, **kwargs) for x in vars]
|
||||
elif isinstance(vars, tuple):
|
||||
return tuple([wrapper(x, *args, **kwargs) for x in vars])
|
||||
elif isinstance(vars, dict):
|
||||
return {k: wrapper(v, *args, **kwargs) for k, v in vars.items()}
|
||||
else:
|
||||
return func(vars, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
@make_recursive_func
|
||||
def todevice(vars, device="cuda"):
|
||||
if isinstance(vars, torch.Tensor):
|
||||
return vars.to(device)
|
||||
elif isinstance(vars, str):
|
||||
return vars
|
||||
elif isinstance(vars, bool):
|
||||
return vars
|
||||
elif isinstance(vars, float):
|
||||
return vars
|
||||
elif isinstance(vars, int):
|
||||
return vars
|
||||
else:
|
||||
raise NotImplementedError("invalid input type {} for tensor2numpy".format(type(vars)))
|
||||
@@ -0,0 +1,279 @@
|
||||
import math
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torch.autograd import Function
|
||||
from torch.cuda.amp import custom_bwd, custom_fwd
|
||||
from pytorch3d import io
|
||||
from pytorch3d.renderer import (
|
||||
PointsRasterizationSettings,
|
||||
PointsRasterizer)
|
||||
from pytorch3d.structures import Pointclouds
|
||||
from pytorch3d.utils.camera_conversions import cameras_from_opencv_projection
|
||||
import cv2
|
||||
|
||||
from .typing import *
|
||||
|
||||
ValidScale = Union[Tuple[float, float], Num[Tensor, "2 D"]]
|
||||
|
||||
def scale_tensor(
|
||||
dat: Num[Tensor, "... D"], inp_scale: ValidScale, tgt_scale: ValidScale
|
||||
):
|
||||
if inp_scale is None:
|
||||
inp_scale = (0, 1)
|
||||
if tgt_scale is None:
|
||||
tgt_scale = (0, 1)
|
||||
if isinstance(tgt_scale, Tensor):
|
||||
assert dat.shape[-1] == tgt_scale.shape[-1]
|
||||
dat = (dat - inp_scale[0]) / (inp_scale[1] - inp_scale[0])
|
||||
dat = dat * (tgt_scale[1] - tgt_scale[0]) + tgt_scale[0]
|
||||
return dat
|
||||
|
||||
|
||||
class _TruncExp(Function): # pylint: disable=abstract-method
|
||||
# Implementation from torch-ngp:
|
||||
# https://github.com/ashawkey/torch-ngp/blob/93b08a0d4ec1cc6e69d85df7f0acdfb99603b628/activation.py
|
||||
@staticmethod
|
||||
@custom_fwd(cast_inputs=torch.float32)
|
||||
def forward(ctx, x): # pylint: disable=arguments-differ
|
||||
ctx.save_for_backward(x)
|
||||
return torch.exp(x)
|
||||
|
||||
@staticmethod
|
||||
@custom_bwd
|
||||
def backward(ctx, g): # pylint: disable=arguments-differ
|
||||
x = ctx.saved_tensors[0]
|
||||
return g * torch.exp(torch.clamp(x, max=15))
|
||||
|
||||
|
||||
trunc_exp = _TruncExp.apply
|
||||
|
||||
|
||||
def get_activation(name) -> Callable:
|
||||
if name is None:
|
||||
return lambda x: x
|
||||
name = name.lower()
|
||||
if name == "none":
|
||||
return lambda x: x
|
||||
elif name == "lin2srgb":
|
||||
return lambda x: torch.where(
|
||||
x > 0.0031308,
|
||||
torch.pow(torch.clamp(x, min=0.0031308), 1.0 / 2.4) * 1.055 - 0.055,
|
||||
12.92 * x,
|
||||
).clamp(0.0, 1.0)
|
||||
elif name == "exp":
|
||||
return lambda x: torch.exp(x)
|
||||
elif name == "shifted_exp":
|
||||
return lambda x: torch.exp(x - 1.0)
|
||||
elif name == "trunc_exp":
|
||||
return trunc_exp
|
||||
elif name == "shifted_trunc_exp":
|
||||
return lambda x: trunc_exp(x - 1.0)
|
||||
elif name == "sigmoid":
|
||||
return lambda x: torch.sigmoid(x)
|
||||
elif name == "tanh":
|
||||
return lambda x: torch.tanh(x)
|
||||
elif name == "shifted_softplus":
|
||||
return lambda x: F.softplus(x - 1.0)
|
||||
elif name == "scale_-11_01":
|
||||
return lambda x: x * 0.5 + 0.5
|
||||
else:
|
||||
try:
|
||||
return getattr(F, name)
|
||||
except AttributeError:
|
||||
raise ValueError(f"Unknown activation function: {name}")
|
||||
|
||||
def get_ray_directions(
|
||||
H: int,
|
||||
W: int,
|
||||
focal: Union[float, Tuple[float, float]],
|
||||
principal: Optional[Tuple[float, float]] = None,
|
||||
use_pixel_centers: bool = True,
|
||||
) -> Float[Tensor, "H W 3"]:
|
||||
"""
|
||||
Get ray directions for all pixels in camera coordinate.
|
||||
Reference: https://www.scratchapixel.com/lessons/3d-basic-rendering/
|
||||
ray-tracing-generating-camera-rays/standard-coordinate-systems
|
||||
|
||||
Inputs:
|
||||
H, W, focal, principal, use_pixel_centers: image height, width, focal length, principal point and whether use pixel centers
|
||||
Outputs:
|
||||
directions: (H, W, 3), the direction of the rays in camera coordinate
|
||||
"""
|
||||
pixel_center = 0.5 if use_pixel_centers else 0
|
||||
|
||||
if isinstance(focal, float):
|
||||
fx, fy = focal, focal
|
||||
cx, cy = W / 2, H / 2
|
||||
else:
|
||||
fx, fy = focal
|
||||
assert principal is not None
|
||||
cx, cy = principal
|
||||
|
||||
i, j = torch.meshgrid(
|
||||
torch.arange(W, dtype=torch.float32) + pixel_center,
|
||||
torch.arange(H, dtype=torch.float32) + pixel_center,
|
||||
indexing="xy",
|
||||
)
|
||||
|
||||
directions: Float[Tensor, "H W 3"] = torch.stack(
|
||||
[(i - cx) / fx, -(j - cy) / fy, -torch.ones_like(i)], -1
|
||||
)
|
||||
|
||||
return directions
|
||||
|
||||
|
||||
def get_rays(
|
||||
directions: Float[Tensor, "... 3"],
|
||||
c2w: Float[Tensor, "... 4 4"],
|
||||
keepdim=False,
|
||||
noise_scale=0.0,
|
||||
) -> Tuple[Float[Tensor, "... 3"], Float[Tensor, "... 3"]]:
|
||||
# Rotate ray directions from camera coordinate to the world coordinate
|
||||
assert directions.shape[-1] == 3
|
||||
|
||||
if directions.ndim == 2: # (N_rays, 3)
|
||||
if c2w.ndim == 2: # (4, 4)
|
||||
c2w = c2w[None, :, :]
|
||||
assert c2w.ndim == 3 # (N_rays, 4, 4) or (1, 4, 4)
|
||||
rays_d = (directions[:, None, :] * c2w[:, :3, :3]).sum(-1) # (N_rays, 3)
|
||||
rays_o = c2w[:, :3, 3].expand(rays_d.shape)
|
||||
elif directions.ndim == 3: # (H, W, 3)
|
||||
assert c2w.ndim in [2, 3]
|
||||
if c2w.ndim == 2: # (4, 4)
|
||||
rays_d = (directions[:, :, None, :] * c2w[None, None, :3, :3]).sum(
|
||||
-1
|
||||
) # (H, W, 3)
|
||||
rays_o = c2w[None, None, :3, 3].expand(rays_d.shape)
|
||||
elif c2w.ndim == 3: # (B, 4, 4)
|
||||
rays_d = (directions[None, :, :, None, :] * c2w[:, None, None, :3, :3]).sum(
|
||||
-1
|
||||
) # (B, H, W, 3)
|
||||
rays_o = c2w[:, None, None, :3, 3].expand(rays_d.shape)
|
||||
elif directions.ndim == 4: # (B, H, W, 3)
|
||||
assert c2w.ndim == 3 # (B, 4, 4)
|
||||
rays_d = (directions[:, :, :, None, :] * c2w[:, None, None, :3, :3]).sum(
|
||||
-1
|
||||
) # (B, H, W, 3)
|
||||
rays_o = c2w[:, None, None, :3, 3].expand(rays_d.shape)
|
||||
|
||||
# add camera noise to avoid grid-like artifect
|
||||
# https://github.com/ashawkey/stable-dreamfusion/blob/49c3d4fa01d68a4f027755acf94e1ff6020458cc/nerf/utils.py#L373
|
||||
if noise_scale > 0:
|
||||
rays_o = rays_o + torch.randn(3, device=rays_o.device) * noise_scale
|
||||
rays_d = rays_d + torch.randn(3, device=rays_d.device) * noise_scale
|
||||
|
||||
rays_d = F.normalize(rays_d, dim=-1)
|
||||
if not keepdim:
|
||||
rays_o, rays_d = rays_o.reshape(-1, 3), rays_d.reshape(-1, 3)
|
||||
|
||||
return rays_o, rays_d
|
||||
|
||||
|
||||
def get_projection_matrix(
|
||||
fovy: Union[float, Float[Tensor, "B"]], aspect_wh: float, near: float, far: float
|
||||
) -> Float[Tensor, "*B 4 4"]:
|
||||
if isinstance(fovy, float):
|
||||
proj_mtx = torch.zeros(4, 4, dtype=torch.float32)
|
||||
proj_mtx[0, 0] = 1.0 / (math.tan(fovy / 2.0) * aspect_wh)
|
||||
proj_mtx[1, 1] = -1.0 / math.tan(
|
||||
fovy / 2.0
|
||||
) # add a negative sign here as the y axis is flipped in nvdiffrast output
|
||||
proj_mtx[2, 2] = -(far + near) / (far - near)
|
||||
proj_mtx[2, 3] = -2.0 * far * near / (far - near)
|
||||
proj_mtx[3, 2] = -1.0
|
||||
else:
|
||||
batch_size = fovy.shape[0]
|
||||
proj_mtx = torch.zeros(batch_size, 4, 4, dtype=torch.float32)
|
||||
proj_mtx[:, 0, 0] = 1.0 / (torch.tan(fovy / 2.0) * aspect_wh)
|
||||
proj_mtx[:, 1, 1] = -1.0 / torch.tan(
|
||||
fovy / 2.0
|
||||
) # add a negative sign here as the y axis is flipped in nvdiffrast output
|
||||
proj_mtx[:, 2, 2] = -(far + near) / (far - near)
|
||||
proj_mtx[:, 2, 3] = -2.0 * far * near / (far - near)
|
||||
proj_mtx[:, 3, 2] = -1.0
|
||||
return proj_mtx
|
||||
|
||||
|
||||
def get_mvp_matrix(
|
||||
c2w: Float[Tensor, "*B 4 4"], proj_mtx: Float[Tensor, "*B 4 4"]
|
||||
) -> Float[Tensor, "*B 4 4"]:
|
||||
# calculate w2c from c2w: R' = Rt, t' = -Rt * t
|
||||
# mathematically equivalent to (c2w)^-1
|
||||
if c2w.ndim == 2:
|
||||
assert proj_mtx.ndim == 2
|
||||
w2c: Float[Tensor, "4 4"] = torch.zeros(4, 4).to(c2w)
|
||||
w2c[:3, :3] = c2w[:3, :3].permute(1, 0)
|
||||
w2c[:3, 3:] = -c2w[:3, :3].permute(1, 0) @ c2w[:3, 3:]
|
||||
w2c[3, 3] = 1.0
|
||||
else:
|
||||
w2c: Float[Tensor, "B 4 4"] = torch.zeros(c2w.shape[0], 4, 4).to(c2w)
|
||||
w2c[:, :3, :3] = c2w[:, :3, :3].permute(0, 2, 1)
|
||||
w2c[:, :3, 3:] = -c2w[:, :3, :3].permute(0, 2, 1) @ c2w[:, :3, 3:]
|
||||
w2c[:, 3, 3] = 1.0
|
||||
# calculate mvp matrix by proj_mtx @ w2c (mv_mtx)
|
||||
mvp_mtx = proj_mtx @ w2c
|
||||
return mvp_mtx
|
||||
|
||||
def get_intrinsic_from_fov(fov, H, W, bs=-1):
|
||||
focal_length = 0.5 * H / np.tan(0.5 * fov)
|
||||
intrinsic = np.identity(3, dtype=np.float32)
|
||||
intrinsic[0, 0] = focal_length
|
||||
intrinsic[1, 1] = focal_length
|
||||
intrinsic[0, 2] = W / 2.0
|
||||
intrinsic[1, 2] = H / 2.0
|
||||
|
||||
if bs > 0:
|
||||
intrinsic = intrinsic[None].repeat(bs, axis=0)
|
||||
|
||||
return torch.from_numpy(intrinsic)
|
||||
|
||||
def points_projection(points: Float[Tensor, "B Np 3"],
|
||||
c2ws: Float[Tensor, "B 4 4"],
|
||||
intrinsics: Float[Tensor, "B 3 3"],
|
||||
local_features: Float[Tensor, "B C H W"],
|
||||
# Rasterization settings
|
||||
raster_point_radius: float = 0.0075, # point size
|
||||
raster_points_per_pixel: int = 1, # a single point per pixel, for now
|
||||
bin_size: int = 0):
|
||||
B, C, H, W = local_features.shape
|
||||
device = local_features.device
|
||||
raster_settings = PointsRasterizationSettings(
|
||||
image_size=(H, W),
|
||||
radius=raster_point_radius,
|
||||
points_per_pixel=raster_points_per_pixel,
|
||||
bin_size=bin_size,
|
||||
)
|
||||
Np = points.shape[1]
|
||||
R = raster_settings.points_per_pixel
|
||||
|
||||
w2cs = torch.inverse(c2ws)
|
||||
image_size = torch.as_tensor([H, W]).view(1, 2).expand(w2cs.shape[0], -1).to(device)
|
||||
cameras = cameras_from_opencv_projection(w2cs[:, :3, :3], w2cs[:, :3, 3], intrinsics, image_size)
|
||||
|
||||
rasterize = PointsRasterizer(cameras=cameras, raster_settings=raster_settings)
|
||||
fragments = rasterize(Pointclouds(points))
|
||||
fragments_idx: Tensor = fragments.idx.long()
|
||||
visible_pixels = (fragments_idx > -1) # (B, H, W, R)
|
||||
points_to_visible_pixels = fragments_idx[visible_pixels]
|
||||
|
||||
# Reshape local features to (B, H, W, R, C)
|
||||
local_features = local_features.permute(0, 2, 3, 1).unsqueeze(-2).expand(-1, -1, -1, R, -1) # (B, H, W, R, C)
|
||||
|
||||
# Get local features corresponding to visible points
|
||||
local_features_proj = torch.zeros(B * Np, C, device=device)
|
||||
local_features_proj[points_to_visible_pixels] = local_features[visible_pixels]
|
||||
local_features_proj = local_features_proj.reshape(B, Np, C)
|
||||
|
||||
return local_features_proj
|
||||
|
||||
def compute_distance_transform(mask: torch.Tensor):
|
||||
image_size = mask.shape[-1]
|
||||
distance_transform = torch.stack([
|
||||
torch.from_numpy(cv2.distanceTransform(
|
||||
(1 - m), distanceType=cv2.DIST_L2, maskSize=cv2.DIST_MASK_3
|
||||
) / (image_size / 2))
|
||||
for m in mask.squeeze(1).detach().cpu().numpy().astype(np.uint8)
|
||||
]).unsqueeze(1).clip(0, 1).to(mask.device)
|
||||
return distance_transform
|
||||
@@ -0,0 +1,314 @@
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
|
||||
import cv2
|
||||
import imageio
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import torch
|
||||
from matplotlib import cm
|
||||
from matplotlib.colors import LinearSegmentedColormap
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
from .typing import *
|
||||
|
||||
class SaverMixin:
|
||||
_save_dir: Optional[str] = None
|
||||
|
||||
def set_save_dir(self, save_dir: str):
|
||||
self._save_dir = save_dir
|
||||
|
||||
def get_save_dir(self):
|
||||
if self._save_dir is None:
|
||||
raise ValueError("Save dir is not set")
|
||||
return self._save_dir
|
||||
|
||||
def convert_data(self, data):
|
||||
if data is None:
|
||||
return None
|
||||
elif isinstance(data, np.ndarray):
|
||||
return data
|
||||
elif isinstance(data, torch.Tensor):
|
||||
return data.detach().cpu().numpy()
|
||||
elif isinstance(data, list):
|
||||
return [self.convert_data(d) for d in data]
|
||||
elif isinstance(data, dict):
|
||||
return {k: self.convert_data(v) for k, v in data.items()}
|
||||
else:
|
||||
raise TypeError(
|
||||
"Data must be in type numpy.ndarray, torch.Tensor, list or dict, getting",
|
||||
type(data),
|
||||
)
|
||||
|
||||
def get_save_path(self, filename):
|
||||
save_path = os.path.join(self.get_save_dir(), filename)
|
||||
os.makedirs(os.path.dirname(save_path), exist_ok=True)
|
||||
return save_path
|
||||
|
||||
DEFAULT_RGB_KWARGS = {"data_format": "HWC", "data_range": (0, 1)}
|
||||
DEFAULT_UV_KWARGS = {
|
||||
"data_format": "HWC",
|
||||
"data_range": (0, 1),
|
||||
"cmap": "checkerboard",
|
||||
}
|
||||
DEFAULT_GRAYSCALE_KWARGS = {"data_range": None, "cmap": "jet"}
|
||||
DEFAULT_GRID_KWARGS = {"align": "max"}
|
||||
|
||||
def get_rgb_image_(self, img, data_format, data_range, rgba=False):
|
||||
img = self.convert_data(img)
|
||||
assert data_format in ["CHW", "HWC"]
|
||||
if data_format == "CHW":
|
||||
img = img.transpose(1, 2, 0)
|
||||
if img.dtype != np.uint8:
|
||||
img = img.clip(min=data_range[0], max=data_range[1])
|
||||
img = (
|
||||
(img - data_range[0]) / (data_range[1] - data_range[0]) * 255.0
|
||||
).astype(np.uint8)
|
||||
nc = 4 if rgba else 3
|
||||
imgs = [img[..., start : start + nc] for start in range(0, img.shape[-1], nc)]
|
||||
imgs = [
|
||||
img_
|
||||
if img_.shape[-1] == nc
|
||||
else np.concatenate(
|
||||
[
|
||||
img_,
|
||||
np.zeros(
|
||||
(img_.shape[0], img_.shape[1], nc - img_.shape[2]),
|
||||
dtype=img_.dtype,
|
||||
),
|
||||
],
|
||||
axis=-1,
|
||||
)
|
||||
for img_ in imgs
|
||||
]
|
||||
img = np.concatenate(imgs, axis=1)
|
||||
if rgba:
|
||||
img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGRA)
|
||||
else:
|
||||
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
|
||||
return img
|
||||
|
||||
def _save_rgb_image(
|
||||
self,
|
||||
filename,
|
||||
img,
|
||||
data_format,
|
||||
data_range
|
||||
):
|
||||
img = self.get_rgb_image_(img, data_format, data_range)
|
||||
cv2.imwrite(filename, img)
|
||||
|
||||
def save_rgb_image(
|
||||
self,
|
||||
filename,
|
||||
img,
|
||||
data_format=DEFAULT_RGB_KWARGS["data_format"],
|
||||
data_range=DEFAULT_RGB_KWARGS["data_range"],
|
||||
) -> str:
|
||||
save_path = self.get_save_path(filename)
|
||||
self._save_rgb_image(save_path, img, data_format, data_range)
|
||||
return save_path
|
||||
|
||||
def get_grayscale_image_(self, img, data_range, cmap):
|
||||
img = self.convert_data(img)
|
||||
img = np.nan_to_num(img)
|
||||
if data_range is None:
|
||||
img = (img - img.min()) / (img.max() - img.min())
|
||||
else:
|
||||
img = img.clip(data_range[0], data_range[1])
|
||||
img = (img - data_range[0]) / (data_range[1] - data_range[0])
|
||||
assert cmap in [None, "jet", "magma", "spectral"]
|
||||
if cmap == None:
|
||||
img = (img * 255.0).astype(np.uint8)
|
||||
img = np.repeat(img[..., None], 3, axis=2)
|
||||
elif cmap == "jet":
|
||||
img = (img * 255.0).astype(np.uint8)
|
||||
img = cv2.applyColorMap(img, cv2.COLORMAP_JET)
|
||||
elif cmap == "magma":
|
||||
img = 1.0 - img
|
||||
base = cm.get_cmap("magma")
|
||||
num_bins = 256
|
||||
colormap = LinearSegmentedColormap.from_list(
|
||||
f"{base.name}{num_bins}", base(np.linspace(0, 1, num_bins)), num_bins
|
||||
)(np.linspace(0, 1, num_bins))[:, :3]
|
||||
a = np.floor(img * 255.0)
|
||||
b = (a + 1).clip(max=255.0)
|
||||
f = img * 255.0 - a
|
||||
a = a.astype(np.uint16).clip(0, 255)
|
||||
b = b.astype(np.uint16).clip(0, 255)
|
||||
img = colormap[a] + (colormap[b] - colormap[a]) * f[..., None]
|
||||
img = (img * 255.0).astype(np.uint8)
|
||||
elif cmap == "spectral":
|
||||
colormap = plt.get_cmap("Spectral")
|
||||
|
||||
def blend_rgba(image):
|
||||
image = image[..., :3] * image[..., -1:] + (
|
||||
1.0 - image[..., -1:]
|
||||
) # blend A to RGB
|
||||
return image
|
||||
|
||||
img = colormap(img)
|
||||
img = blend_rgba(img)
|
||||
img = (img * 255).astype(np.uint8)
|
||||
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
|
||||
return img
|
||||
|
||||
def _save_grayscale_image(
|
||||
self,
|
||||
filename,
|
||||
img,
|
||||
data_range,
|
||||
cmap,
|
||||
):
|
||||
img = self.get_grayscale_image_(img, data_range, cmap)
|
||||
cv2.imwrite(filename, img)
|
||||
|
||||
def save_grayscale_image(
|
||||
self,
|
||||
filename,
|
||||
img,
|
||||
data_range=DEFAULT_GRAYSCALE_KWARGS["data_range"],
|
||||
cmap=DEFAULT_GRAYSCALE_KWARGS["cmap"],
|
||||
) -> str:
|
||||
save_path = self.get_save_path(filename)
|
||||
self._save_grayscale_image(save_path, img, data_range, cmap)
|
||||
return save_path
|
||||
|
||||
def get_image_grid_(self, imgs, align):
|
||||
if isinstance(imgs[0], list):
|
||||
return np.concatenate(
|
||||
[self.get_image_grid_(row, align) for row in imgs], axis=0
|
||||
)
|
||||
cols = []
|
||||
for col in imgs:
|
||||
assert col["type"] in ["rgb", "uv", "grayscale"]
|
||||
if col["type"] == "rgb":
|
||||
rgb_kwargs = self.DEFAULT_RGB_KWARGS.copy()
|
||||
rgb_kwargs.update(col["kwargs"])
|
||||
cols.append(self.get_rgb_image_(col["img"], **rgb_kwargs))
|
||||
elif col["type"] == "uv":
|
||||
uv_kwargs = self.DEFAULT_UV_KWARGS.copy()
|
||||
uv_kwargs.update(col["kwargs"])
|
||||
cols.append(self.get_uv_image_(col["img"], **uv_kwargs))
|
||||
elif col["type"] == "grayscale":
|
||||
grayscale_kwargs = self.DEFAULT_GRAYSCALE_KWARGS.copy()
|
||||
grayscale_kwargs.update(col["kwargs"])
|
||||
cols.append(self.get_grayscale_image_(col["img"], **grayscale_kwargs))
|
||||
|
||||
if align == "max":
|
||||
h = max([col.shape[0] for col in cols])
|
||||
w = max([col.shape[1] for col in cols])
|
||||
elif align == "min":
|
||||
h = min([col.shape[0] for col in cols])
|
||||
w = min([col.shape[1] for col in cols])
|
||||
elif isinstance(align, int):
|
||||
h = align
|
||||
w = align
|
||||
elif (
|
||||
isinstance(align, tuple)
|
||||
and isinstance(align[0], int)
|
||||
and isinstance(align[1], int)
|
||||
):
|
||||
h, w = align
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Unsupported image grid align: {align}, should be min, max, int or (int, int)"
|
||||
)
|
||||
|
||||
for i in range(len(cols)):
|
||||
if cols[i].shape[0] != h or cols[i].shape[1] != w:
|
||||
cols[i] = cv2.resize(cols[i], (w, h), interpolation=cv2.INTER_LINEAR)
|
||||
return np.concatenate(cols, axis=1)
|
||||
|
||||
def save_image_grid(
|
||||
self,
|
||||
filename,
|
||||
imgs,
|
||||
align=DEFAULT_GRID_KWARGS["align"],
|
||||
texts: Optional[List[float]] = None,
|
||||
):
|
||||
save_path = self.get_save_path(filename)
|
||||
img = self.get_image_grid_(imgs, align=align)
|
||||
|
||||
if texts is not None:
|
||||
img = Image.fromarray(img)
|
||||
draw = ImageDraw.Draw(img)
|
||||
black, white = (0, 0, 0), (255, 255, 255)
|
||||
for i, text in enumerate(texts):
|
||||
draw.text((2, (img.size[1] // len(texts)) * i + 1), f"{text}", white)
|
||||
draw.text((0, (img.size[1] // len(texts)) * i + 1), f"{text}", white)
|
||||
draw.text((2, (img.size[1] // len(texts)) * i - 1), f"{text}", white)
|
||||
draw.text((0, (img.size[1] // len(texts)) * i - 1), f"{text}", white)
|
||||
draw.text((1, (img.size[1] // len(texts)) * i), f"{text}", black)
|
||||
img = np.asarray(img)
|
||||
|
||||
cv2.imwrite(save_path, img)
|
||||
return save_path
|
||||
|
||||
def save_image(self, filename, img) -> str:
|
||||
save_path = self.get_save_path(filename)
|
||||
img = self.convert_data(img)
|
||||
assert img.dtype == np.uint8 or img.dtype == np.uint16
|
||||
if img.ndim == 3 and img.shape[-1] == 3:
|
||||
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
|
||||
elif img.ndim == 3 and img.shape[-1] == 4:
|
||||
img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGRA)
|
||||
cv2.imwrite(save_path, img)
|
||||
return save_path
|
||||
|
||||
def save_img_sequence(
|
||||
self,
|
||||
filename,
|
||||
img_dir,
|
||||
matcher,
|
||||
save_format="mp4",
|
||||
fps=30,
|
||||
) -> str:
|
||||
assert save_format in ["gif", "mp4"]
|
||||
if not filename.endswith(save_format):
|
||||
filename += f".{save_format}"
|
||||
save_path = self.get_save_path(filename)
|
||||
matcher = re.compile(matcher)
|
||||
img_dir = os.path.join(self.get_save_dir(), img_dir)
|
||||
imgs = []
|
||||
for f in os.listdir(img_dir):
|
||||
if matcher.search(f):
|
||||
imgs.append(f)
|
||||
imgs = sorted(imgs, key=lambda f: int(matcher.search(f).groups()[0]))
|
||||
imgs = [cv2.imread(os.path.join(img_dir, f)) for f in imgs]
|
||||
|
||||
if save_format == "gif":
|
||||
imgs = [cv2.cvtColor(i, cv2.COLOR_BGR2RGB) for i in imgs]
|
||||
imageio.mimsave(save_path, imgs, fps=fps, palettesize=256)
|
||||
elif save_format == "mp4":
|
||||
imgs = [cv2.cvtColor(i, cv2.COLOR_BGR2RGB) for i in imgs]
|
||||
imageio.mimsave(save_path, imgs, fps=fps)
|
||||
return save_path
|
||||
|
||||
def save_img_sequences(
|
||||
self,
|
||||
seq_dir,
|
||||
matcher,
|
||||
save_format="mp4",
|
||||
fps=30,
|
||||
delete=True
|
||||
):
|
||||
seq_dir_ = os.path.join(self.get_save_dir(), seq_dir)
|
||||
for f in os.listdir(seq_dir_):
|
||||
img_dir_ = os.path.join(seq_dir_, f)
|
||||
if not os.path.isdir(img_dir_):
|
||||
continue
|
||||
try:
|
||||
self.save_img_sequence(
|
||||
os.path.join(seq_dir, f),
|
||||
os.path.join(seq_dir, f),
|
||||
matcher,
|
||||
save_format=save_format,
|
||||
fps=fps
|
||||
)
|
||||
except:
|
||||
raise ValueError(f"Video saving for directory {seq_dir_} failed!")
|
||||
|
||||
if delete:
|
||||
shutil.rmtree(img_dir_)
|
||||
@@ -0,0 +1,40 @@
|
||||
"""
|
||||
This module contains type annotations for the project, using
|
||||
1. Python type hints (https://docs.python.org/3/library/typing.html) for Python objects
|
||||
2. jaxtyping (https://github.com/google/jaxtyping/blob/main/API.md) for PyTorch tensors
|
||||
|
||||
Two types of typing checking can be used:
|
||||
1. Static type checking with mypy (install with pip and enabled as the default linter in VSCode)
|
||||
2. Runtime type checking with typeguard (install with pip and triggered at runtime, mainly for tensor dtype and shape checking)
|
||||
"""
|
||||
|
||||
# Basic types
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
List,
|
||||
Literal,
|
||||
NamedTuple,
|
||||
NewType,
|
||||
Optional,
|
||||
Sized,
|
||||
Tuple,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
||||
# Tensor dtype
|
||||
# for jaxtyping usage, see https://github.com/google/jaxtyping/blob/main/API.md
|
||||
from jaxtyping import Bool, Complex, Float, Inexact, Int, Integer, Num, Shaped, UInt
|
||||
|
||||
# Config type
|
||||
from omegaconf import DictConfig
|
||||
|
||||
# PyTorch Tensor type
|
||||
from torch import Tensor
|
||||
|
||||
# Runtime type checking decorator
|
||||
from typeguard import typechecked as typechecker
|
||||
Reference in New Issue
Block a user