asmadeyi commited on
Commit
c29de8d
·
1 Parent(s): 638ce77

Upload 72 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +1 -0
  2. neuralangelo-main/.DS_Store +0 -0
  3. neuralangelo-main/.gitignore +199 -0
  4. neuralangelo-main/.gitmodules +3 -0
  5. neuralangelo-main/.pre-commit-config.yaml +7 -0
  6. neuralangelo-main/DATA_PROCESSING.md +126 -0
  7. neuralangelo-main/LICENSE.md +55 -0
  8. neuralangelo-main/README.md +79 -0
  9. neuralangelo-main/assets/teaser.gif +3 -0
  10. neuralangelo-main/docker/Dockerfile-colmap +47 -0
  11. neuralangelo-main/docker/Dockerfile-neuralangelo +33 -0
  12. neuralangelo-main/imaginaire/config.py +223 -0
  13. neuralangelo-main/imaginaire/config_base.yaml +115 -0
  14. neuralangelo-main/imaginaire/datasets/base.py +603 -0
  15. neuralangelo-main/imaginaire/datasets/utils/dataloader.py +49 -0
  16. neuralangelo-main/imaginaire/datasets/utils/get_dataloader.py +181 -0
  17. neuralangelo-main/imaginaire/datasets/utils/sampler.py +117 -0
  18. neuralangelo-main/imaginaire/models/base.py +30 -0
  19. neuralangelo-main/imaginaire/models/utils/init_weight.py +91 -0
  20. neuralangelo-main/imaginaire/models/utils/model_average.py +113 -0
  21. neuralangelo-main/imaginaire/trainers/base.py +685 -0
  22. neuralangelo-main/imaginaire/trainers/utils/get_trainer.py +223 -0
  23. neuralangelo-main/imaginaire/trainers/utils/logging.py +72 -0
  24. neuralangelo-main/imaginaire/trainers/utils/meters.py +147 -0
  25. neuralangelo-main/imaginaire/utils/cudnn.py +30 -0
  26. neuralangelo-main/imaginaire/utils/distributed.py +153 -0
  27. neuralangelo-main/imaginaire/utils/gpu_affinity.py +78 -0
  28. neuralangelo-main/imaginaire/utils/misc.py +376 -0
  29. neuralangelo-main/imaginaire/utils/set_random_seed.py +36 -0
  30. neuralangelo-main/imaginaire/utils/termcolor.py +43 -0
  31. neuralangelo-main/imaginaire/utils/visualization.py +41 -0
  32. neuralangelo-main/neuralangelo.yaml +26 -0
  33. neuralangelo-main/projects/nerf/configs/ingp_blender.yaml +41 -0
  34. neuralangelo-main/projects/nerf/configs/nerf_blender.yaml +101 -0
  35. neuralangelo-main/projects/nerf/configs/nerf_llff.yaml +64 -0
  36. neuralangelo-main/projects/nerf/datasets/base.py +55 -0
  37. neuralangelo-main/projects/nerf/datasets/nerf_blender.py +114 -0
  38. neuralangelo-main/projects/nerf/datasets/nerf_llff.py +140 -0
  39. neuralangelo-main/projects/nerf/models/ingp.py +77 -0
  40. neuralangelo-main/projects/nerf/models/nerf.py +251 -0
  41. neuralangelo-main/projects/nerf/trainers/base.py +159 -0
  42. neuralangelo-main/projects/nerf/trainers/nerf.py +109 -0
  43. neuralangelo-main/projects/nerf/utils/camera.py +514 -0
  44. neuralangelo-main/projects/nerf/utils/misc.py +71 -0
  45. neuralangelo-main/projects/nerf/utils/nerf_util.py +270 -0
  46. neuralangelo-main/projects/nerf/utils/render.py +112 -0
  47. neuralangelo-main/projects/nerf/utils/visualize.py +120 -0
  48. neuralangelo-main/projects/neuralangelo/configs/base.yaml +147 -0
  49. neuralangelo-main/projects/neuralangelo/configs/custom/template.yaml +40 -0
  50. neuralangelo-main/projects/neuralangelo/configs/dtu.yaml +37 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ neuralangelo-main/assets/teaser.gif filter=lfs diff=lfs merge=lfs -text
neuralangelo-main/.DS_Store ADDED
Binary file (6.15 kB). View file
 
neuralangelo-main/.gitignore ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ checkpoints
2
+
3
+ # Other uncheckable file types
4
+ *.zip
5
+ *.exe
6
+ *.dll
7
+ *.swp
8
+ *.vscode
9
+ *.ipynb
10
+ *.DS_Store
11
+ *.pyc
12
+
13
+ # Credential information that should never be checked in
14
+ *.secret
15
+
16
+ _datasets/
17
+ scripts/wandb/_staff/
18
+
19
+ # Data types
20
+ *.png
21
+ *.hdr
22
+ *.jpg
23
+ *.jpeg
24
+ *.pgm
25
+ *.tiff
26
+ *.tif
27
+ *.mp4
28
+ *.tar
29
+ *.tar.gz
30
+ *.pkl
31
+ *.pt
32
+ *.bin
33
+
34
+ # log folder
35
+ logs/
36
+
37
+ # ------------------------ BELOW IS AUTO-GENERATED FOR PYTHON REPOS ------------------------
38
+
39
+ # Byte-compiled / optimized / DLL files
40
+ __pycache__/
41
+ *.py[cod]
42
+ *$py.class
43
+
44
+ # C extensions
45
+ *.so
46
+
47
+ # Distribution / packaging
48
+ .Python
49
+ build/
50
+ develop-eggs/
51
+ dist/
52
+ downloads/
53
+ eggs/
54
+ .eggs/
55
+ lib/
56
+ lib64/
57
+ parts/
58
+ sdist/
59
+ var/
60
+ wheels/
61
+ share/python-wheels/
62
+ *.egg-info/
63
+ .installed.cfg
64
+ *.egg
65
+ MANIFEST
66
+
67
+ # PyInstaller
68
+ # Usually these files are written by a python script from a template
69
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
70
+ *.manifest
71
+ *.spec
72
+
73
+ # Installer logs
74
+ pip-log.txt
75
+ pip-delete-this-directory.txt
76
+
77
+ # Unit test / coverage reports
78
+ htmlcov/
79
+ .tox/
80
+ .nox/
81
+ .coverage
82
+ .coverage.*
83
+ .cache
84
+ nosetests.xml
85
+ coverage.xml
86
+ *.cover
87
+ *.py,cover
88
+ .hypothesis/
89
+ .pytest_cache/
90
+ cover/
91
+
92
+ # Translations
93
+ *.mo
94
+ *.pot
95
+
96
+ # Django stuff:
97
+ *.log
98
+ local_settings.py
99
+ db.sqlite3
100
+ db.sqlite3-journal
101
+
102
+ # Flask stuff:
103
+ instance/
104
+ .webassets-cache
105
+
106
+ # Scrapy stuff:
107
+ .scrapy
108
+
109
+ # Sphinx documentation
110
+ docs/_build/
111
+
112
+ # PyBuilder
113
+ .pybuilder/
114
+ target/
115
+
116
+ # Jupyter Notebook
117
+ .ipynb_checkpoints
118
+
119
+ # IPython
120
+ profile_default/
121
+ ipython_config.py
122
+
123
+ # pyenv
124
+ # For a library or package, you might want to ignore these files since the code is
125
+ # intended to run in multiple environments; otherwise, check them in:
126
+ # .python-version
127
+
128
+ # pipenv
129
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
130
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
131
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
132
+ # install all needed dependencies.
133
+ #Pipfile.lock
134
+
135
+ # poetry
136
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
137
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
138
+ # commonly ignored for libraries.
139
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
140
+ #poetry.lock
141
+
142
+ # pdm
143
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
144
+ #pdm.lock
145
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
146
+ # in version control.
147
+ # https://pdm.fming.dev/#use-with-ide
148
+ .pdm.toml
149
+
150
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
151
+ __pypackages__/
152
+
153
+ # Celery stuff
154
+ celerybeat-schedule
155
+ celerybeat.pid
156
+
157
+ # SageMath parsed files
158
+ *.sage.py
159
+
160
+ # Environments
161
+ .env
162
+ .venv
163
+ env/
164
+ venv/
165
+ ENV/
166
+ env.bak/
167
+ venv.bak/
168
+
169
+ # Spyder project settings
170
+ .spyderproject
171
+ .spyproject
172
+
173
+ # Rope project settings
174
+ .ropeproject
175
+
176
+ # mkdocs documentation
177
+ /site
178
+
179
+ # mypy
180
+ .mypy_cache/
181
+ .dmypy.json
182
+ dmypy.json
183
+
184
+ # Pyre type checker
185
+ .pyre/
186
+
187
+ # pytype static type analyzer
188
+ .pytype/
189
+
190
+ # Cython debug symbols
191
+ cython_debug/
192
+
193
+ # PyCharm
194
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
195
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
196
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
197
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
198
+ #.idea/
199
+ CLIP
neuralangelo-main/.gitmodules ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ [submodule "third_party/colmap"]
2
+ path = third_party/colmap
3
+ url = https://github.com/colmap/colmap.git
neuralangelo-main/.pre-commit-config.yaml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ repos:
2
+ - repo: https://github.com/pycqa/flake8
3
+ rev: 4.0.0
4
+ hooks:
5
+ - id: flake8
6
+ args: [--max-line-length=120]
7
+ exclude: third_party
neuralangelo-main/DATA_PROCESSING.md ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Data Preparation
2
+
3
+ The following sections provide a step-by-step guide on how to convert a video to a json file that Neuralangelo parses.
4
+
5
+ ## Prerequisites
6
+ Initialize the COLMAP submodule:
7
+ ```bash
8
+ git submodule update --init --recursive
9
+ ```
10
+
11
+ ## Self-captured video sequence
12
+ To capture your own data, we recommend using a high-shutter speed to avoid motion blur (which is very common when using a phone camera). To follow the instructions below, you can download a toy example from the link: https://drive.google.com/file/d/1VJeWYNJEBK0MFIzHjI8xZzwf3_I3eLwH/view?usp=drive_link
13
+
14
+ ### Preprocessing
15
+ You can run the following command to preprocess your data:
16
+
17
+ ```bash
18
+ EXPERIMENT_NAME=toy_example
19
+ PATH_TO_VIDEO=toy_example.MOV
20
+ SKIP_FRAME_RATE=24
21
+ SCENE_TYPE=object # {outdoor,indoor,object}
22
+ bash projects/neuralangelo/scripts/preprocess.sh ${EXPERIMENT_NAME} ${PATH_TO_VIDEO} ${SKIP_FRAME_RATE} ${SCENE_TYPE}
23
+ ```
24
+
25
+ Alternatively, you can follow the steps below if you want more fine-grained control.
26
+
27
+ 1. Convert video to images
28
+
29
+ ```bash
30
+ PATH_TO_VIDEO=toy_example.MOV
31
+ SKIP_FRAME_RATE=30
32
+ bash projects/neuralangelo/scripts/run_ffmpeg.sh ${PATH_TO_VIDEO} ${SKIP_FRAME_RATE}
33
+ ```
34
+ `PATH_TO_VIDEO`: path to video
35
+ `SKIP_FRAME_RATE`: downsampling rate (recommended 10 for 24 fps captured videos)
36
+
37
+ 2. Run COLMAP
38
+
39
+ ```bash
40
+ PATH_TO_IMAGES=toy_example_skip30
41
+ bash projects/neuralangelo/scripts/run_colmap.sh ${PATH_TO_IMAGES}
42
+ ```
43
+ `PATH_TO_IMAGES`: path to extracted images
44
+
45
+ After COLMAP finishes, the folder structure will look like following:
46
+ ```bash
47
+ PATH_TO_IMAGES
48
+ |__ database.db (COLMAP databse)
49
+ |__ raw_images (raw input images)
50
+ |__ dense
51
+ |____ images (undistorted images)
52
+ |____ sparse (COLMAP correspondences, intrinsics and sparse point cloud)
53
+ |____ stereo (COLMAP files for MVS)
54
+ ```
55
+ `dense/images` will be the input for surface reconstruction.
56
+
57
+ 3. Generate json file for data loading
58
+
59
+ In this step, we define the bounding region for reconstruction and convert the COLMAP data to json format following Instant NGP. We strongly recommend you go to step 5 to validate the quality of the automatic bounding region extraction for improved performance.
60
+
61
+ ```bash
62
+ PATH_TO_IMAGES=toy_example_skip30
63
+ SCENE_TYPE=object # {outdoor,indoor,object}
64
+ python3 projects/neuralangelo/scripts/convert_data_to_json.py --data_dir ${PATH_TO_IMAGES}/dense --scene_type ${SCENE_TYPE}
65
+ ```
66
+ `PATH_TO_IMAGES`: path to extracted images
67
+
68
+ 4. Config files
69
+
70
+ Use the following to configure and generate your config files
71
+ ```bash
72
+ EXPERIMENT_NAME=toy_example
73
+ SCENE_TYPE=object # {outdoor,indoor,object}
74
+ python3 projects/neuralangelo/scripts/generate_config.py --experiment_name ${EXPERIMENT_NAME} --data_dir ${PATH_TO_IMAGES}/dense --scene_type ${SCENE_TYPE} --auto_exposure_wb
75
+ ```
76
+ The config file will be generated as `projects/neuralangelo/configs/custom/{EXPERIMENT_NAME}.yaml`.
77
+
78
+ To find more arguments and how they work:
79
+ ```bash
80
+ python3 projects/neuralangelo/scripts/generate_config.py -h
81
+ ```
82
+ You can also manually adjust the parameters in the yaml file directly.
83
+
84
+ 5. Inspect results in Blender (optional but recommended)
85
+
86
+ For certain cases, the camera poses estimated by COLMAP could be wrong, and the bounding region estimation could be off.
87
+ We offer some tools to to inspect the pre-processing results. Below are some options:
88
+
89
+ - Blender: Download [Blender](https://www.blender.org/download/) and follow the instructions in our [add-on repo](https://github.com/mli0603/BlenderNeuralangelo).
90
+ - This [Jupyter notebook](projects/neuralangelo/scripts/visualize_colmap.ipynb) (using K3D) can be helpful for visualizing the COLMAP results.
91
+
92
+ ## DTU dataset
93
+ - Please use respecting the license terms of the dataset.
94
+
95
+ You can run the following command to download [the DTU dataset](https://roboimagedata.compute.dtu.dk/?page_id=36) that is preprocessed by NeuS authors and generate json files:
96
+ ```bash
97
+ PATH_TO_DTU=datasets/dtu # Modify this to be the DTU dataset root directory.
98
+ bash projects/neuralangelo/scripts/preprocess_dtu.sh ${PATH_TO_DTU}
99
+ ```
100
+
101
+ ## Tanks and Temples dataset
102
+ - Please use respecting the license terms of the dataset.
103
+
104
+ Download the data from [Tanks and Temples](https://tanksandtemples.org/download/) website.
105
+ You will also need to download additional [COLMAP/camera/alignment](https://drive.google.com/file/d/1jAr3IDvhVmmYeDWi0D_JfgiHcl70rzVE/view?resourcekey=) and the images of each scene.
106
+ The file structure should look like (you may need to move around the downloaded images):
107
+ ```
108
+ tanks_and_temples
109
+ ├─ Barn
110
+ │ ├─ Barn_COLMAP_SfM.log (camera poses)
111
+ │ ├─ Barn.json (cropfiles)
112
+ │ ├─ Barn.ply (ground-truth point cloud)
113
+ │ ├─ Barn_trans.txt (colmap-to-ground-truth transformation)
114
+ │ └─ images (folder of images)
115
+ │ ├─ 000001.png
116
+ │ ├─ 000002.png
117
+ │ ...
118
+ ├─ Caterpillar
119
+ │ ├─ ...
120
+ ...
121
+ ```
122
+ Run the following command to generate json files:
123
+ ```bash
124
+ PATH_TO_TNT=datasets/tanks_and_temples # Modify this to be the Tanks and Temples root directory.
125
+ bash projects/neuralangelo/scripts/preprocess_tnt.sh ${PATH_TO_TNT}
126
+ ```
neuralangelo-main/LICENSE.md ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # NVIDIA Source Code License for Neuralangelo
2
+
3
+ ## 1. Definitions
4
+
5
+ - “Licensor” means any person or entity that distributes its Work.
6
+
7
+ - “Software” means the original work of authorship made available under this License.
8
+
9
+ - “Work” means the Software and any additions to or derivative works of the Software that are made available under this License.
10
+
11
+ - “NVIDIA Processors” means any central processing unit (CPU), graphics processing unit (GPU), field-programmable gate array (FPGA), application-specific integrated circuit (ASIC) or any combination thereof designed, made, sold, or provided by NVIDIA or its affiliates.
12
+
13
+ - The terms “reproduce,” “reproduction,” “derivative works,” and “distribution” have the meaning as provided under U.S. copyright law; provided, however, that for the purposes of this License, derivative works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work.
14
+
15
+ - Works, including the Software, are “made available” under this License by including in or with the Work either (a) a copyright notice referencing the applicability of this License to the Work, or (b) a copy of this License.
16
+
17
+ ## 2. License Grant
18
+
19
+ ### 2.1 Copyright Grant.
20
+
21
+ Subject to the terms and conditions of this License, each Licensor grants to you a perpetual, worldwide, non-exclusive, royalty-free, copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense and distribute its Work and any resulting derivative works in any form.
22
+
23
+ ## 3. Limitations
24
+
25
+ ### 3.1 Redistribution.
26
+
27
+ You may reproduce or distribute the Work only if (a) you do so under this License, (b) you include a complete copy of this License with your distribution, and (c) you retain without modification any copyright, patent, trademark, or attribution notices that are present in the Work.
28
+
29
+ ### 3.2 Derivative Works.
30
+
31
+ You may specify that additional or different terms apply to the use, reproduction, and distribution of your derivative works of the Work (“Your Terms”) only if (a) Your Terms provide that the use limitation in Section 3.3 applies to your derivative works, and (b) you identify the specific derivative works that are subject to Your Terms. Notwithstanding Your Terms, this License (including the redistribution requirements in Section 3.1) will continue to apply to the Work itself.
32
+
33
+ ### 3.3 Use Limitation.
34
+
35
+ The Work and any derivative works thereof only may be used or intended for use non-commercially and with NVIDIA Processors. Notwithstanding the foregoing, NVIDIA and its affiliates may use the Work and any derivative works commercially. As used herein, “non-commercially” means for research or evaluation purposes only.
36
+
37
+ ### 3.4 Patent Claims.
38
+
39
+ If you bring or threaten to bring a patent claim against any Licensor (including any claim, cross-claim or counterclaim in a lawsuit) to enforce any patents that you allege are infringed by any Work, then your rights under this License from such Licensor (including the grant in Section 2.1) will terminate immediately.
40
+
41
+ ### 3.5 Trademarks.
42
+
43
+ This License does not grant any rights to use any Licensor’s or its affiliates’ names, logos, or trademarks, except as necessary to reproduce the notices described in this License.
44
+
45
+ ### 3.6 Termination.
46
+
47
+ If you violate any term of this License, then your rights under this License (including the grant in Section 2.1) will terminate immediately.
48
+
49
+ ## 4. Disclaimer of Warranty.
50
+
51
+ THE WORK IS PROVIDED “AS IS” WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WARRANTIES OR CONDITIONS OF M ERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE OR NON-INFRINGEMENT. YOU BEAR THE RISK OF UNDERTAKING ANY ACTIVITIES UNDER THIS LICENSE.
52
+
53
+ ## 5. Limitation of Liability.
54
+
55
+ EXCEPT AS PROHIBITED BY APPLICABLE LAW, IN NO EVENT AND UNDER NO LEGAL THEORY, WHETHER IN TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE SHALL ANY LICENSOR BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATED TO THIS LICENSE, THE USE OR INABILITY TO USE THE WORK (INCLUDING BUT NOT LIMITED TO LOSS OF GOODWILL, BUSINESS INTERRUPTION, LOST PROFITS OR DATA, COMPUTER FAILURE OR MALFUNCTION, OR ANY OTHER COMM ERCIAL DAMAGES OR LOSSES), EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
neuralangelo-main/README.md ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Neuralangelo
2
+
3
+ ## [Project Page](https://research.nvidia.com/labs/dir/neuralangelo/) | [Paper](https://arxiv.org/abs/2306.03092/)
4
+ This is the official repo for the implementation of **Neuralangelo: High-Fidelity Neural Surface Reconstruction**.
5
+ The code is built upon the Imaginaire library from the Deep Imagination Research Group at NVIDIA.
6
+
7
+ <img src="assets/teaser.gif">
8
+
9
+ For business inquiries, please submit the [NVIDIA research licensing form](https://www.nvidia.com/en-us/research/inquiries/).
10
+
11
+ ## Installation
12
+ We offer two ways to setup the environment:
13
+ 1. We provide prebuilt Docker images, where
14
+ - `docker.io/chenhsuanlin/colmap:3.9` is for running COLMAP and the data preprocessing scripts. This includes the prebuilt COLMAP library (CUDA-supported).
15
+ - `docker.io/chenhsuanlin/neuralangelo:23.04-py3` is for running the main Neuralangelo pipeline.
16
+
17
+ The corresponding Dockerfiles can be found in the `docker` directory.
18
+ 2. The conda environment for Neuralangelo. Install the dependencies and activate the environment `neuralangelo` with
19
+ ```bash
20
+ conda env create --file neuralangelo.yaml
21
+ conda activate neuralangelo
22
+ ```
23
+ For COLMAP, alternative installation options are also available on the [COLMAP website](https://colmap.github.io/).
24
+
25
+ ## Data preparation
26
+ Please refer to [Data Preparation](DATA_PROCESSING.md) for step-by-step instructions.
27
+ We assume known camera poses for each extracted frame from the video.
28
+ The code uses the same json format as [Instant NGP](https://github.com/NVlabs/instant-ngp).
29
+
30
+ ## Run Neuralangelo!
31
+ ```bash
32
+ EXPERIMENT=toy_example
33
+ GROUP=example_group
34
+ NAME=example_name
35
+ CONFIG=projects/neuralangelo/configs/custom/${EXPERIMENT}.yaml
36
+ GPUS=1 # use >1 for multi-GPU training!
37
+ torchrun --nproc_per_node=${GPUS} train.py \
38
+ --logdir=logs/${GROUP}/${NAME} \
39
+ --config=${CONFIG} \
40
+ --show_pbar
41
+ ```
42
+ Some useful notes:
43
+ - This codebase supports logging with [Weights & Biases](https://wandb.ai/site). You should have a W&B account for this.
44
+ - Add `--wandb` to the command line argument to enable W&B logging.
45
+ - Add `--wandb_name` to specify the W&B project name.
46
+ - More detailed control can be found in the `init_wandb()` function in `imaginaire/trainers/base.py`.
47
+ - Configs can be overridden through the command line (e.g. `--optim.params.lr=1e-2`).
48
+ - Set `--checkpoint={CHECKPOINT_PATH}` to initialize with a certain checkpoint; set `--resume=True` to resume training.
49
+ - If appearance embeddings are enabled, make sure `data.num_images` is set to the number of training images.
50
+
51
+ ## Isosurface extraction
52
+ Use the following command to run isosurface mesh extraction:
53
+ ```bash
54
+ CHECKPOINT=logs/${GROUP}/${NAME}/xxx.pt
55
+ OUTPUT_MESH=xxx.ply
56
+ CONFIG=projects/neuralangelo/configs/custom/${EXPERIMENT}.yaml
57
+ RESOLUTION=2048
58
+ BLOCK_RES=128
59
+ GPUS=1 # use >1 for multi-GPU mesh extraction
60
+ torchrun --nproc_per_node=${GPUS} projects/neuralangelo/scripts/extract_mesh.py \
61
+ --logdir=logs/${GROUP}/${NAME} \
62
+ --config=${CONFIG} \
63
+ --checkpoint=${CHECKPOINT} \
64
+ --output_file=${OUTPUT_MESH} \
65
+ --resolution=${RESOLUTION} \
66
+ --block_res=${BLOCK_RES}
67
+ ```
68
+
69
+ --------------------------------------
70
+
71
+ If you find our code useful for your research, please cite
72
+ ```
73
+ @inproceedings{li2023neuralangelo,
74
+ title={Neuralangelo: High-Fidelity Neural Surface Reconstruction},
75
+ author={Li, Zhaoshuo and M\"uller, Thomas and Evans, Alex and Taylor, Russell H and Unberath, Mathias and Liu, Ming-Yu and Lin, Chen-Hsuan},
76
+ booktitle={IEEE Conference on Computer Vision and Pattern Recognition ({CVPR})},
77
+ year={2023}
78
+ }
79
+ ```
neuralangelo-main/assets/teaser.gif ADDED

Git LFS Details

  • SHA256: 34ac48ce705c8301950957e5f6089d0ba8adadfa41301d2a37837c045b5224f6
  • Pointer size: 133 Bytes
  • Size of remote file: 15.5 MB
neuralangelo-main/docker/Dockerfile-colmap ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # docker build -f docker/Dockerfile-colmap -t chenhsuanlin/colmap:3.9 .
2
+ # docker push chenhsuanlin/colmap:3.9
3
+
4
+ FROM nvcr.io/nvidia/cuda:11.8.0-devel-ubuntu20.04
5
+ ARG DEBIAN_FRONTEND=noninteractive
6
+
7
+ # colmap dependencies
8
+ RUN apt-get update && apt-get install -y \
9
+ git \
10
+ cmake \
11
+ ninja-build \
12
+ build-essential \
13
+ libboost-program-options-dev \
14
+ libboost-filesystem-dev \
15
+ libboost-graph-dev \
16
+ libboost-system-dev \
17
+ libboost-test-dev \
18
+ libeigen3-dev \
19
+ libflann-dev \
20
+ libfreeimage-dev \
21
+ libmetis-dev \
22
+ libgoogle-glog-dev \
23
+ libgflags-dev \
24
+ libsqlite3-dev \
25
+ libglew-dev \
26
+ qtbase5-dev \
27
+ libqt5opengl5-dev \
28
+ libcgal-dev \
29
+ libceres-dev
30
+ # headless servers
31
+ RUN apt-get update && apt-get install -y \
32
+ xvfb
33
+ # Colmap
34
+ RUN git clone https://github.com/colmap/colmap.git && cd colmap
35
+ RUN cd colmap && mkdir build && cd build && cmake .. -DCUDA_ENABLED=ON -DCMAKE_CUDA_ARCHITECTURES="70;72;75;80;86" -GNinja
36
+ RUN cd colmap/build && ninja && ninja install
37
+
38
+ # additional python packages
39
+ RUN apt-get update && apt-get install -y \
40
+ pip \
41
+ ffmpeg
42
+ RUN pip install \
43
+ addict \
44
+ opencv-python-headless \
45
+ pillow \
46
+ pyyaml \
47
+ trimesh
neuralangelo-main/docker/Dockerfile-neuralangelo ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # docker build -f docker/Dockerfile-neuralangelo -t chenhsuanlin/neuralangelo:23.04-py3 .
2
+ # docker push chenhsuanlin/neuralangelo:23.04-py3
3
+
4
+ FROM nvcr.io/nvidia/pytorch:23.04-py3
5
+ ARG DEBIAN_FRONTEND=noninteractive
6
+
7
+ # Install basics
8
+ RUN apt-get update && apt-get install -y --no-install-recommends \
9
+ build-essential \
10
+ bzip2 \
11
+ ca-certificates \
12
+ cmake \
13
+ curl \
14
+ ffmpeg \
15
+ g++ \
16
+ git \
17
+ libx264-dev \
18
+ tmux \
19
+ wget
20
+
21
+ # Update pip
22
+ RUN pip install --upgrade pip
23
+
24
+ # Code formatting
25
+ RUN pip install --upgrade \
26
+ flake8 \
27
+ pre-commit
28
+
29
+ # Install base Python libraries for Imaginaire
30
+ COPY requirements.txt requirements.txt
31
+ ARG FORCE_CUDA=1
32
+ ARG TCNN_CUDA_ARCHITECTURES=70,72,75,80,86
33
+ RUN pip install --upgrade -r requirements.txt
neuralangelo-main/imaginaire/config.py ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import collections
14
+ import functools
15
+ import os
16
+ import re
17
+
18
+ import yaml
19
+ from imaginaire.utils.distributed import master_only_print as print
20
+ from imaginaire.utils.termcolor import cyan, green, yellow
21
+
22
+ DEBUG = False
23
+ USE_JIT = False
24
+
25
+
26
+ class AttrDict(dict):
27
+ """Dict as attribute trick."""
28
+
29
+ def __init__(self, *args, **kwargs):
30
+ super(AttrDict, self).__init__(*args, **kwargs)
31
+ self.__dict__ = self
32
+ for key, value in self.__dict__.items():
33
+ if isinstance(value, dict):
34
+ self.__dict__[key] = AttrDict(value)
35
+ elif isinstance(value, (list, tuple)):
36
+ if value and isinstance(value[0], dict):
37
+ self.__dict__[key] = [AttrDict(item) for item in value]
38
+ else:
39
+ self.__dict__[key] = value
40
+
41
+ def yaml(self):
42
+ """Convert object to yaml dict and return."""
43
+ yaml_dict = {}
44
+ for key, value in self.__dict__.items():
45
+ if isinstance(value, AttrDict):
46
+ yaml_dict[key] = value.yaml()
47
+ elif isinstance(value, list):
48
+ if value and isinstance(value[0], AttrDict):
49
+ new_l = []
50
+ for item in value:
51
+ new_l.append(item.yaml())
52
+ yaml_dict[key] = new_l
53
+ else:
54
+ yaml_dict[key] = value
55
+ else:
56
+ yaml_dict[key] = value
57
+ return yaml_dict
58
+
59
+ def __repr__(self):
60
+ """Print all variables."""
61
+ ret_str = []
62
+ for key, value in self.__dict__.items():
63
+ if isinstance(value, AttrDict):
64
+ ret_str.append('{}:'.format(key))
65
+ child_ret_str = value.__repr__().split('\n')
66
+ for item in child_ret_str:
67
+ ret_str.append(' ' + item)
68
+ elif isinstance(value, list):
69
+ if value and isinstance(value[0], AttrDict):
70
+ ret_str.append('{}:'.format(key))
71
+ for item in value:
72
+ # Treat as AttrDict above.
73
+ child_ret_str = item.__repr__().split('\n')
74
+ for item in child_ret_str:
75
+ ret_str.append(' ' + item)
76
+ else:
77
+ ret_str.append('{}: {}'.format(key, value))
78
+ else:
79
+ ret_str.append('{}: {}'.format(key, value))
80
+ return '\n'.join(ret_str)
81
+
82
+
83
+ class Config(AttrDict):
84
+ r"""Configuration class. This should include every human specifiable
85
+ hyperparameter values for your training."""
86
+
87
+ def __init__(self, filename=None, verbose=False):
88
+ super(Config, self).__init__()
89
+ self.source_filename = filename
90
+
91
+ # Load the base configuration file.
92
+ base_filename = os.path.join(
93
+ os.path.dirname(__file__), '../imaginaire/config_base.yaml'
94
+ )
95
+ cfg_base = self.load_config(base_filename)
96
+ recursive_update(self, cfg_base)
97
+
98
+ # Update with given configurations.
99
+ cfg_dict = self.load_config(filename)
100
+ recursive_update(self, cfg_dict)
101
+
102
+ if verbose:
103
+ print(' imaginaire config '.center(80, '-'))
104
+ print(self.__repr__())
105
+ print(''.center(80, '-'))
106
+
107
+ def load_config(self, filename):
108
+ # Update with given configurations.
109
+ assert os.path.exists(filename), f'File {filename} not exist.'
110
+ yaml_loader = yaml.SafeLoader
111
+ yaml_loader.add_implicit_resolver(
112
+ u'tag:yaml.org,2002:float',
113
+ re.compile(u'''^(?:
114
+ [-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+]?[0-9]+)?
115
+ |[-+]?(?:[0-9][0-9_]*)(?:[eE][-+]?[0-9]+)
116
+ |\\.[0-9_]+(?:[eE][-+][0-9]+)?
117
+ |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*
118
+ |[-+]?\\.(?:inf|Inf|INF)
119
+ |\\.(?:nan|NaN|NAN))$''', re.X),
120
+ list(u'-+0123456789.'))
121
+ try:
122
+ with open(filename) as file:
123
+ cfg_dict = yaml.load(file, Loader=yaml_loader)
124
+ cfg_dict = AttrDict(cfg_dict)
125
+ except EnvironmentError:
126
+ print(f'Please check the file with name of "{filename}"')
127
+ # Inherit configurations from parent
128
+ parent_key = "_parent_"
129
+ if parent_key in cfg_dict:
130
+ parent_filename = cfg_dict.pop(parent_key)
131
+ cfg_parent = self.load_config(parent_filename)
132
+ recursive_update(cfg_parent, cfg_dict)
133
+ cfg_dict = cfg_parent
134
+ return cfg_dict
135
+
136
+ def print_config(self, level=0):
137
+ """Recursively print the configuration (with termcolor)."""
138
+ for key, value in sorted(self.items()):
139
+ if isinstance(value, (dict, Config)):
140
+ print(" " * level + cyan("* ") + green(key) + ":")
141
+ Config.print_config(value, level + 1)
142
+ else:
143
+ print(" " * level + cyan("* ") + green(key) + ":", yellow(value))
144
+
145
+ def save_config(self, logdir):
146
+ """Save the final configuration to a yaml file."""
147
+ cfg_fname = f"{logdir}/config.yaml"
148
+ with open(cfg_fname, "w") as file:
149
+ yaml.safe_dump(self.yaml(), file, default_flow_style=False, indent=4)
150
+
151
+
152
+ def rsetattr(obj, attr, val):
153
+ """Recursively find object and set value"""
154
+ pre, _, post = attr.rpartition('.')
155
+ return setattr(rgetattr(obj, pre) if pre else obj, post, val)
156
+
157
+
158
+ def rgetattr(obj, attr, *args):
159
+ """Recursively find object and return value"""
160
+
161
+ def _getattr(obj, attr):
162
+ r"""Get attribute."""
163
+ return getattr(obj, attr, *args)
164
+
165
+ return functools.reduce(_getattr, [obj] + attr.split('.'))
166
+
167
+
168
+ def recursive_update(d, u):
169
+ """Recursively update AttrDict d with AttrDict u"""
170
+ for key, value in u.items():
171
+ if isinstance(value, collections.abc.Mapping):
172
+ d.__dict__[key] = recursive_update(d.get(key, AttrDict({})), value)
173
+ elif isinstance(value, (list, tuple)):
174
+ if value and isinstance(value[0], dict):
175
+ d.__dict__[key] = [AttrDict(item) for item in value]
176
+ else:
177
+ d.__dict__[key] = value
178
+ else:
179
+ d.__dict__[key] = value
180
+ return d
181
+
182
+
183
+ def recursive_update_strict(d, u, stack=[]):
184
+ """Recursively update AttrDict d with AttrDict u with strict matching"""
185
+ for key, value in u.items():
186
+ if key not in d:
187
+ key_full = ".".join(stack + [key])
188
+ raise KeyError(f"The input key '{key_full}; does not exist in the config files.")
189
+ if isinstance(value, collections.abc.Mapping):
190
+ d.__dict__[key] = recursive_update_strict(d.get(key, AttrDict({})), value, stack + [key])
191
+ elif isinstance(value, (list, tuple)):
192
+ if value and isinstance(value[0], dict):
193
+ d.__dict__[key] = [AttrDict(item) for item in value]
194
+ else:
195
+ d.__dict__[key] = value
196
+ else:
197
+ d.__dict__[key] = value
198
+ return d
199
+
200
+
201
+ def parse_cmdline_arguments(args):
202
+ """
203
+ Parse arguments from command line.
204
+ Syntax: --key1.key2.key3=value --> value
205
+ --key1.key2.key3= --> None
206
+ --key1.key2.key3 --> True
207
+ --key1.key2.key3! --> False
208
+ """
209
+ cfg_cmd = {}
210
+ for arg in args:
211
+ assert arg.startswith("--")
212
+ if "=" not in arg[2:]:
213
+ key_str, value = (arg[2:-1], "false") if arg[-1] == "!" else (arg[2:], "true")
214
+ else:
215
+ key_str, value = arg[2:].split("=")
216
+ keys_sub = key_str.split(".")
217
+ cfg_sub = cfg_cmd
218
+ for k in keys_sub[:-1]:
219
+ cfg_sub.setdefault(k, {})
220
+ cfg_sub = cfg_sub[k]
221
+ assert keys_sub[-1] not in cfg_sub, keys_sub[-1]
222
+ cfg_sub[keys_sub[-1]] = yaml.safe_load(value)
223
+ return cfg_cmd
neuralangelo-main/imaginaire/config_base.yaml ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -----------------------------------------------------------------------------
2
+ # Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
3
+ #
4
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
5
+ # and proprietary rights in and to this software, related documentation
6
+ # and any modifications thereto. Any use, reproduction, disclosure or
7
+ # distribution of this software and related documentation without an express
8
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
9
+ # -----------------------------------------------------------------------------
10
+
11
+ # This is the base configuration file.
12
+
13
+ # We often dump images to understand what's going on in the training.
14
+ # image_save_iter specifies how often we dump images.
15
+ image_save_iter: 9999999999
16
+ # metrics_iter and metrics_epoch specify how often we compute the performance metrics
17
+ # If these two numbers are not set, they are copied from checkpoint.save_iter and checkpoint.save_epoch respectively.
18
+ metrics_iter:
19
+ metrics_epoch:
20
+ # max_epoch and max_iter specify what is the maximum epoch and iteration that we will train our model.
21
+ # min( max_epoch * dataset_size / batch_size, max_iter) will be the total number of iterations that the model will be trained.
22
+ max_epoch: 9999999999
23
+ max_iter: 9999999999
24
+ # logging_iter controls how often we log the training stats.
25
+ logging_iter: 100
26
+ # If speed_benchmark is True, we will print out time required for forward, backward, and gradient update.
27
+ speed_benchmark: False
28
+ # Kill the process if `timeout_period` seconds have passed since the last iteration. This usually means the process gets stuck.
29
+ timeout_period: 9999999
30
+
31
+ # Default local rank
32
+ local_rank: 0
33
+ # Toggle NVTX profiler
34
+ nvtx_profile: False
35
+
36
+ # Checkpointer
37
+ checkpoint:
38
+ # If save_iter is set to M, then we save the checkpoint every M iteration.
39
+ # If save_latest_iter is set to M, then we save the checkpoint every M iteration using the name
40
+ # 'latest_checkpoint.pt', so that the new checkpoint will overwrite previous ones.
41
+ # If save_epoch is set to N, then we save the checkpoint every N epoch.
42
+ # Both can be set at the same time.
43
+ save_iter: 9999999999
44
+ save_latest_iter: 9999999999
45
+ save_epoch: 9999999999
46
+ save_period: 9999999999
47
+ # If True, load state_dict to the models in strict mode
48
+ strict_resume: True
49
+
50
+ # Trainer
51
+ trainer:
52
+ ema_config:
53
+ enabled: False
54
+ beta: 0.9999
55
+ start_iteration: 0
56
+
57
+ image_to_tensorboard: False
58
+ ddp_config:
59
+ find_unused_parameters: False
60
+ static_graph: True
61
+ init:
62
+ type: none
63
+ gain:
64
+ amp_config:
65
+ init_scale: 65536.0
66
+ growth_factor: 2.0
67
+ backoff_factor: 0.5
68
+ growth_interval: 2000
69
+ enabled: False
70
+ grad_accum_iter: 1
71
+
72
+ # Networks
73
+ model:
74
+ type: dummy
75
+
76
+ # Optimizers
77
+ optim:
78
+ type: Adam
79
+ params:
80
+ # This defines the parameters for the specified PyTorch optimizer class (e.g. betas, eps).
81
+ lr: 0.0001
82
+ fused_opt: False
83
+ # Default learning rate policy is step with iteration_mode=False (epoch mode), step_size=10^10, and gamma=1.
84
+ # This means a constant learning rate
85
+ sched:
86
+ iteration_mode: False
87
+ type: step
88
+ step_size: 9999999999
89
+ gamma: 1
90
+
91
+ # Data
92
+ data:
93
+ name: dummy
94
+ type: imaginaire.datasets.images
95
+ use_multi_epoch_loader: False
96
+ num_workers: 0
97
+ test_data:
98
+ name: dummy
99
+ type: imaginaire.datasets.images
100
+ num_workers: 0
101
+ test:
102
+ is_lmdb: False
103
+ roots:
104
+ batch_size: 1
105
+
106
+ # cuDNN
107
+ # set deterministic to True for better reproducibility of the results. When deterministic is True, it will only use CUDNN functions that are deterministic.
108
+ # If benchmark is set to True, cudnn will benchmark several algorithms and pick that which it found to be fastest at the first iteration.
109
+ cudnn:
110
+ deterministic: False
111
+ benchmark: True
112
+
113
+ # Others
114
+ pretrained_weight:
115
+ inference_args: {}
neuralangelo-main/imaginaire/datasets/base.py ADDED
@@ -0,0 +1,603 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import importlib
14
+ import json
15
+ import os
16
+ import pickle
17
+ from collections import OrderedDict
18
+ from functools import partial
19
+ from inspect import signature
20
+
21
+ import numpy as np
22
+ import torch
23
+ import torch.utils.data as data
24
+ import torchvision.transforms as transforms
25
+
26
+ from imaginaire.datasets.folder import FolderDataset
27
+ from imaginaire.datasets.lmdb import \
28
+ IMG_EXTENSIONS, HDR_IMG_EXTENSIONS, LMDBDataset
29
+ from imaginaire.datasets.object_store import ObjectStoreDataset
30
+ from imaginaire.datasets.utils.data import \
31
+ (VIDEO_EXTENSIONS, Augmentor,
32
+ load_from_folder, load_from_lmdb, load_from_object_store)
33
+ from imaginaire.datasets.utils.lmdb import create_metadata
34
+
35
+
36
+ DATASET_TYPES = ['lmdb', 'folder', 'object_store']
37
+
38
+
39
+ class BaseDataset(data.Dataset):
40
+ r"""Base class for image/video datasets.
41
+
42
+ Args:
43
+ cfg (Config object): Input config.
44
+ is_inference (bool): Training if False, else validation.
45
+ is_test (bool): Final test set after training and validation.
46
+ """
47
+
48
+ def __init__(self, cfg, is_inference, is_test):
49
+ super(BaseDataset, self).__init__()
50
+
51
+ self.cfg = cfg
52
+ self.is_inference = is_inference
53
+ self.is_test = is_test
54
+ if self.is_test:
55
+ self.cfgdata = self.cfg.test_data
56
+ data_info = self.cfgdata.test
57
+ else:
58
+ self.cfgdata = self.cfg.data
59
+ if self.is_inference:
60
+ data_info = self.cfgdata.val
61
+ else:
62
+ data_info = self.cfgdata.train
63
+ self.name = self.cfgdata.name
64
+ self.lmdb_roots = data_info.roots
65
+ self.dataset_type = getattr(data_info, 'dataset_type', None)
66
+ self.cache = getattr(self.cfgdata, 'cache', None)
67
+ self.interpolator = getattr(self.cfgdata, 'interpolator', "INTER_LINEAR")
68
+
69
+ # Get AWS secret keys.
70
+ if self.dataset_type == 'object_store':
71
+ assert hasattr(cfg, 'aws_credentials_file')
72
+ self.aws_credentials_file = cfg.aws_credentials_file
73
+
74
+ # Legacy lmdb/folder only support.
75
+ if self.dataset_type is None:
76
+ self.dataset_is_lmdb = getattr(data_info, 'is_lmdb', False)
77
+ if self.dataset_is_lmdb:
78
+ self.dataset_type = 'lmdb'
79
+ else:
80
+ self.dataset_type = 'folder'
81
+ # Legacy support ends.
82
+
83
+ assert self.dataset_type in DATASET_TYPES
84
+ if self.dataset_type == 'lmdb':
85
+ # Add handle to function to load data from LMDB.
86
+ self.load_from_dataset = load_from_lmdb
87
+ elif self.dataset_type == 'folder':
88
+ # For some unpaired experiments, we would like the dataset to be presented in a paired way
89
+
90
+ if hasattr(self.cfgdata, 'paired') is False:
91
+ self.cfgdata.paired = self.paired
92
+ # Add handle to function to load data from folder.
93
+ self.load_from_dataset = load_from_folder
94
+ # Create metadata for folders.
95
+ print('Creating metadata')
96
+ all_filenames, all_metadata = [], []
97
+ if self.is_test:
98
+ cfg.data_backup = cfg.data
99
+ cfg.data = cfg.test_data
100
+ for root in self.lmdb_roots:
101
+ filenames, metadata = create_metadata(
102
+ data_root=root, cfg=cfg, paired=self.cfgdata['paired'])
103
+ all_filenames.append(filenames)
104
+ all_metadata.append(metadata)
105
+ if self.is_test:
106
+ cfg.data = cfg.data_backup
107
+ elif self.dataset_type == 'object_store':
108
+ # Add handle to function to load data from AWS S3.
109
+ self.load_from_dataset = load_from_object_store
110
+
111
+ # Get the types of data stored in dataset, and their extensions.
112
+ self.data_types = [] # Names of data types.
113
+ self.dataset_data_types = [] # These data types are in the dataset.
114
+ self.image_data_types = [] # These types are images.
115
+ self.hdr_image_data_types = [] # These types are HDR images.
116
+ self.normalize = {} # Does this data type need normalization?
117
+ self.extensions = {} # What is this data type's file extension.
118
+ self.is_mask = {} # Whether this data type is discrete masks?
119
+ self.num_channels = {} # How many channels does this data type have?
120
+ self.pre_aug_ops = {} # Ops on data type before augmentation.
121
+ self.post_aug_ops = {} # Ops on data type after augmentation.
122
+
123
+ # Extract info from data types.
124
+ for data_type in self.cfgdata.input_types:
125
+ name = list(data_type.keys())
126
+ assert len(name) == 1
127
+ name = name[0]
128
+ info = data_type[name]
129
+
130
+ if 'ext' not in info:
131
+ info['ext'] = None
132
+ if 'normalize' not in info:
133
+ info['normalize'] = False
134
+ if 'is_mask' not in info:
135
+ info['is_mask'] = False
136
+ if 'pre_aug_ops' not in info:
137
+ info['pre_aug_ops'] = 'None'
138
+ if 'post_aug_ops' not in info:
139
+ info['post_aug_ops'] = 'None'
140
+ if 'computed_on_the_fly' not in info:
141
+ info['computed_on_the_fly'] = False
142
+ if 'num_channels' not in info:
143
+ info['num_channels'] = None
144
+
145
+ self.data_types.append(name)
146
+ if not info['computed_on_the_fly']:
147
+ self.dataset_data_types.append(name)
148
+
149
+ self.extensions[name] = info['ext']
150
+ self.normalize[name] = info['normalize']
151
+ self.num_channels[name] = info['num_channels']
152
+ self.pre_aug_ops[name] = [op.strip() for op in
153
+ info['pre_aug_ops'].split(',')]
154
+ self.post_aug_ops[name] = [op.strip() for op in
155
+ info['post_aug_ops'].split(',')]
156
+ self.is_mask[name] = info['is_mask']
157
+ if info['ext'] is not None and (info['ext'] in IMG_EXTENSIONS or info['ext'] in VIDEO_EXTENSIONS):
158
+ self.image_data_types.append(name)
159
+ if info['ext'] is not None and info['ext'] in HDR_IMG_EXTENSIONS:
160
+ self.hdr_image_data_types.append(name)
161
+
162
+ # Add some info into cfgdata for legacy support.
163
+ self.cfgdata.data_types = self.data_types
164
+ self.cfgdata.num_channels = [self.num_channels[name]
165
+ for name in self.data_types]
166
+
167
+ # Augmentations which need full dict.
168
+ self.full_data_post_aug_ops, self.full_data_ops = [], []
169
+ if hasattr(self.cfgdata, 'full_data_ops'):
170
+ ops = self.cfgdata.full_data_ops
171
+ self.full_data_ops.extend([op.strip() for op in ops.split(',')])
172
+ if hasattr(self.cfgdata, 'full_data_post_aug_ops'):
173
+ ops = self.cfgdata.full_data_post_aug_ops
174
+ self.full_data_post_aug_ops.extend(
175
+ [op.strip() for op in ops.split(',')])
176
+
177
+ # These are the labels which will be concatenated for generator input.
178
+ self.input_labels = []
179
+ if hasattr(self.cfgdata, 'input_labels'):
180
+ self.input_labels = self.cfgdata.input_labels
181
+
182
+ # These are the keypoints which also need to be augmented.
183
+ self.keypoint_data_types = []
184
+ if hasattr(self.cfgdata, 'keypoint_data_types'):
185
+ self.keypoint_data_types = self.cfgdata.keypoint_data_types
186
+
187
+ # Create augmentation operations.
188
+ aug_list = data_info.augmentations
189
+ individual_video_frame_aug_list = getattr(data_info, 'individual_video_frame_augmentations', dict())
190
+ post_aug_list = getattr(data_info, 'post_augmentations', dict())
191
+ self.augmentor = Augmentor(
192
+ aug_list, individual_video_frame_aug_list, post_aug_list, self.image_data_types, self.is_mask,
193
+ self.keypoint_data_types, self.interpolator)
194
+ self.augmentable_types = self.image_data_types + \
195
+ self.keypoint_data_types
196
+
197
+ # Create torch transformations.
198
+ self.transform = {}
199
+ for data_type in self.image_data_types:
200
+ normalize = self.normalize[data_type]
201
+ self.transform[data_type] = self._get_transform(
202
+ normalize, self.num_channels[data_type])
203
+
204
+ # Create torch transformations for HDR images.
205
+ for data_type in self.hdr_image_data_types:
206
+ normalize = self.normalize[data_type]
207
+ self.transform[data_type] = self._get_transform(
208
+ normalize, self.num_channels[data_type])
209
+
210
+ # Initialize handles.
211
+ self.sequence_lists = [] # List of sequences per dataset root.
212
+ self.lmdbs = {} # Dict for list of lmdb handles per data type.
213
+ for data_type in self.dataset_data_types:
214
+ self.lmdbs[data_type] = []
215
+ self.dataset_probability = None
216
+ self.additional_lists = []
217
+
218
+ # Load each dataset.
219
+ for idx, root in enumerate(self.lmdb_roots):
220
+ if self.dataset_type == 'lmdb':
221
+ self._add_dataset(root)
222
+ elif self.dataset_type == 'folder':
223
+ self._add_dataset(root, filenames=all_filenames[idx],
224
+ metadata=all_metadata[idx])
225
+ elif self.dataset_type == 'object_store':
226
+ self._add_dataset(
227
+ root, aws_credentials_file=self.aws_credentials_file)
228
+
229
+ # Compute dataset statistics and create whatever self.variables required
230
+ # for the specific dataloader.
231
+ self._compute_dataset_stats()
232
+
233
+ # Build index of data to sample.
234
+ self.mapping, self.epoch_length = self._create_mapping()
235
+
236
+ def _create_mapping(self):
237
+ r"""Creates mapping from data sample idx to actual LMDB keys.
238
+ All children need to implement their own.
239
+
240
+ Returns:
241
+ self.mapping (list): List of LMDB keys.
242
+ """
243
+ raise NotImplementedError
244
+
245
+ def _compute_dataset_stats(self):
246
+ r"""Computes required statistics about dataset.
247
+ All children need to implement their own.
248
+ """
249
+ pass
250
+
251
+ def __getitem__(self, index):
252
+ r"""Entry function for dataset."""
253
+ raise NotImplementedError
254
+
255
+ def _get_transform(self, normalize, num_channels):
256
+ r"""Convert numpy to torch tensor.
257
+
258
+ Args:
259
+ normalize (bool): Normalize image i.e. (x - 0.5) * 2.
260
+ Goes from [0, 1] -> [-1, 1].
261
+ Returns:
262
+ Composed list of torch transforms.
263
+ """
264
+ transform_list = [transforms.ToTensor()]
265
+ if normalize:
266
+ transform_list.append(
267
+ transforms.Normalize((0.5, ) * num_channels,
268
+ (0.5, ) * num_channels, inplace=True))
269
+ return transforms.Compose(transform_list)
270
+
271
+ def _add_dataset(self, root, filenames=None, metadata=None,
272
+ aws_credentials_file=None):
273
+ r"""Adds an LMDB dataset to a list of datasets.
274
+
275
+ Args:
276
+ root (str): Path to LMDB or folder dataset.
277
+ filenames: List of filenames for folder dataset.
278
+ metadata: Metadata for folder dataset.
279
+ aws_credentials_file: Path to file containing AWS credentials.
280
+ """
281
+ if aws_credentials_file and self.dataset_type == 'object_store':
282
+ object_store_dataset = ObjectStoreDataset(
283
+ root, aws_credentials_file, cache=self.cache)
284
+ sequence_list = object_store_dataset.sequence_list
285
+ else:
286
+ # Get sequences associated with this dataset.
287
+ if filenames is None:
288
+ list_path = 'all_filenames.json'
289
+ with open(os.path.join(root, list_path)) as fin:
290
+ sequence_list = OrderedDict(json.load(fin))
291
+ else:
292
+ sequence_list = filenames
293
+
294
+ additional_path = 'all_indices.json'
295
+ if os.path.exists(os.path.join(root, additional_path)):
296
+ print('Using additional list for object indices.')
297
+ with open(os.path.join(root, additional_path)) as fin:
298
+ additional_list = OrderedDict(json.load(fin))
299
+ self.additional_lists.append(additional_list)
300
+ self.sequence_lists.append(sequence_list)
301
+
302
+ # Get LMDB dataset handles.
303
+ for data_type in self.dataset_data_types:
304
+ if self.dataset_type == 'lmdb':
305
+ self.lmdbs[data_type].append(
306
+ LMDBDataset(os.path.join(root, data_type)))
307
+ elif self.dataset_type == 'folder':
308
+ self.lmdbs[data_type].append(
309
+ FolderDataset(os.path.join(root, data_type), metadata))
310
+ elif self.dataset_type == 'object_store':
311
+ # All data types use the same handle.
312
+ self.lmdbs[data_type].append(object_store_dataset)
313
+
314
+ def perform_individual_video_frame(self, data, augment_ops):
315
+ r"""Perform data augmentation on images only.
316
+
317
+ Args:
318
+ data (dict): Keys are from data types. Values can be numpy.ndarray
319
+ or list of numpy.ndarray (image or list of images).
320
+ augment_ops (list): The augmentation operations for individual frames.
321
+ Returns:
322
+ (tuple):
323
+ - data (dict): Augmented data, with same keys as input data.
324
+ - is_flipped (bool): Flag which tells if images have been
325
+ left-right flipped.
326
+ """
327
+ if augment_ops:
328
+ all_data = dict()
329
+ for ix, key in enumerate(data.keys()):
330
+ if ix == 0:
331
+ num = len(data[key])
332
+ for j in range(num):
333
+ all_data['%d' % j] = dict()
334
+ for j in range(num):
335
+ all_data['%d' % j][key] = data[key][j:(j+1)]
336
+ for j in range(num):
337
+ all_data['%d' % j], _ = self.perform_augmentation(
338
+ all_data['%d' % j], paired=True, augment_ops=augment_ops)
339
+ for key in data.keys():
340
+ tmp = []
341
+ for j in range(num):
342
+ tmp += all_data['%d' % j][key]
343
+ data[key] = tmp
344
+ return data
345
+
346
+ def perform_augmentation(self, data, paired, augment_ops=None):
347
+ r"""Perform data augmentation on images only.
348
+
349
+ Args:
350
+ data (dict): Keys are from data types. Values can be numpy.ndarray
351
+ or list of numpy.ndarray (image or list of images).
352
+ paired (bool): Apply same augmentation to all input keys?
353
+ augment_ops (list): The augmentation operations.
354
+ Returns:
355
+ (tuple):
356
+ - data (dict): Augmented data, with same keys as input data.
357
+ - is_flipped (bool): Flag which tells if images have been
358
+ left-right flipped.
359
+ """
360
+ aug_inputs = {}
361
+ for data_type in self.augmentable_types:
362
+ aug_inputs[data_type] = data[data_type]
363
+
364
+ augmented, is_flipped = self.augmentor.perform_augmentation(
365
+ aug_inputs, paired=paired, augment_ops=augment_ops)
366
+
367
+ for data_type in self.augmentable_types:
368
+ data[data_type] = augmented[data_type]
369
+
370
+ return data, is_flipped
371
+
372
+ def flip_hdr(self, data, is_flipped=False):
373
+ r"""Flip hdr images.
374
+
375
+ Args:
376
+ data (dict): Keys are from data types. Values can be numpy.ndarray
377
+ or list of numpy.ndarray (image or list of images).
378
+ is_flipped (bool): Applying left-right flip to the hdr images
379
+ Returns:
380
+ (tuple):
381
+ - data (dict): Augmented data, with same keys as input data.
382
+ """
383
+ if is_flipped is False:
384
+ return data
385
+
386
+ for data_type in self.hdr_image_data_types:
387
+ # print('Length of data: {}'.format(len(data[data_type])))
388
+ data[data_type][0] = data[data_type][0][:, ::-1, :].copy()
389
+ return data
390
+
391
+ def to_tensor(self, data):
392
+ r"""Convert all images to tensor.
393
+
394
+ Args:
395
+ data (dict): Dict containing data_type as key, with each value
396
+ as a list of numpy.ndarrays.
397
+ Returns:
398
+ data (dict): Dict containing data_type as key, with each value
399
+ as a list of torch.Tensors.
400
+ """
401
+ for data_type in self.image_data_types:
402
+ for idx in range(len(data[data_type])):
403
+ if data[data_type][idx].dtype == np.uint16:
404
+ data[data_type][idx] = data[data_type][idx].astype(
405
+ np.float32)
406
+ data[data_type][idx] = self.transform[data_type](
407
+ data[data_type][idx])
408
+ for data_type in self.hdr_image_data_types:
409
+ for idx in range(len(data[data_type])):
410
+ data[data_type][idx] = self.transform[data_type](
411
+ data[data_type][idx])
412
+ return data
413
+
414
+ def apply_ops(self, data, op_dict, full_data=False):
415
+ r"""Apply any ops from op_dict to data types.
416
+
417
+ Args:
418
+ data (dict): Dict containing data_type as key, with each value
419
+ as a list of numpy.ndarrays.
420
+ op_dict (dict): Dict containing data_type as key, with each value
421
+ containing string of operations to apply.
422
+ full_data (bool): Do these ops require access to the full data?
423
+ Returns:
424
+ data (dict): Dict containing data_type as key, with each value
425
+ modified by the op if any.
426
+ """
427
+ if full_data:
428
+ # op needs entire data dict.
429
+ for op in op_dict:
430
+ if op == 'None':
431
+ continue
432
+ op, op_type = self.get_op(op)
433
+ assert op_type == 'full_data'
434
+ data = op(data)
435
+ else:
436
+ # op per data type.
437
+ if not op_dict:
438
+ return data
439
+ for data_type in data:
440
+ for op in op_dict[data_type]:
441
+ if op == 'None':
442
+ continue
443
+ op, op_type = self.get_op(op)
444
+ data[data_type] = op(data[data_type])
445
+
446
+ if op_type == 'vis':
447
+ # We have converted this data type to an image. Enter it
448
+ # in self.image_data_types and give it a torch
449
+ # transform.
450
+ if data_type not in self.image_data_types:
451
+ self.image_data_types.append(data_type)
452
+ normalize = self.normalize[data_type]
453
+ num_channels = self.num_channels[data_type]
454
+ self.transform[data_type] = \
455
+ self._get_transform(normalize, num_channels)
456
+ elif op_type == 'convert':
457
+ continue
458
+ elif op_type is None:
459
+ continue
460
+ else:
461
+ raise NotImplementedError
462
+ return data
463
+
464
+ def get_op(self, op):
465
+ r"""Get function to apply for specific op.
466
+
467
+ Args:
468
+ op (str): Name of the op.
469
+ Returns:
470
+ function handle.
471
+ """
472
+ def list_to_tensor(data):
473
+ r"""Convert list of numeric values to tensor."""
474
+ assert isinstance(data, list)
475
+ return torch.from_numpy(np.array(data, dtype=np.float32))
476
+
477
+ def decode_json_list(data):
478
+ r"""Decode list of strings in json to objects."""
479
+ assert isinstance(data, list)
480
+ return [json.loads(item) for item in data]
481
+
482
+ def decode_pkl_list(data):
483
+ r"""Decode list of pickled strings to objects."""
484
+ assert isinstance(data, list)
485
+ return [pickle.loads(item) for item in data]
486
+
487
+ def list_to_numpy(data):
488
+ r"""Convert list of numeric values to numpy array."""
489
+ assert isinstance(data, list)
490
+ return np.array(data)
491
+
492
+ def l2_normalize(data):
493
+ r"""L2 normalization."""
494
+ assert isinstance(data, torch.Tensor)
495
+ import torch.nn.functional as F
496
+ return F.normalize(data, dim=1)
497
+
498
+ if op == 'to_tensor':
499
+ return list_to_tensor, None
500
+ elif op == 'decode_json':
501
+ return decode_json_list, None
502
+ elif op == 'decode_pkl':
503
+ return decode_pkl_list, None
504
+ elif op == 'to_numpy':
505
+ return list_to_numpy, None
506
+ elif op == 'l2_norm':
507
+ return l2_normalize, None
508
+ elif '::' in op:
509
+ parts = op.split('::')
510
+ if len(parts) == 2:
511
+ module, function = parts
512
+ module = importlib.import_module(module)
513
+ function = getattr(module, function)
514
+ sig = signature(function)
515
+ num_params = len(sig.parameters)
516
+ assert num_params in [3, 4], \
517
+ 'Full data functions take in (cfgdata, is_inference, ' \
518
+ 'full_data) or (cfgdata, is_inference, self, full_data) ' \
519
+ 'as input.'
520
+ if num_params == 3:
521
+ function = partial(
522
+ function, self.cfgdata, self.is_inference)
523
+ elif num_params == 4:
524
+ function = partial(
525
+ function, self.cfgdata, self.is_inference, self)
526
+ function_type = 'full_data'
527
+ elif len(parts) == 3:
528
+ function_type, module, function = parts
529
+ module = importlib.import_module(module)
530
+
531
+ # Get function inputs, if provided.
532
+ partial_fn = False
533
+ if '(' in function and ')' in function:
534
+ partial_fn = True
535
+ function, params = self._get_fn_params(function)
536
+
537
+ function = getattr(module, function)
538
+
539
+ # Create partial function.
540
+ if partial_fn:
541
+ function = partial(function, **params)
542
+
543
+ # Get function signature.
544
+ sig = signature(function)
545
+ num_params = 0
546
+ for param in sig.parameters.values():
547
+ if param.kind == param.POSITIONAL_OR_KEYWORD:
548
+ num_params += 1
549
+
550
+ if function_type == 'vis':
551
+ if num_params != 9:
552
+ raise ValueError(
553
+ 'vis function type needs to take ' +
554
+ '(resize_h, resize_w, crop_h, crop_w, ' +
555
+ 'original_h, original_w, is_flipped, cfgdata, ' +
556
+ 'data) as input.')
557
+ function = partial(function,
558
+ self.augmentor.resize_h,
559
+ self.augmentor.resize_w,
560
+ self.augmentor.crop_h,
561
+ self.augmentor.crop_w,
562
+ self.augmentor.original_h,
563
+ self.augmentor.original_w,
564
+ self.augmentor.is_flipped,
565
+ self.cfgdata)
566
+ elif function_type == 'convert':
567
+ if num_params != 1:
568
+ raise ValueError(
569
+ 'convert function type needs to take ' +
570
+ '(data) as input.')
571
+ else:
572
+ raise ValueError('Unknown op: %s' % (op))
573
+ else:
574
+ raise ValueError('Unknown op: %s' % (op))
575
+ return function, function_type
576
+ else:
577
+ raise ValueError('Unknown op: %s' % (op))
578
+
579
+ def _get_fn_params(self, function_string):
580
+ r"""Find key-value inputs to function from string definition.
581
+
582
+ Args:
583
+ function_string (str): String with function name and args. e.g.
584
+ my_function(a=10, b=20).
585
+ Returns:
586
+ function (str): Name of function.
587
+ params (dict): Key-value params for function.
588
+ """
589
+ start = function_string.find('(')
590
+ end = function_string.find(')')
591
+ function = function_string[:start]
592
+ params_str = function_string[start+1:end]
593
+ params = {}
594
+ for item in params_str.split(':'):
595
+ key, value = item.split('=')
596
+ try:
597
+ params[key] = float(value)
598
+ except Exception:
599
+ params[key] = value
600
+ return function, params
601
+
602
+ def __len__(self):
603
+ return self.epoch_length
neuralangelo-main/imaginaire/datasets/utils/dataloader.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import torch
14
+
15
+
16
+ class MultiEpochsDataLoader(torch.utils.data.DataLoader):
17
+ """
18
+ Relentlessly sample from the dataset.
19
+ This eliminates the overhead of prefetching data before each epoch.
20
+ https://github.com/rwightman/pytorch-image-models/blob/master/timm/data/loader.py
21
+ """
22
+
23
+ def __init__(self, *args, **kwargs):
24
+ super().__init__(*args, **kwargs)
25
+ self._DataLoader__initialized = False
26
+ self.batch_sampler = _RepeatSampler(self.batch_sampler)
27
+ self._DataLoader__initialized = True
28
+ self.iterator = super().__iter__()
29
+
30
+ def __len__(self):
31
+ return len(self.batch_sampler.sampler)
32
+
33
+ def __iter__(self):
34
+ for i in range(len(self)):
35
+ yield next(self.iterator)
36
+
37
+
38
+ class _RepeatSampler(object):
39
+ """ Sampler that repeats forever.
40
+ Args:
41
+ sampler (Sampler)
42
+ """
43
+
44
+ def __init__(self, sampler):
45
+ self.sampler = sampler
46
+
47
+ def __iter__(self):
48
+ while True:
49
+ yield from iter(self.sampler)
neuralangelo-main/imaginaire/datasets/utils/get_dataloader.py ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import importlib
14
+
15
+ import torch
16
+ import torch.distributed as dist
17
+
18
+ from imaginaire.utils.distributed import master_only_print as print
19
+
20
+ from imaginaire.datasets.utils.sampler import DistributedSamplerPreemptable
21
+ from imaginaire.datasets.utils.dataloader import MultiEpochsDataLoader
22
+
23
+
24
+ def _get_train_dataset_objects(cfg, subset_indices=None):
25
+ r"""Return dataset objects for the training set.
26
+ Args:
27
+ cfg (obj): Global configuration file.
28
+ subset_indices (sequence): Indices of the subset to use.
29
+
30
+ Returns:
31
+ train_dataset (obj): PyTorch training dataset object.
32
+ """
33
+ dataset_module = importlib.import_module(cfg.data.type)
34
+ train_dataset = dataset_module.Dataset(cfg, is_inference=False)
35
+ if subset_indices is not None:
36
+ train_dataset = torch.utils.data.Subset(train_dataset, subset_indices)
37
+ print('Train dataset length:', len(train_dataset))
38
+ return train_dataset
39
+
40
+
41
+ def _get_val_dataset_objects(cfg, subset_indices=None):
42
+ r"""Return dataset objects for the validation set.
43
+ Args:
44
+ cfg (obj): Global configuration file.
45
+ subset_indices (sequence): Indices of the subset to use.
46
+ Returns:
47
+ val_dataset (obj): PyTorch validation dataset object.
48
+ """
49
+ dataset_module = importlib.import_module(cfg.data.type)
50
+ if hasattr(cfg.data.val, 'type'):
51
+ for key in ['type', 'input_types', 'input_image']:
52
+ setattr(cfg.data, key, getattr(cfg.data.val, key))
53
+ dataset_module = importlib.import_module(cfg.data.type)
54
+ val_dataset = dataset_module.Dataset(cfg, is_inference=True)
55
+
56
+ if subset_indices is not None:
57
+ val_dataset = torch.utils.data.Subset(val_dataset, subset_indices)
58
+ print('Val dataset length:', len(val_dataset))
59
+ return val_dataset
60
+
61
+
62
+ def _get_test_dataset_object(cfg, subset_indices=None):
63
+ r"""Return dataset object for the test set
64
+
65
+ Args:
66
+ cfg (obj): Global configuration file.
67
+ subset_indices (sequence): Indices of the subset to use.
68
+ Returns:
69
+ (obj): PyTorch dataset object.
70
+ """
71
+ dataset_module = importlib.import_module(cfg.test_data.type)
72
+ test_dataset = dataset_module.Dataset(cfg, is_inference=True, is_test=True)
73
+ if subset_indices is not None:
74
+ test_dataset = torch.utils.data.Subset(test_dataset, subset_indices)
75
+ return test_dataset
76
+
77
+
78
+ def _get_data_loader(cfg, dataset, batch_size, not_distributed=False,
79
+ shuffle=True, drop_last=True, seed=0, use_multi_epoch_loader=False,
80
+ preemptable=False):
81
+ r"""Return data loader .
82
+
83
+ Args:
84
+ cfg (obj): Global configuration file.
85
+ dataset (obj): PyTorch dataset object.
86
+ batch_size (int): Batch size.
87
+ not_distributed (bool): Do not use distributed samplers.
88
+ shuffle (bool): Whether to shuffle the data
89
+ drop_last (bool): Whether to drop the last batch is the number of samples is smaller than the batch size
90
+ seed (int): random seed.
91
+ preemptable (bool): Whether to handle preemptions.
92
+ Return:
93
+ (obj): Data loader.
94
+ """
95
+ not_distributed = not_distributed or not dist.is_initialized()
96
+ if not_distributed:
97
+ sampler = None
98
+ else:
99
+ if preemptable:
100
+ sampler = DistributedSamplerPreemptable(dataset, shuffle=shuffle, seed=seed)
101
+ else:
102
+ sampler = torch.utils.data.distributed.DistributedSampler(dataset, shuffle=shuffle, seed=seed)
103
+ num_workers = getattr(cfg.data, 'num_workers', 8)
104
+ persistent_workers = getattr(cfg.data, 'persistent_workers', False)
105
+ data_loader = (MultiEpochsDataLoader if use_multi_epoch_loader else torch.utils.data.DataLoader)(
106
+ dataset,
107
+ batch_size=batch_size,
108
+ shuffle=shuffle and (sampler is None),
109
+ sampler=sampler,
110
+ pin_memory=True,
111
+ num_workers=num_workers,
112
+ drop_last=drop_last,
113
+ persistent_workers=persistent_workers if num_workers > 0 else False
114
+ )
115
+ return data_loader
116
+
117
+
118
+ def get_train_dataloader(
119
+ cfg, shuffle=True, drop_last=True, subset_indices=None, seed=0, preemptable=False):
120
+ r"""Return dataset objects for the training and validation sets.
121
+ Args:
122
+ cfg (obj): Global configuration file.
123
+ shuffle (bool): Whether to shuffle the data
124
+ drop_last (bool): Whether to drop the last batch is the number of samples is smaller than the batch size
125
+ subset_indices (sequence): Indices of the subset to use.
126
+ seed (int): random seed.
127
+ preemptable (bool): Flag for preemption handling
128
+ Returns:
129
+ train_data_loader (obj): Train data loader.
130
+ """
131
+ train_dataset = _get_train_dataset_objects(cfg, subset_indices=subset_indices)
132
+ train_data_loader = _get_data_loader(
133
+ cfg, train_dataset, cfg.data.train.batch_size, not_distributed=False,
134
+ shuffle=shuffle, drop_last=drop_last, seed=seed,
135
+ use_multi_epoch_loader=cfg.data.use_multi_epoch_loader,
136
+ preemptable=preemptable
137
+ )
138
+ return train_data_loader
139
+
140
+
141
+ def get_val_dataloader(cfg, subset_indices=None, seed=0):
142
+ r"""Return dataset objects for the training and validation sets.
143
+ Args:
144
+ cfg (obj): Global configuration file.
145
+ subset_indices (sequence): Indices of the subset to use.
146
+ seed (int): random seed.
147
+ Returns:
148
+ val_data_loader (obj): Val data loader.
149
+ """
150
+ val_dataset = _get_val_dataset_objects(cfg, subset_indices=subset_indices)
151
+ not_distributed = getattr(cfg.data, 'val_data_loader_not_distributed', False)
152
+ # We often use a folder of images to represent a video. As doing evaluation, we like the images to preserve the
153
+ # original order. As a result, we do not want to distribute images from the same video to different GPUs.
154
+ not_distributed = 'video' in cfg.data.type or not_distributed
155
+ drop_last = getattr(cfg.data.val, 'drop_last', False)
156
+ # Validation loader need not have preemption handling.
157
+ val_data_loader = _get_data_loader(
158
+ cfg, val_dataset, cfg.data.val.batch_size, not_distributed=not_distributed,
159
+ shuffle=False, drop_last=drop_last, seed=seed,
160
+ preemptable=False
161
+ )
162
+ return val_data_loader
163
+
164
+
165
+ def get_test_dataloader(cfg, subset_indices=None):
166
+ r"""Return dataset objects for testing
167
+
168
+ Args:
169
+ cfg (obj): Global configuration file.
170
+ subset_indices (sequence): Indices of the subset to use.
171
+ Returns:
172
+ (obj): Test data loader. It may not contain the ground truth.
173
+ """
174
+ test_dataset = _get_test_dataset_object(cfg, subset_indices=subset_indices)
175
+ not_distributed = getattr(
176
+ cfg.test_data, 'val_data_loader_not_distributed', False)
177
+ not_distributed = 'video' in cfg.test_data.type or not_distributed
178
+ test_data_loader = _get_data_loader(
179
+ cfg, test_dataset, cfg.test_data.test.batch_size, not_distributed=not_distributed,
180
+ shuffle=False)
181
+ return test_data_loader
neuralangelo-main/imaginaire/datasets/utils/sampler.py ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import math
14
+ import torch.distributed as dist
15
+ import torch
16
+
17
+ from torch.utils.data import Sampler
18
+ from typing import TypeVar
19
+
20
+ T_co = TypeVar('T_co', covariant=True)
21
+
22
+
23
+ class DistributedSamplerPreemptable(Sampler[T_co]):
24
+ r"""Sampler that supports loading from an iteration.
25
+ This is very useful for preemptable jobs.
26
+
27
+ Args:
28
+ dataset (torch.utils.data.Dataset): Dataset object
29
+ num_replicas (int): Number of replicas to the distribute the dataloader over.
30
+ This is typically the world size in DDP jobs.
31
+ rank (int): Rank of the current process.
32
+ shuffle (bool): Whether to shuffle the dataloader in each epoch.
33
+ seed (int): Random seed used for shuffling the dataloader.
34
+ drop_last (bool): Whether to drop the last batch.
35
+ """
36
+
37
+ def __init__(self, dataset, num_replicas=None, rank=None, shuffle=True,
38
+ seed=0, drop_last=False):
39
+
40
+ if num_replicas is None:
41
+ if not dist.is_available():
42
+ raise RuntimeError("Requires distributed package to be available")
43
+ num_replicas = dist.get_world_size()
44
+ if rank is None:
45
+ if not dist.is_available():
46
+ raise RuntimeError("Requires distributed package to be available")
47
+ rank = dist.get_rank()
48
+ if rank >= num_replicas or rank < 0:
49
+ raise ValueError(
50
+ "Invalid rank {}, rank should be in the interval"
51
+ " [0, {}]".format(rank, num_replicas - 1))
52
+ self.dataset = dataset
53
+ self.num_replicas = num_replicas
54
+ self.rank = rank
55
+ self.epoch = 0
56
+
57
+ # start_index is the index to begin the dataloader from.
58
+ self.start_index = 0
59
+
60
+ self.drop_last = drop_last
61
+ # If the dataset length is evenly divisible by # of replicas, then there
62
+ # is no need to drop any data, since the dataset will be split equally.
63
+ if self.drop_last and len(self.dataset) % self.num_replicas != 0: # type: ignore[arg-type]
64
+ # Split to nearest available length that is evenly divisible.
65
+ # This is to ensure each rank receives the same amount of data when
66
+ # using this Sampler.
67
+ self.num_samples = math.ceil(
68
+ (len(self.dataset) - self.num_replicas) / self.num_replicas # type: ignore[arg-type]
69
+ )
70
+ else:
71
+ self.num_samples = math.ceil(len(self.dataset) / self.num_replicas) # type: ignore[arg-type]
72
+ self.total_size = self.num_samples * self.num_replicas
73
+ self.shuffle = shuffle
74
+ self.seed = seed
75
+
76
+ def __iter__(self):
77
+ if self.shuffle:
78
+ # deterministically shuffle based on epoch and seed
79
+ g = torch.Model()
80
+ g.manual_seed(self.seed + self.epoch)
81
+ indices = torch.randperm(len(self.dataset), generator=g).tolist() # type: ignore[arg-type]
82
+ else:
83
+ indices = list(range(len(self.dataset))) # type: ignore[arg-type]
84
+
85
+ if not self.drop_last:
86
+ # add extra samples to make it evenly divisible
87
+ padding_size = self.total_size - len(indices)
88
+ if padding_size <= len(indices):
89
+ indices += indices[:padding_size]
90
+ else:
91
+ indices += (indices * math.ceil(padding_size / len(indices)))[:padding_size]
92
+ else:
93
+ # remove tail of data to make it evenly divisible.
94
+ indices = indices[:self.total_size]
95
+ assert len(indices) == self.total_size
96
+
97
+ # subsample
98
+ indices = indices[self.rank:self.total_size:self.num_replicas]
99
+ assert len(indices) == self.num_samples
100
+
101
+ # assert self.start_index < len(indices)
102
+ if self.start_index >= len(indices):
103
+ print('(Warning): Start index is less than len of dataloader. Goint to the last batch of dataset instead')
104
+ # This is hardcoded to go one batch before.
105
+ self.start_index = len(indices) - 64
106
+ indices = indices[self.start_index:]
107
+
108
+ return iter(indices)
109
+
110
+ def __len__(self):
111
+ return self.num_samples
112
+
113
+ def set_epoch(self, epoch):
114
+ self.epoch = epoch
115
+
116
+ def set_iteration(self, start_index):
117
+ self.start_index = start_index
neuralangelo-main/imaginaire/models/base.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import torch
14
+
15
+
16
+ class Model(torch.nn.Module):
17
+
18
+ def __init__(self, cfg_model, cfg_data):
19
+ super().__init__()
20
+
21
+ def get_param_groups(self, cfg_optim):
22
+ """Allow the network to use different hyperparameters (e.g., learning rate) for different parameters.
23
+ Returns:
24
+ PyTorch parameter group (list or generator). See the PyTorch documentation for details.
25
+ """
26
+ return self.parameters()
27
+
28
+ def device(self):
29
+ """Return device on which model resides."""
30
+ return next(self.parameters()).device
neuralangelo-main/imaginaire/models/utils/init_weight.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import torch
14
+ from torch.nn import init
15
+
16
+
17
+ def weights_init(init_type, gain, bias=None):
18
+ r"""Initialize weights in the network.
19
+
20
+ Args:
21
+ init_type (str): The name of the initialization scheme.
22
+ gain (float): The parameter that is required for the initialization
23
+ scheme.
24
+ bias (object): If not ``None``, specifies the initialization parameter
25
+ for bias.
26
+
27
+ Returns:
28
+ (obj): init function to be applied.
29
+ """
30
+
31
+ def init_func(m):
32
+ r"""Init function
33
+
34
+ Args:
35
+ m: module to be weight initialized.
36
+ """
37
+ class_name = m.__class__.__name__
38
+ if hasattr(m, 'weight') and (
39
+ class_name.find('Conv') != -1 or
40
+ class_name.find('Linear') != -1 or
41
+ class_name.find('Embedding') != -1):
42
+ lr_mul = getattr(m, 'lr_mul', 1.)
43
+ gain_final = gain / lr_mul
44
+ if init_type == 'normal':
45
+ init.normal_(m.weight.data, 0.0, gain_final)
46
+ elif init_type == 'xavier':
47
+ init.xavier_normal_(m.weight.data, gain=gain_final)
48
+ elif init_type == 'xavier_uniform':
49
+ init.xavier_uniform_(m.weight.data, gain=gain_final)
50
+ elif init_type == 'kaiming':
51
+ init.kaiming_normal_(m.weight.data, a=0, mode='fan_in')
52
+ with torch.no_grad():
53
+ m.weight.data *= gain_final
54
+ elif init_type == 'kaiming_linear':
55
+ init.kaiming_normal_(
56
+ m.weight.data, a=0, mode='fan_in', nonlinearity='linear'
57
+ )
58
+ with torch.no_grad():
59
+ m.weight.data *= gain_final
60
+ elif init_type == 'orthogonal':
61
+ init.orthogonal_(m.weight.data, gain=gain_final)
62
+ elif init_type == 'none':
63
+ pass
64
+ else:
65
+ raise NotImplementedError(
66
+ 'initialization method [%s] is '
67
+ 'not implemented' % init_type)
68
+ if hasattr(m, 'bias') and m.bias is not None:
69
+ if init_type == 'none':
70
+ pass
71
+ elif bias is not None:
72
+ bias_type = getattr(bias, 'type', 'normal')
73
+ if bias_type == 'normal':
74
+ bias_gain = getattr(bias, 'gain', 0.5)
75
+ init.normal_(m.bias.data, 0.0, bias_gain)
76
+ else:
77
+ raise NotImplementedError(
78
+ 'initialization method [%s] is '
79
+ 'not implemented' % bias_type)
80
+ else:
81
+ init.constant_(m.bias.data, 0.0)
82
+ return init_func
83
+
84
+
85
+ def weights_rescale():
86
+ def init_func(m):
87
+ if hasattr(m, 'init_gain'):
88
+ for name, p in m.named_parameters():
89
+ if 'output_scale' not in name:
90
+ p.data.mul_(m.init_gain)
91
+ return init_func
neuralangelo-main/imaginaire/models/utils/model_average.py ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import copy
14
+
15
+ import torch
16
+ from torch import nn
17
+ from imaginaire.utils.misc import requires_grad
18
+
19
+
20
+ def reset_batch_norm(m):
21
+ r"""Reset batch norm statistics
22
+
23
+ Args:
24
+ m: Pytorch module
25
+ """
26
+ if hasattr(m, 'reset_running_stats'):
27
+ m.reset_running_stats()
28
+
29
+
30
+ def calibrate_batch_norm_momentum(m):
31
+ r"""Calibrate batch norm momentum
32
+
33
+ Args:
34
+ m: Pytorch module
35
+ """
36
+ if hasattr(m, 'reset_running_stats'):
37
+ # if m._get_name() == 'SyncBatchNorm':
38
+ if 'BatchNorm' in m._get_name():
39
+ m.momentum = 1.0 / float(m.num_batches_tracked + 1)
40
+
41
+
42
+ class ModelAverage(nn.Module):
43
+ r"""In this model average implementation, the spectral layers are
44
+ absorbed in the model parameter by default. If such options are
45
+ turned on, be careful with how you do the training. Remember to
46
+ re-estimate the batch norm parameters before using the model.
47
+
48
+ Args:
49
+ module (torch nn module): Torch network.
50
+ beta (float): Moving average weights. How much we weight the past.
51
+ start_iteration (int): From which iteration, we start the update.
52
+ """
53
+ def __init__(self, module, beta=0.9999, start_iteration=0):
54
+ super(ModelAverage, self).__init__()
55
+
56
+ self.module = module
57
+ # A shallow copy creates a new object which stores the reference of
58
+ # the original elements.
59
+ # A deep copy creates a new object and recursively adds the copies of
60
+ # nested objects present in the original elements.
61
+ self._averaged_model = copy.deepcopy(self.module).to('cuda')
62
+ self.stream = torch.cuda.Stream()
63
+
64
+ self.beta = beta
65
+
66
+ self.start_iteration = start_iteration
67
+ # This buffer is to track how many iterations has the model been
68
+ # trained for. We will ignore the first $(start_iterations) and start
69
+ # the averaging after.
70
+ self.register_buffer('num_updates_tracked',
71
+ torch.tensor(0, dtype=torch.long))
72
+ self.num_updates_tracked = self.num_updates_tracked.to('cuda')
73
+ self.averaged_model.eval()
74
+
75
+ # Averaged model does not require grad.
76
+ requires_grad(self.averaged_model, False)
77
+
78
+ @property
79
+ def averaged_model(self):
80
+ self.stream.synchronize()
81
+ return self._averaged_model
82
+
83
+ def forward(self, *inputs, **kwargs):
84
+ r"""PyTorch module forward function overload."""
85
+ return self.module(*inputs, **kwargs)
86
+
87
+ @torch.no_grad()
88
+ def update_average(self):
89
+ r"""Update the moving average."""
90
+ self.stream.wait_stream(torch.cuda.current_stream())
91
+ with torch.cuda.stream(self.stream):
92
+ self.num_updates_tracked += 1
93
+ if self.num_updates_tracked <= self.start_iteration:
94
+ beta = 0.
95
+ else:
96
+ beta = self.beta
97
+ source_dict = self.module.state_dict()
98
+ target_dict = self._averaged_model.state_dict()
99
+ source_list = []
100
+ target_list = []
101
+ for key in target_dict:
102
+ if 'num_batches_tracked' in key:
103
+ continue
104
+ source_list.append(source_dict[key].data)
105
+ target_list.append(target_dict[key].data.float())
106
+
107
+ torch._foreach_mul_(target_list, beta)
108
+ torch._foreach_add_(target_list, source_list, alpha=1 - beta)
109
+
110
+ def __repr__(self):
111
+ r"""Returns a string that holds a printable representation of an
112
+ object"""
113
+ return self.module.__repr__()
neuralangelo-main/imaginaire/trainers/base.py ADDED
@@ -0,0 +1,685 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import importlib
14
+ import json
15
+ import os
16
+ import threading
17
+ import time
18
+ import wandb
19
+ from tqdm import tqdm
20
+ import inspect
21
+
22
+ import torch
23
+ from torch.autograd import profiler
24
+ from torch.cuda.amp import GradScaler, autocast
25
+
26
+ from imaginaire.datasets.utils.get_dataloader import get_train_dataloader, get_val_dataloader, get_test_dataloader
27
+ from imaginaire.models.utils.init_weight import weights_init, weights_rescale
28
+ from imaginaire.trainers.utils.get_trainer import _calculate_model_size, get_optimizer, get_scheduler, wrap_model
29
+
30
+ from imaginaire.utils.misc import to_cuda, requires_grad, to_cpu, Timer
31
+ from imaginaire.utils.distributed import master_only_print as print
32
+ from imaginaire.utils.distributed import is_master, get_rank
33
+ from imaginaire.utils.set_random_seed import set_random_seed
34
+
35
+
36
+ class BaseTrainer(object):
37
+ r"""Base trainer. We expect that all trainers inherit this class.
38
+
39
+ Args:
40
+ cfg (obj): Global configuration.
41
+ is_inference (bool): if True, load the test dataloader and run in inference mode.
42
+ """
43
+
44
+ def __init__(self, cfg, is_inference=True, seed=0):
45
+ super().__init__()
46
+ print('Setup trainer.')
47
+ self.cfg = cfg
48
+ torch.cuda.set_device(cfg.local_rank)
49
+ # Create objects for the networks, optimizers, and schedulers.
50
+ self.model = self.setup_model(cfg, seed=seed)
51
+ if not is_inference:
52
+ self.optim = self.setup_optimizer(cfg, self.model, seed=seed)
53
+ self.sched = self.setup_scheduler(cfg, self.optim)
54
+ else:
55
+ self.optim = None
56
+ self.sched = None
57
+ self.model = self.wrap_model(cfg, self.model)
58
+ # Data loaders & inference mode.
59
+ self.is_inference = is_inference
60
+ # Initialize automatic mixed precision training.
61
+ self.init_amp()
62
+ # Initialize loss functions.
63
+ self.init_losses(cfg)
64
+
65
+ self.checkpointer = Checkpointer(cfg, self.model, self.optim, self.sched)
66
+ self.timer = Timer(cfg)
67
+
68
+ # -------- The initialization steps below can be skipped during inference. --------
69
+ if self.is_inference:
70
+ return
71
+
72
+ # Initialize logging attributes.
73
+ self.init_logging_attributes()
74
+ # Initialize validation parameters.
75
+ self.init_val_parameters()
76
+ # AWS credentials.
77
+ if hasattr(cfg, 'aws_credentials_file'):
78
+ with open(cfg.aws_credentials_file) as fin:
79
+ self.credentials = json.load(fin)
80
+ else:
81
+ self.credentials = None
82
+ if 'TORCH_HOME' not in os.environ:
83
+ os.environ['TORCH_HOME'] = os.path.join(os.environ['HOME'], ".cache")
84
+
85
+ def set_data_loader(self, cfg, split, shuffle=True, drop_last=True, seed=0):
86
+ """Set the data loader corresponding to the indicated split.
87
+ Args:
88
+ split (str): Must be either 'train', 'val', or 'test'.
89
+ shuffle (bool): Whether to shuffle the data (only applies to the training set).
90
+ drop_last (bool): Whether to drop the last batch if it is not full (only applies to the training set).
91
+ seed (int): Random seed.
92
+ """
93
+ assert (split in ["train", "val", "test"])
94
+ if split == "train":
95
+ self.train_data_loader = get_train_dataloader(cfg, shuffle=shuffle, drop_last=drop_last, seed=seed)
96
+ elif split == "val":
97
+ self.eval_data_loader = get_val_dataloader(cfg, seed=seed)
98
+ elif split == "test":
99
+ self.eval_data_loader = get_test_dataloader(cfg)
100
+
101
+ def setup_model(self, cfg, seed=0):
102
+ r"""Return the networks. We will first set the random seed to a fixed value so that each GPU copy will be
103
+ initialized to have the same network weights. We will then use different random seeds for different GPUs.
104
+ After this we will wrap the network with a moving average model if applicable.
105
+
106
+ The following objects are constructed as class members:
107
+ - model (obj): Model object (historically: generator network object).
108
+
109
+ Args:
110
+ cfg (obj): Global configuration.
111
+ seed (int): Random seed.
112
+ """
113
+ # We first set the random seed for all the process so that we initialize each copy of the network the same.
114
+ set_random_seed(seed, by_rank=False)
115
+ # Construct networks
116
+ lib_model = importlib.import_module(cfg.model.type)
117
+ model = lib_model.Model(cfg.model, cfg.data)
118
+ print('model parameter count: {:,}'.format(_calculate_model_size(model)))
119
+ print(f'Initialize model weights using type: {cfg.trainer.init.type}, gain: {cfg.trainer.init.gain}')
120
+ init_bias = getattr(cfg.trainer.init, 'bias', None)
121
+ init_gain = cfg.trainer.init.gain or 1.
122
+ model.apply(weights_init(cfg.trainer.init.type, init_gain, init_bias))
123
+ model.apply(weights_rescale())
124
+ model = model.to('cuda')
125
+ # Different GPU copies of the same model will receive noises initialized with different random seeds
126
+ # (if applicable) thanks to the set_random_seed command (GPU #K has random seed = args.seed + K).
127
+ set_random_seed(seed, by_rank=True)
128
+ return model
129
+
130
+ def setup_optimizer(self, cfg, model, seed=0):
131
+ r"""Return the optimizers.
132
+
133
+ The following objects are constructed as class members:
134
+ - optim (obj): Model optimizer object.
135
+
136
+ Args:
137
+ cfg (obj): Global configuration.
138
+ seed (int): Random seed.
139
+ """
140
+ optim = get_optimizer(cfg.optim, model)
141
+ self.optim_zero_grad_kwargs = {}
142
+ if 'set_to_none' in inspect.signature(optim.zero_grad).parameters:
143
+ self.optim_zero_grad_kwargs['set_to_none'] = True
144
+ return optim
145
+
146
+ def setup_scheduler(self, cfg, optim):
147
+ r"""Return the schedulers.
148
+
149
+ The following objects are constructed as class members:
150
+ - sched (obj): Model optimizer scheduler object.
151
+
152
+ Args:
153
+ cfg (obj): Global configuration.
154
+ """
155
+ return get_scheduler(cfg.optim, optim)
156
+
157
+ def wrap_model(self, cfg, model):
158
+ # Moving average model & data distributed data parallel wrapping.
159
+ model = wrap_model(cfg, model)
160
+ # Get actual modules from wrappers.
161
+ if cfg.trainer.ema_config.enabled:
162
+ # Two wrappers (DDP + model average).
163
+ self.model_module = model.module.module
164
+ else:
165
+ # One wrapper (DDP)
166
+ self.model_module = model.module
167
+ return model
168
+
169
+ def init_amp(self):
170
+ r"""Initialize automatic mixed precision training."""
171
+
172
+ if getattr(self.cfg.trainer, 'allow_tf32', True):
173
+ print("Allow TensorFloat32 operations on supported devices")
174
+ torch.backends.cudnn.allow_tf32 = True
175
+ torch.backends.cuda.matmul.allow_tf32 = True
176
+ else:
177
+ torch.backends.cudnn.allow_tf32 = False
178
+ torch.backends.cuda.matmul.allow_tf32 = False
179
+
180
+ if self.cfg.trainer.amp_config.enabled:
181
+ print("Using automatic mixed precision training.")
182
+
183
+ # amp scaler can be used without mixed precision training
184
+ if hasattr(self.cfg.trainer, 'scaler_config'):
185
+ scaler_kwargs = vars(self.cfg.trainer.scaler_config)
186
+ scaler_kwargs['enabled'] = self.cfg.trainer.amp_config.enabled and \
187
+ getattr(self.cfg.trainer.scaler_config, 'enabled', True)
188
+ else:
189
+ scaler_kwargs = vars(self.cfg.trainer.amp_config) # backward compatibility
190
+ scaler_kwargs.pop('dtype', None)
191
+ scaler_kwargs.pop('cache_enabled', None)
192
+
193
+ self.scaler = GradScaler(**scaler_kwargs)
194
+
195
+ def init_losses(self, cfg):
196
+ r"""Initialize loss functions. All loss names have weights. Some have criterion modules."""
197
+ self.losses = dict()
198
+
199
+ # Mapping from loss names to criterion modules.
200
+ self.criteria = torch.nn.ModuleDict()
201
+ # Mapping from loss names to loss weights.
202
+ self.weights = dict()
203
+
204
+ self._init_loss(cfg) # this should be implemented by children classes
205
+
206
+ for loss_name, loss_weight in self.weights.items():
207
+ print("Loss {:<20} Weight {}".format(loss_name, loss_weight))
208
+ if loss_name in self.criteria.keys() and self.criteria[loss_name] is not None:
209
+ self.criteria[loss_name].to('cuda')
210
+
211
+ def init_logging_attributes(self):
212
+ r"""Initialize logging attributes."""
213
+ self.current_iteration = 0
214
+ self.current_epoch = 0
215
+ self.start_iteration_time = None
216
+ self.start_epoch_time = None
217
+ self.elapsed_iteration_time = 0
218
+ if self.cfg.speed_benchmark:
219
+ self.timer.reset()
220
+
221
+ def init_val_parameters(self):
222
+ r"""Initialize validation parameters."""
223
+ if self.cfg.metrics_iter is None:
224
+ self.cfg.metrics_iter = self.cfg.checkpoint.save_iter
225
+ if self.cfg.metrics_epoch is None:
226
+ self.cfg.metrics_epoch = self.cfg.checkpoint.save_epoch
227
+
228
+ def init_wandb(self, cfg, wandb_id=None, project="", run_name=None, mode="online", resume="allow", use_group=False):
229
+ r"""Initialize Weights & Biases (wandb) logger.
230
+
231
+ Args:
232
+ cfg (obj): Global configuration.
233
+ wandb_id (str): A unique ID for this run, used for resuming.
234
+ project (str): The name of the project where you're sending the new run.
235
+ If the project is not specified, the run is put in an "Uncategorized" project.
236
+ run_name (str): name for each wandb run (useful for logging changes)
237
+ mode (str): online/offline/disabled
238
+ """
239
+ if is_master():
240
+ print('Initialize wandb')
241
+ if not wandb_id:
242
+ wandb_path = os.path.join(cfg.logdir, "wandb_id.txt")
243
+ if self.checkpointer.resume and os.path.exists(wandb_path):
244
+ with open(wandb_path, "r") as f:
245
+ wandb_id = f.read()
246
+ else:
247
+ wandb_id = wandb.util.generate_id()
248
+ with open(wandb_path, "w") as f:
249
+ f.write(wandb_id)
250
+ if use_group:
251
+ group, name = cfg.logdir.split("/")[-2:]
252
+ else:
253
+ group, name = None, os.path.basename(cfg.logdir)
254
+
255
+ if run_name is not None:
256
+ name = run_name
257
+
258
+ wandb.init(id=wandb_id,
259
+ project=project,
260
+ config=cfg,
261
+ group=group,
262
+ name=name,
263
+ dir=cfg.logdir,
264
+ resume=resume,
265
+ settings=wandb.Settings(start_method="fork"),
266
+ mode=mode)
267
+ wandb.config.update({'dataset': cfg.data.name})
268
+ if self.model_module is not None:
269
+ wandb.watch(self.model_module)
270
+
271
+ def start_of_epoch(self, current_epoch):
272
+ r"""Things to do before an epoch.
273
+
274
+ Args:
275
+ current_epoch (int): Current number of epoch.
276
+ """
277
+ self._start_of_epoch(current_epoch)
278
+ self.current_epoch = current_epoch
279
+ self.start_epoch_time = time.time()
280
+
281
+ def start_of_iteration(self, data, current_iteration):
282
+ r"""Things to do before an iteration.
283
+
284
+ Args:
285
+ data (dict): Data used for the current iteration.
286
+ current_iteration (int): Current number of iteration.
287
+ """
288
+ data = self._start_of_iteration(data, current_iteration)
289
+ data = to_cuda(data)
290
+ self.current_iteration = current_iteration
291
+ self.model.train()
292
+ self.start_iteration_time = time.time()
293
+ return data
294
+
295
+ def end_of_iteration(self, data, current_epoch, current_iteration):
296
+ r"""Things to do after an iteration.
297
+
298
+ Args:
299
+ data (dict): Data used for the current iteration.
300
+ current_epoch (int): Current number of epoch.
301
+ current_iteration (int): Current number of iteration.
302
+ """
303
+ self.current_iteration = current_iteration
304
+ self.current_epoch = current_epoch
305
+
306
+ # Accumulate time
307
+ self.elapsed_iteration_time += time.time() - self.start_iteration_time
308
+ # Logging.
309
+ if current_iteration % self.cfg.logging_iter == 0:
310
+ avg_time = self.elapsed_iteration_time / self.cfg.logging_iter
311
+ self.timer.time_iteration = avg_time
312
+ print('Iteration: {}, average iter time: {:6f}.'.format(current_iteration, avg_time))
313
+ self.elapsed_iteration_time = 0
314
+
315
+ if self.cfg.speed_benchmark:
316
+ # only needed when analyzing computation bottleneck.
317
+ self.timer._print_speed_benchmark(avg_time)
318
+
319
+ self._end_of_iteration(data, current_epoch, current_iteration)
320
+
321
+ # Save everything to the checkpoint by time period.
322
+ if self.checkpointer.reached_checkpointing_period(self.timer):
323
+ self.checkpointer.save(current_epoch, current_iteration)
324
+ self.timer.checkpoint_tic() # reset timer
325
+
326
+ # Save everything to the checkpoint.
327
+ if current_iteration % self.cfg.checkpoint.save_iter == 0:
328
+ self.checkpointer.save(current_epoch, current_iteration)
329
+
330
+ # Save everything to the checkpoint using the name 'latest_checkpoint.pt'.
331
+ if current_iteration % self.cfg.checkpoint.save_latest_iter == 0:
332
+ if current_iteration >= self.cfg.checkpoint.save_latest_iter:
333
+ self.checkpointer.save(current_epoch, current_iteration, True)
334
+
335
+ # Update the learning rate policy for the generator if operating in the iteration mode.
336
+ if self.cfg.optim.sched.iteration_mode:
337
+ self.sched.step()
338
+
339
+ # This iteration was successfully finished. Reset timeout counter.
340
+ self.timer.reset_timeout_counter()
341
+
342
+ def end_of_epoch(self, data, current_epoch, current_iteration):
343
+ r"""Things to do after an epoch.
344
+
345
+ Args:
346
+ data (dict): Data used for the current iteration.
347
+
348
+ current_epoch (int): Current number of epoch.
349
+ current_iteration (int): Current number of iteration.
350
+ """
351
+ # Update the learning rate policy for the generator if operating in the epoch mode.
352
+ self.current_iteration = current_iteration
353
+ self.current_epoch = current_epoch
354
+ if not self.cfg.optim.sched.iteration_mode:
355
+ self.sched.step()
356
+ elapsed_epoch_time = time.time() - self.start_epoch_time
357
+ # Logging.
358
+ print('Epoch: {}, total time: {:6f}.'.format(current_epoch, elapsed_epoch_time))
359
+ self.timer.time_epoch = elapsed_epoch_time
360
+ self._end_of_epoch(data, current_epoch, current_iteration)
361
+
362
+ # Save everything to the checkpoint.
363
+ if current_epoch % self.cfg.checkpoint.save_epoch == 0:
364
+ self.checkpointer.save(current_epoch, current_iteration)
365
+
366
+ def _extra_step(self, data):
367
+ pass
368
+
369
+ def _start_of_epoch(self, current_epoch):
370
+ r"""Operations to do before starting an epoch.
371
+
372
+ Args:
373
+ current_epoch (int): Current number of epoch.
374
+ """
375
+ pass
376
+
377
+ def _start_of_iteration(self, data, current_iteration):
378
+ r"""Operations to do before starting an iteration.
379
+
380
+ Args:
381
+ data (dict): Data used for the current iteration.
382
+ current_iteration (int): Current epoch number.
383
+ Returns:
384
+ (dict): Data used for the current iteration. They might be
385
+ processed by the custom _start_of_iteration function.
386
+ """
387
+ return data
388
+
389
+ def _end_of_iteration(self, data, current_epoch, current_iteration):
390
+ r"""Operations to do after an iteration.
391
+
392
+ Args:
393
+ data (dict): Data used for the current iteration.
394
+ current_epoch (int): Current number of epoch.
395
+ current_iteration (int): Current epoch number.
396
+ """
397
+ pass
398
+
399
+ def _end_of_epoch(self, data, current_epoch, current_iteration):
400
+ r"""Operations to do after an epoch.
401
+
402
+ Args:
403
+ data (dict): Data used for the current iteration.
404
+ current_epoch (int): Current number of epoch.
405
+ current_iteration (int): Current epoch number.
406
+ """
407
+ pass
408
+
409
+ def _get_visualizations(self, data):
410
+ r"""Compute visualization outputs.
411
+
412
+ Args:
413
+ data (dict): Data used for the current iteration.
414
+ """
415
+ return None
416
+
417
+ def _init_loss(self, cfg):
418
+ r"""Every trainer should implement its own init loss function."""
419
+ raise NotImplementedError
420
+
421
+ def train_step(self, data, last_iter_in_epoch=False):
422
+ r"""One training step.
423
+
424
+ Args:
425
+ data (dict): Data used for the current iteration.
426
+ """
427
+ # Set requires_grad flags.
428
+ requires_grad(self.model_module, True)
429
+
430
+ # Compute the loss.
431
+ self.timer._time_before_forward()
432
+
433
+ autocast_dtype = getattr(self.cfg.trainer.amp_config, 'dtype', 'float16')
434
+ autocast_dtype = torch.bfloat16 if autocast_dtype == 'bfloat16' else torch.float16
435
+ amp_kwargs = {
436
+ 'enabled': self.cfg.trainer.amp_config.enabled,
437
+ 'dtype': autocast_dtype
438
+ }
439
+ with autocast(**amp_kwargs):
440
+ total_loss = self.model_forward(data)
441
+ # Scale down the loss w.r.t. gradient accumulation iterations.
442
+ total_loss = total_loss / float(self.cfg.trainer.grad_accum_iter)
443
+
444
+ # Backpropagate the loss.
445
+ self.timer._time_before_backward()
446
+ self.scaler.scale(total_loss).backward()
447
+
448
+ self._extra_step(data)
449
+
450
+ # Perform an optimizer step. This enables gradient accumulation when grad_accum_iter is not 1.
451
+ if (self.current_iteration + 1) % self.cfg.trainer.grad_accum_iter == 0 or last_iter_in_epoch:
452
+ self.timer._time_before_step()
453
+ self.scaler.step(self.optim)
454
+ self.scaler.update()
455
+ # Zero out the gradients.
456
+ self.optim.zero_grad(**self.optim_zero_grad_kwargs)
457
+
458
+ # Update model average.
459
+ self.timer._time_before_model_avg()
460
+ if self.cfg.trainer.ema_config.enabled:
461
+ self.model.module.update_average()
462
+
463
+ self._detach_losses()
464
+ self.timer._time_before_leave_gen()
465
+
466
+ def model_forward(self, data):
467
+ r"""Every trainer should implement its own model forward."""
468
+ raise NotImplementedError
469
+
470
+ def train(self, cfg, data_loader, single_gpu=False, profile=False, show_pbar=False):
471
+ r"""Generic training loop. Main structure in a nutshell:
472
+ for epoch in [start_epoch, end_epoch]:
473
+ for batch in dataset (one epoch):
474
+ train_step(batch)
475
+
476
+ Args:
477
+ cfg (obj): Global configuration.
478
+ data_loader (torch.utils.data.DataLoader): PyTorch dataloader.
479
+ single_gpu (bool): Use only a single GPU.
480
+ profile (bool): Enable profiling.
481
+ show_pbar (bool): Whether to show the progress bar
482
+ """
483
+ start_epoch = self.checkpointer.resume_epoch or self.current_epoch # The epoch to start with.
484
+ current_iteration = self.checkpointer.resume_iteration or self.current_iteration # The starting iteration.
485
+
486
+ self.timer.checkpoint_tic() # start timer
487
+ self.timer.reset_timeout_counter()
488
+ for current_epoch in range(start_epoch, cfg.max_epoch):
489
+ if not single_gpu:
490
+ data_loader.sampler.set_epoch(current_epoch)
491
+ self.start_of_epoch(current_epoch)
492
+ if show_pbar:
493
+ data_loader_wrapper = tqdm(data_loader, desc=f"Training epoch {current_epoch + 1}", leave=False)
494
+ else:
495
+ data_loader_wrapper = data_loader
496
+ for it, data in enumerate(data_loader_wrapper):
497
+ with profiler.profile(enabled=profile,
498
+ use_cuda=True,
499
+ profile_memory=True,
500
+ record_shapes=True) as prof:
501
+ data = self.start_of_iteration(data, current_iteration)
502
+
503
+ self.train_step(data, last_iter_in_epoch=(it == len(data_loader) - 1))
504
+
505
+ current_iteration += 1
506
+ if show_pbar:
507
+ data_loader_wrapper.set_postfix(iter=current_iteration)
508
+ if it == len(data_loader) - 1:
509
+ self.end_of_iteration(data, current_epoch + 1, current_iteration)
510
+ else:
511
+ self.end_of_iteration(data, current_epoch, current_iteration)
512
+ if current_iteration >= cfg.max_iter:
513
+ print('Done with training!!!')
514
+ return
515
+ if profile:
516
+ print(prof.key_averages().table(sort_by="cuda_time_total", row_limit=20))
517
+ prof.export_chrome_trace(os.path.join(cfg.logdir, "trace.json"))
518
+
519
+ self.end_of_epoch(data, current_epoch + 1, current_iteration)
520
+ print('Done with training!!!')
521
+
522
+ def test(self, data_loader, output_dir, inference_args, show_pbar=False):
523
+ r"""Compute results images and save the results in the specified folder.
524
+ Args:
525
+ data_loader (torch.utils.data.DataLoader): PyTorch dataloader.
526
+ output_dir (str): Target location for saving the output image.
527
+ """
528
+ pass
529
+
530
+ def _get_total_loss(self):
531
+ r"""Return the total loss to be backpropagated.
532
+ """
533
+ total_loss = torch.tensor(0., device=torch.device('cuda'))
534
+ # Iterates over all possible losses.
535
+ for loss_name in self.weights:
536
+ if loss_name in self.losses:
537
+ # Multiply it with the corresponding weight and add it to the total loss.
538
+ total_loss += self.losses[loss_name] * self.weights[loss_name]
539
+ self.losses['total'] = total_loss # logging purpose
540
+ return total_loss
541
+
542
+ def _detach_losses(self):
543
+ r"""Detach all logging variables to prevent potential memory leak."""
544
+ for loss_name in self.losses:
545
+ self.losses[loss_name] = self.losses[loss_name].detach()
546
+
547
+ def finalize(self, cfg):
548
+ # Finish the W&B logger.
549
+ if is_master():
550
+ wandb.finish()
551
+
552
+
553
+ class Checkpointer(object):
554
+
555
+ def __init__(self, cfg, model, optim=None, sched=None):
556
+ self.model = model
557
+ self.optim = optim
558
+ self.sched = sched
559
+ self.logdir = cfg.logdir
560
+ self.save_period = cfg.checkpoint.save_period
561
+ self.strict_resume = cfg.checkpoint.strict_resume
562
+ self.iteration_mode = cfg.optim.sched.iteration_mode
563
+ self.resume = False
564
+ self.resume_epoch = self.resume_iteration = None
565
+
566
+ def save(self, current_epoch, current_iteration, latest=False):
567
+ r"""Save network weights, optimizer parameters, scheduler parameters to a checkpoint.
568
+
569
+ Args:
570
+ current_epoch (int): Current epoch.
571
+ current_iteration (int): Current iteration.
572
+ latest (bool): If ``True``, save it using the name 'latest_checkpoint.pt'.
573
+ """
574
+ checkpoint_file = 'latest_checkpoint.pt' if latest else \
575
+ f'epoch_{current_epoch:05}_iteration_{current_iteration:09}_checkpoint.pt'
576
+ if is_master():
577
+ save_dict = to_cpu(self._collect_state_dicts())
578
+ save_dict.update(
579
+ epoch=current_epoch,
580
+ iteration=current_iteration,
581
+ )
582
+ # Run the checkpoint saver in a separate thread.
583
+ threading.Thread(
584
+ target=self._save_worker, daemon=False, args=(save_dict, checkpoint_file, get_rank())).start()
585
+ checkpoint_path = self._get_full_path(checkpoint_file)
586
+ return checkpoint_path
587
+
588
+ def _save_worker(self, save_dict, checkpoint_file, rank=0):
589
+ checkpoint_path = self._get_full_path(checkpoint_file)
590
+ # Save to local disk.
591
+ os.makedirs(os.path.dirname(checkpoint_path), exist_ok=True)
592
+ torch.save(save_dict, checkpoint_path)
593
+ if rank == 0:
594
+ self.write_latest_checkpoint_file(checkpoint_file)
595
+ print('Saved checkpoint to {}'.format(checkpoint_path))
596
+
597
+ def _collect_state_dicts(self):
598
+ r"""Collect all the state dicts from network modules to be saved."""
599
+ return dict(
600
+ model=self.model.state_dict(),
601
+ optim=self.optim.state_dict(),
602
+ sched=self.sched.state_dict(),
603
+ )
604
+
605
+ def load(self, checkpoint_path=None, resume=False, load_opt=True, load_sch=True, **kwargs):
606
+ r"""Load network weights, optimizer parameters, scheduler parameters from a checkpoint.
607
+ Args:
608
+ checkpoint_path (str): Path to the checkpoint (local file or S3 key).
609
+ resume (bool): if False, only the model weights are loaded. If True, the metadata (epoch/iteration) and
610
+ optimizer/scheduler (optional) are also loaded.
611
+ load_opt (bool): Whether to load the optimizer state dict (resume should be True).
612
+ load_sch (bool): Whether to load the scheduler state dict (resume should be True).
613
+ """
614
+ # Priority: (1) checkpoint_path (2) latest_path (3) train from scratch.
615
+ self.resume = resume
616
+ # If checkpoint path were not specified, try to load the latest one from the same run.
617
+ if resume and checkpoint_path is None:
618
+ latest_checkpoint_file = self.read_latest_checkpoint_file()
619
+ if latest_checkpoint_file is not None:
620
+ checkpoint_path = self._get_full_path(latest_checkpoint_file)
621
+ # Load checkpoint.
622
+ if checkpoint_path is not None:
623
+ self._check_checkpoint_exists(checkpoint_path)
624
+ self.checkpoint_path = checkpoint_path
625
+ state_dict = torch.load(checkpoint_path, map_location=lambda storage, loc: storage)
626
+ print(f"Loading checkpoint (local): {checkpoint_path}")
627
+ # Load the state dicts.
628
+ print('- Loading the model...')
629
+ self.model.load_state_dict(state_dict['model'], strict=self.strict_resume)
630
+ if resume:
631
+ try:
632
+ self.resume_epoch = state_dict['epoch']
633
+ self.resume_iteration = state_dict['iteration']
634
+ except Exception: # TODO: for backward compatibility, should be removed eventually.
635
+ self.resume_epoch = state_dict['current_epoch']
636
+ self.resume_iteration = state_dict['current_iteration']
637
+ self.sched.last_epoch = self.resume_iteration if self.iteration_mode else self.resume_epoch
638
+ if load_opt:
639
+ print('- Loading the optimizer...')
640
+ self.optim.load_state_dict(state_dict['optim'])
641
+ if load_sch:
642
+ print('- Loading the scheduler...')
643
+ self.sched.load_state_dict(state_dict['sched'])
644
+ print(f"Done with loading the checkpoint (epoch {self.resume_epoch}, iter {self.resume_iteration}).")
645
+ else:
646
+ print('Done with loading the checkpoint.')
647
+ else:
648
+ # Checkpoint not found and not specified. We will train everything from scratch.
649
+ print('Training from scratch.')
650
+ torch.cuda.empty_cache()
651
+
652
+ def _get_full_path(self, file):
653
+ return os.path.join(self.logdir, file)
654
+
655
+ def _get_latest_pointer_path(self):
656
+ return self._get_full_path('latest_checkpoint.txt')
657
+
658
+ def read_latest_checkpoint_file(self):
659
+ checkpoint_file = None
660
+ latest_path = self._get_latest_pointer_path()
661
+ if os.path.exists(latest_path):
662
+ checkpoint_file = open(latest_path).read().strip()
663
+ if checkpoint_file.startswith("latest_checkpoint:"): # TODO: for backward compatibility, to be removed
664
+ checkpoint_file = checkpoint_file.split(' ')[-1]
665
+ return checkpoint_file
666
+
667
+ def write_latest_checkpoint_file(self, checkpoint_file):
668
+ latest_path = self._get_latest_pointer_path()
669
+ content = f"{checkpoint_file}\n"
670
+ with open(latest_path, "w") as file:
671
+ file.write(content)
672
+
673
+ def _check_checkpoint_exists(self, checkpoint_path):
674
+ if not os.path.exists(checkpoint_path):
675
+ raise FileNotFoundError(f'File not found (local): {checkpoint_path}')
676
+
677
+ def reached_checkpointing_period(self, timer):
678
+ save_now = torch.cuda.BoolTensor([False])
679
+ if is_master():
680
+ if timer.checkpoint_toc() > self.save_period:
681
+ save_now.fill_(True)
682
+ if save_now:
683
+ if is_master():
684
+ print('checkpointing period!')
685
+ return save_now
neuralangelo-main/imaginaire/trainers/utils/get_trainer.py ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import importlib
14
+ import torch
15
+ import torch.distributed as dist
16
+ import torch.nn as nn
17
+ from torch.optim import lr_scheduler
18
+ from imaginaire.models.utils.model_average import ModelAverage
19
+
20
+
21
+ def get_trainer(cfg, is_inference=True, seed=0):
22
+ """Return the trainer object.
23
+
24
+ Args:
25
+ cfg (Config): Loaded config object.
26
+ is_inference (bool): Inference mode.
27
+
28
+ Returns:
29
+ (obj): Trainer object.
30
+ """
31
+ trainer_lib = importlib.import_module(cfg.trainer.type)
32
+ trainer = trainer_lib.Trainer(cfg, is_inference=is_inference, seed=seed)
33
+ return trainer
34
+
35
+
36
+ def wrap_model(cfg, model):
37
+ r"""Wrap the networks with AMP DDP and (optionally) model average.
38
+
39
+ Args:
40
+ cfg (obj): Global configuration.
41
+ model (obj): Model object.
42
+
43
+ Returns:
44
+ (dict):
45
+ - model (obj): Model object.
46
+ """
47
+ # Apply model average wrapper.
48
+ if cfg.trainer.ema_config.enabled:
49
+ model = ModelAverage(model,
50
+ cfg.trainer.ema_config.beta,
51
+ cfg.trainer.ema_config.start_iteration,
52
+ )
53
+ model = _wrap_model(cfg, model)
54
+ return model
55
+
56
+
57
+ class WrappedModel(nn.Module):
58
+ r"""Dummy wrapping the module.
59
+ """
60
+
61
+ def __init__(self, module):
62
+ super(WrappedModel, self).__init__()
63
+ self.module = module
64
+
65
+ def forward(self, *args, **kwargs):
66
+ r"""PyTorch module forward function overload."""
67
+ return self.module(*args, **kwargs)
68
+
69
+
70
+ def _wrap_model(cfg, model):
71
+ r"""Wrap a model for distributed data parallel training.
72
+
73
+ Args:
74
+ model (obj): PyTorch network model.
75
+
76
+ Returns:
77
+ (obj): Wrapped PyTorch network model.
78
+ """
79
+ # Apply DDP wrapper.
80
+ if dist.is_available() and dist.is_initialized():
81
+ model = torch.nn.parallel.DistributedDataParallel(
82
+ model,
83
+ device_ids=[cfg.local_rank],
84
+ output_device=cfg.local_rank,
85
+ find_unused_parameters=cfg.trainer.ddp_config.find_unused_parameters,
86
+ static_graph=cfg.trainer.ddp_config.static_graph,
87
+ broadcast_buffers=False,
88
+ )
89
+ else:
90
+ model = WrappedModel(model)
91
+ return model
92
+
93
+
94
+ def _calculate_model_size(model):
95
+ r"""Calculate number of parameters in a PyTorch network.
96
+
97
+ Args:
98
+ model (obj): PyTorch network.
99
+
100
+ Returns:
101
+ (int): Number of parameters.
102
+ """
103
+ return sum(p.numel() for p in model.parameters() if p.requires_grad)
104
+
105
+
106
+ def get_optimizer(cfg_optim, model):
107
+ r"""Return the optimizer object.
108
+
109
+ Args:
110
+ cfg_optim (obj): Config for the specific optimization module (gen/dis).
111
+ model (obj): PyTorch network object.
112
+
113
+ Returns:
114
+ (obj): Pytorch optimizer
115
+ """
116
+ if hasattr(model, 'get_param_groups'):
117
+ # Allow the network to use different hyperparameters (e.g., learning rate) for different parameters.
118
+ params = model.get_param_groups(cfg_optim)
119
+ else:
120
+ params = model.parameters()
121
+
122
+ try:
123
+ # Try the PyTorch optimizer class first.
124
+ optimizer_class = getattr(torch.optim, cfg_optim.type)
125
+ except AttributeError:
126
+ raise NotImplementedError(f"Optimizer {cfg_optim.type} is not yet implemented.")
127
+ optimizer_kwargs = cfg_optim.params
128
+
129
+ # We will try to use fuse optimizers by default.
130
+ try:
131
+ from apex.optimizers import FusedAdam, FusedSGD
132
+ fused_opt = cfg_optim.fused_opt
133
+ except (ImportError, ModuleNotFoundError):
134
+ fused_opt = False
135
+
136
+ if fused_opt:
137
+ if cfg_optim.type == 'Adam':
138
+ optimizer_class = FusedAdam
139
+ optimizer_kwargs['adam_w_mode'] = False
140
+ elif cfg_optim.type == 'AdamW':
141
+ optimizer_class = FusedAdam
142
+ optimizer_kwargs['adam_w_mode'] = True
143
+ elif cfg_optim.type == 'SGD':
144
+ optimizer_class = FusedSGD
145
+ if cfg_optim.type in ["RAdam", "RMSprop"]:
146
+ optimizer_kwargs["foreach"] = fused_opt
147
+
148
+ optim = optimizer_class(params, **optimizer_kwargs)
149
+
150
+ return optim
151
+
152
+
153
+ def get_scheduler(cfg_optim, optim):
154
+ """Return the scheduler object.
155
+
156
+ Args:
157
+ cfg_optim (obj): Config for the specific optimization module (gen/dis).
158
+ optim (obj): PyTorch optimizer object.
159
+
160
+ Returns:
161
+ (obj): Scheduler
162
+ """
163
+ if cfg_optim.sched.type == 'step':
164
+ scheduler = lr_scheduler.StepLR(optim,
165
+ step_size=cfg_optim.sched.step_size,
166
+ gamma=cfg_optim.sched.gamma)
167
+ elif cfg_optim.sched.type == 'constant':
168
+ scheduler = lr_scheduler.LambdaLR(optim, lambda x: 1)
169
+ elif cfg_optim.sched.type == 'linear_warmup':
170
+ scheduler = lr_scheduler.LambdaLR(
171
+ optim, lambda x: x * 1.0 / cfg_optim.sched.warmup if x < cfg_optim.sched.warmup else 1.0)
172
+ elif cfg_optim.sched.type == 'cosine_warmup':
173
+
174
+ warmup_scheduler = lr_scheduler.LinearLR(
175
+ optim,
176
+ start_factor=1.0 / cfg_optim.sched.warmup,
177
+ end_factor=1.0,
178
+ total_iters=cfg_optim.sched.warmup
179
+ )
180
+ T_max_val = cfg_optim.sched.decay_steps - cfg_optim.sched.warmup
181
+ cosine_lr_scheduler = lr_scheduler.CosineAnnealingLR(
182
+ optim,
183
+ T_max=T_max_val,
184
+ eta_min=getattr(cfg_optim.sched, 'eta_min', 0),
185
+ )
186
+ scheduler = lr_scheduler.SequentialLR(
187
+ optim,
188
+ schedulers=[warmup_scheduler, cosine_lr_scheduler],
189
+ milestones=[cfg_optim.sched.warmup]
190
+ )
191
+
192
+ elif cfg_optim.sched.type == 'linear':
193
+ # Start linear decay from here.
194
+ decay_start = cfg_optim.sched.decay_start
195
+ # End linear decay here.
196
+ # Continue to train using the lowest learning rate till the end.
197
+ decay_end = cfg_optim.sched.decay_end
198
+ # Lowest learning rate multiplier.
199
+ decay_target = cfg_optim.sched.decay_target
200
+
201
+ def sch(x):
202
+ decay = ((x - decay_start) * decay_target + decay_end - x) / (decay_end - decay_start)
203
+ return min(max(decay, decay_target), 1.)
204
+
205
+ scheduler = lr_scheduler.LambdaLR(optim, lambda x: sch(x))
206
+ elif cfg_optim.sched.type == 'step_with_warmup':
207
+ # The step_size and gamma follows the signature of lr_scheduler.StepLR.
208
+ step_size = cfg_optim.sched.step_size,
209
+ gamma = cfg_optim.sched.gamma
210
+ # An additional parameter defines the warmup iteration.
211
+ warmup_step_size = cfg_optim.sched.warmup_step_size
212
+
213
+ def sch(x):
214
+ lr_after_warmup = gamma ** (warmup_step_size // step_size)
215
+ if x < warmup_step_size:
216
+ return x / warmup_step_size * lr_after_warmup
217
+ else:
218
+ return gamma ** (x // step_size)
219
+
220
+ scheduler = lr_scheduler.LambdaLR(optim, lambda x: sch(x))
221
+ else:
222
+ return NotImplementedError('Learning rate policy {} not implemented.'.format(cfg_optim.sched.type))
223
+ return scheduler
neuralangelo-main/imaginaire/trainers/utils/logging.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import datetime
14
+ import os
15
+
16
+ import torch.distributed as dist
17
+
18
+ from imaginaire.utils.distributed import is_master, broadcast_object_list
19
+ from imaginaire.utils.distributed import master_only_print as print
20
+ from imaginaire.trainers.utils.meters import set_summary_writer
21
+
22
+
23
+ def get_date_uid():
24
+ """Generate a unique id based on date.
25
+ Returns:
26
+ str: Return uid string, e.g. '20171122171307111552'.
27
+ """
28
+ return str(datetime.datetime.now().strftime("%Y_%m%d_%H%M_%S"))
29
+
30
+
31
+ def init_logging(config_path, logdir, makedir=True):
32
+ r"""Create log directory for storing checkpoints and output images.
33
+
34
+ Args:
35
+ config_path (str): Path to the configuration file.
36
+ logdir (str or None): Log directory name
37
+ makedir (bool): Make a new dir or not
38
+ Returns:
39
+ str: Return log dir
40
+ """
41
+ def _create_logdir(_config_path, _logdir, _root_dir):
42
+ config_file = os.path.basename(_config_path)
43
+ date_uid = get_date_uid()
44
+ # example: logs/2019_0125_1047_58_spade_cocostuff
45
+ _log_file = '_'.join([date_uid, os.path.splitext(config_file)[0]])
46
+ if _logdir is None:
47
+ _logdir = os.path.join(_root_dir, _log_file)
48
+ if makedir:
49
+ print('Make folder {}'.format(_logdir))
50
+ os.makedirs(_logdir, exist_ok=True)
51
+ _tensorboard_dir = os.path.join(_logdir, 'tensorboard')
52
+ os.makedirs(_tensorboard_dir, exist_ok=True)
53
+ set_summary_writer(_tensorboard_dir)
54
+ return _logdir
55
+
56
+ root_dir = 'logs'
57
+ if dist.is_available():
58
+ if dist.is_initialized():
59
+ message = [None]
60
+ if is_master():
61
+ logdir = _create_logdir(config_path, logdir, root_dir)
62
+ message = [logdir]
63
+
64
+ # Send logdir from master to all workers.
65
+ message = broadcast_object_list(message=message, src=0)
66
+ logdir = message[0]
67
+ else:
68
+ logdir = _create_logdir(config_path, logdir, root_dir)
69
+ else:
70
+ logdir = _create_logdir(config_path, logdir, root_dir)
71
+
72
+ return logdir
neuralangelo-main/imaginaire/trainers/utils/meters.py ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import math
14
+ import torch
15
+ import wandb
16
+ from torch.utils.tensorboard import SummaryWriter
17
+
18
+ from imaginaire.utils.distributed import master_only, dist_all_reduce_tensor, \
19
+ is_master, get_rank
20
+
21
+ from imaginaire.utils.distributed import master_only_print as print
22
+
23
+ LOG_WRITER = None
24
+ LOG_DIR = None
25
+
26
+
27
+ @torch.no_grad()
28
+ def sn_reshape_weight_to_matrix(weight):
29
+ r"""Reshape weight to obtain the matrix form.
30
+
31
+ Args:
32
+ weight (Parameters): pytorch layer parameter tensor.
33
+ """
34
+ weight_mat = weight
35
+ height = weight_mat.size(0)
36
+ return weight_mat.reshape(height, -1)
37
+
38
+
39
+ @torch.no_grad()
40
+ def get_weight_stats(mod):
41
+ r"""Get weight state
42
+
43
+ Args:
44
+ mod: Pytorch module
45
+ """
46
+ if mod.weight_orig.grad is not None:
47
+ grad_norm = mod.weight_orig.grad.data.norm().item()
48
+ else:
49
+ grad_norm = 0.
50
+ weight_norm = mod.weight_orig.data.norm().item()
51
+ weight_mat = sn_reshape_weight_to_matrix(mod.weight_orig)
52
+ sigma = torch.sum(mod.weight_u * torch.mv(weight_mat, mod.weight_v))
53
+ return grad_norm, weight_norm, sigma
54
+
55
+
56
+ @master_only
57
+ def set_summary_writer(log_dir):
58
+ r"""Set summary writer
59
+
60
+ Args:
61
+ log_dir (str): Log directory.
62
+ """
63
+ global LOG_DIR, LOG_WRITER
64
+ LOG_DIR = log_dir
65
+ LOG_WRITER = SummaryWriter(log_dir=log_dir)
66
+
67
+
68
+ def write_summary(name, summary, step, hist=False):
69
+ """Utility function for write summary to log_writer.
70
+ """
71
+ global LOG_WRITER
72
+ lw = LOG_WRITER
73
+ if lw is None:
74
+ raise Exception("Log writer not set.")
75
+ if hist:
76
+ lw.add_histogram(name, summary, step)
77
+ else:
78
+ lw.add_scalar(name, summary, step)
79
+
80
+
81
+ class Meter(object):
82
+ """Meter is to keep track of statistics along steps.
83
+ Meters write values for purpose like printing average values.
84
+ Meters can be flushed to log files (i.e. TensorBoard for now)
85
+ regularly.
86
+
87
+ Args:
88
+ name (str): the name of meter
89
+ reduce (bool): If ``True``, perform a distributed reduce for the log
90
+ values across all GPUs.
91
+ """
92
+
93
+ def __init__(self, name, reduce=True):
94
+ self.name = name
95
+ self.reduce = reduce
96
+ self.values = []
97
+
98
+ def reset(self):
99
+ r"""Reset the meter values"""
100
+ if not self.reduce and get_rank() != 0:
101
+ return
102
+ self.values = []
103
+
104
+ def write(self, value):
105
+ r"""Record the value"""
106
+ if not self.reduce and get_rank() != 0:
107
+ return
108
+ if value is not None:
109
+ self.values.append(value)
110
+
111
+ def flush(self, step):
112
+ r"""Write the value in the tensorboard.
113
+
114
+ Args:
115
+ step (int): Epoch or iteration number.
116
+ """
117
+ if not self.reduce and get_rank() != 0:
118
+ return
119
+ values = torch.tensor(self.values, device="cuda")
120
+ if self.reduce:
121
+ values = dist_all_reduce_tensor(values)
122
+
123
+ if not all(math.isfinite(x) for x in values):
124
+ print("meter {} contained a nan or inf.".format(self.name))
125
+ filtered_values = list(filter(lambda x: math.isfinite(x), self.values))
126
+ if float(len(filtered_values)) != 0:
127
+ value = float(sum(filtered_values)) / float(len(filtered_values))
128
+ if is_master():
129
+ write_summary(self.name, value, step)
130
+ wandb.log({self.name: value}, step=step)
131
+ self.reset()
132
+
133
+ @master_only
134
+ def write_image(self, img_grid, step):
135
+ r"""Write the value in the tensorboard.
136
+
137
+ Args:
138
+ img_grid:
139
+ step (int): Epoch or iteration number.
140
+ """
141
+ if not self.reduce and get_rank() != 0:
142
+ return
143
+ global LOG_WRITER
144
+ lw = LOG_WRITER
145
+ if lw is None:
146
+ raise Exception("Log writer not set.")
147
+ lw.add_image("Visualizations", img_grid, step)
neuralangelo-main/imaginaire/utils/cudnn.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import torch.backends.cudnn as cudnn
14
+
15
+ from imaginaire.utils.distributed import master_only_print as print
16
+
17
+
18
+ def init_cudnn(deterministic, benchmark):
19
+ r"""Initialize the cudnn module. The two things to consider is whether to
20
+ use cudnn benchmark and whether to use cudnn deterministic. If cudnn
21
+ benchmark is set, then the cudnn deterministic is automatically false.
22
+
23
+ Args:
24
+ deterministic (bool): Whether to use cudnn deterministic.
25
+ benchmark (bool): Whether to use cudnn benchmark.
26
+ """
27
+ cudnn.deterministic = deterministic
28
+ cudnn.benchmark = benchmark
29
+ print('cudnn benchmark: {}'.format(benchmark))
30
+ print('cudnn deterministic: {}'.format(deterministic))
neuralangelo-main/imaginaire/utils/distributed.py ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import functools
14
+ import ctypes
15
+
16
+ import torch
17
+ import torch.distributed as dist
18
+ from contextlib import contextmanager
19
+
20
+
21
+ def init_dist(local_rank, backend='nccl', **kwargs):
22
+ r"""Initialize distributed training"""
23
+ if dist.is_available():
24
+ if dist.is_initialized():
25
+ return torch.cuda.current_device()
26
+ torch.cuda.set_device(local_rank)
27
+ dist.init_process_group(backend=backend, init_method='env://', **kwargs)
28
+
29
+ # Increase the L2 fetch granularity for faster speed.
30
+ _libcudart = ctypes.CDLL('libcudart.so')
31
+ # Set device limit on the current device
32
+ # cudaLimitMaxL2FetchGranularity = 0x05
33
+ pValue = ctypes.cast((ctypes.c_int * 1)(), ctypes.POINTER(ctypes.c_int))
34
+ _libcudart.cudaDeviceSetLimit(ctypes.c_int(0x05), ctypes.c_int(128))
35
+ _libcudart.cudaDeviceGetLimit(pValue, ctypes.c_int(0x05))
36
+ # assert pValue.contents.value == 128
37
+
38
+
39
+ def get_rank():
40
+ r"""Get rank of the thread."""
41
+ rank = 0
42
+ if dist.is_available():
43
+ if dist.is_initialized():
44
+ rank = dist.get_rank()
45
+ return rank
46
+
47
+
48
+ def get_world_size():
49
+ r"""Get world size. How many GPUs are available in this job."""
50
+ world_size = 1
51
+ if dist.is_available():
52
+ if dist.is_initialized():
53
+ world_size = dist.get_world_size()
54
+ return world_size
55
+
56
+
57
+ def broadcast_object_list(message, src=0):
58
+ r"""Broadcast object list from the master to the others"""
59
+ # Send logdir from master to all workers.
60
+ if dist.is_available():
61
+ if dist.is_initialized():
62
+ torch.distributed.broadcast_object_list(message, src=src)
63
+ return message
64
+
65
+
66
+ def master_only(func):
67
+ r"""Apply this function only to the master GPU."""
68
+ @functools.wraps(func)
69
+ def wrapper(*args, **kwargs):
70
+ r"""Simple function wrapper for the master function"""
71
+ if get_rank() == 0:
72
+ return func(*args, **kwargs)
73
+ else:
74
+ return None
75
+ return wrapper
76
+
77
+
78
+ def is_master():
79
+ r"""check if current process is the master"""
80
+ return get_rank() == 0
81
+
82
+
83
+ def is_dist():
84
+ return dist.is_initialized()
85
+
86
+
87
+ def barrier():
88
+ if is_dist():
89
+ dist.barrier()
90
+
91
+
92
+ @contextmanager
93
+ def master_first():
94
+ if not is_master():
95
+ barrier()
96
+ yield
97
+ if dist.is_initialized() and is_master():
98
+ barrier()
99
+
100
+
101
+ def is_local_master():
102
+ return torch.cuda.current_device() == 0
103
+
104
+
105
+ @master_only
106
+ def master_only_print(*args):
107
+ r"""master-only print"""
108
+ print(*args)
109
+
110
+
111
+ def dist_reduce_tensor(tensor, rank=0, reduce='mean'):
112
+ r""" Reduce to rank 0 """
113
+ world_size = get_world_size()
114
+ if world_size < 2:
115
+ return tensor
116
+ with torch.no_grad():
117
+ dist.reduce(tensor, dst=rank)
118
+ if get_rank() == rank:
119
+ if reduce == 'mean':
120
+ tensor /= world_size
121
+ elif reduce == 'sum':
122
+ pass
123
+ else:
124
+ raise NotImplementedError
125
+ return tensor
126
+
127
+
128
+ def dist_all_reduce_tensor(tensor, reduce='mean'):
129
+ r""" Reduce to all ranks """
130
+ world_size = get_world_size()
131
+ if world_size < 2:
132
+ return tensor
133
+ with torch.no_grad():
134
+ dist.all_reduce(tensor)
135
+ if reduce == 'mean':
136
+ tensor /= world_size
137
+ elif reduce == 'sum':
138
+ pass
139
+ else:
140
+ raise NotImplementedError
141
+ return tensor
142
+
143
+
144
+ def dist_all_gather_tensor(tensor):
145
+ r""" gather to all ranks """
146
+ world_size = get_world_size()
147
+ if world_size < 2:
148
+ return [tensor]
149
+ tensor_list = [
150
+ torch.ones_like(tensor) for _ in range(dist.get_world_size())]
151
+ with torch.no_grad():
152
+ dist.all_gather(tensor_list, tensor)
153
+ return tensor_list
neuralangelo-main/imaginaire/utils/gpu_affinity.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import math
14
+ import os
15
+ # pynvml is a python bindings to the NVIDIA Management Library
16
+ # https://developer.nvidia.com/nvidia-management-library-nvml
17
+ # An API for monitoring and managing various states of the NVIDIA GPU devices.
18
+ # It provides direct access to the queries and commands exposed via nvidia-smi.
19
+
20
+ import pynvml
21
+
22
+ pynvml.nvmlInit()
23
+
24
+
25
+ def system_get_driver_version():
26
+ r"""Get Driver Version"""
27
+ return pynvml.nvmlSystemGetDriverVersion()
28
+
29
+
30
+ def device_get_count():
31
+ r"""Get number of devices"""
32
+ return pynvml.nvmlDeviceGetCount()
33
+
34
+
35
+ class Device(object):
36
+ r"""Device used for nvml."""
37
+ _nvml_affinity_elements = math.ceil(os.cpu_count() / 64)
38
+
39
+ def __init__(self, device_idx):
40
+ super().__init__()
41
+ self.handle = pynvml.nvmlDeviceGetHandleByIndex(device_idx)
42
+
43
+ def get_name(self):
44
+ r"""Get obect name"""
45
+ return pynvml.nvmlDeviceGetName(self.handle)
46
+
47
+ def get_cpu_affinity(self):
48
+ r"""Get CPU affinity"""
49
+ affinity_string = ''
50
+ for j in pynvml.nvmlDeviceGetCpuAffinity(self.handle, Device._nvml_affinity_elements):
51
+ # assume nvml returns list of 64 bit ints
52
+ affinity_string = '{:064b}'.format(j) + affinity_string
53
+ affinity_list = [int(x) for x in affinity_string]
54
+ affinity_list.reverse() # so core 0 is in 0th element of list
55
+
56
+ return [i for i, e in enumerate(affinity_list) if e != 0]
57
+
58
+
59
+ def set_affinity(gpu_id=None):
60
+ r"""Set GPU affinity
61
+
62
+ Args:
63
+ gpu_id (int): Which gpu device.
64
+ """
65
+ if gpu_id is None:
66
+ gpu_id = int(os.getenv('LOCAL_RANK', 0))
67
+
68
+ dev = Device(gpu_id)
69
+ # os.sched_setaffinity() method in Python is used to set the CPU affinity mask of a process indicated
70
+ # by the specified process id.
71
+ # A process’s CPU affinity mask determines the set of CPUs on which it is eligible to run.
72
+ # Syntax: os.sched_setaffinity(pid, mask)
73
+ # pid=0 means the current process
74
+ os.sched_setaffinity(0, dev.get_cpu_affinity())
75
+
76
+ # list of ints
77
+ # representing the logical cores this process is now affinitied with
78
+ return os.sched_getaffinity(0)
neuralangelo-main/imaginaire/utils/misc.py ADDED
@@ -0,0 +1,376 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import collections
14
+ import functools
15
+ import os
16
+ import signal
17
+ import time
18
+ from collections import OrderedDict
19
+
20
+ import torch
21
+ import torch.nn.functional as F
22
+ import wandb
23
+
24
+ from imaginaire.utils.distributed import is_master, master_only
25
+
26
+ string_classes = (str, bytes)
27
+
28
+ from imaginaire.utils.termcolor import alert, PP # noqa
29
+
30
+
31
+ def santize_args(name, locals_fn):
32
+ args = {k: v for k, v in locals_fn.items()}
33
+ if 'kwargs' in args and args['kwargs']:
34
+ unused = PP(args['kwargs'])
35
+ alert(f'{name}: Unused kwargs\n{unused}')
36
+
37
+ keys_to_remove = ['self', 'kwargs']
38
+ for k in keys_to_remove:
39
+ args.pop(k, None)
40
+ alert(f'{name}: Used args\n{PP(args)}', 'green')
41
+ return args
42
+
43
+
44
+ def split_labels(labels, label_lengths):
45
+ r"""Split concatenated labels into their parts.
46
+
47
+ Args:
48
+ labels (torch.Tensor): Labels obtained through concatenation.
49
+ label_lengths (OrderedDict): Containing order of labels & their lengths.
50
+
51
+ Returns:
52
+
53
+ """
54
+ assert isinstance(label_lengths, OrderedDict)
55
+ start = 0
56
+ outputs = {}
57
+ for data_type, length in label_lengths.items():
58
+ end = start + length
59
+ if labels.dim() == 5:
60
+ outputs[data_type] = labels[:, :, start:end]
61
+ elif labels.dim() == 4:
62
+ outputs[data_type] = labels[:, start:end]
63
+ elif labels.dim() == 3:
64
+ outputs[data_type] = labels[start:end]
65
+ start = end
66
+ return outputs
67
+
68
+
69
+ def requires_grad(model, require=True):
70
+ r""" Set a model to require gradient or not.
71
+
72
+ Args:
73
+ model (nn.Module): Neural network model.
74
+ require (bool): Whether the network requires gradient or not.
75
+
76
+ Returns:
77
+
78
+ """
79
+ for p in model.parameters():
80
+ p.requires_grad = require
81
+
82
+
83
+ def to_device(data, device):
84
+ r"""Move all tensors inside data to device.
85
+
86
+ Args:
87
+ data (dict, list, or tensor): Input data.
88
+ device (str): 'cpu' or 'cuda'.
89
+ """
90
+ if isinstance(device, str):
91
+ device = torch.device(device)
92
+ assert isinstance(device, torch.device)
93
+
94
+ if isinstance(data, torch.Tensor):
95
+ data = data.to(device, non_blocking=True)
96
+ return data
97
+ elif isinstance(data, collections.abc.Mapping):
98
+ return type(data)({key: to_device(data[key], device) for key in data})
99
+ elif isinstance(data, collections.abc.Sequence) and not isinstance(data, string_classes):
100
+ return type(data)([to_device(d, device) for d in data])
101
+ else:
102
+ return data
103
+
104
+
105
+ def to_cuda(data):
106
+ r"""Move all tensors inside data to gpu.
107
+
108
+ Args:
109
+ data (dict, list, or tensor): Input data.
110
+ """
111
+ return to_device(data, 'cuda')
112
+
113
+
114
+ def to_cpu(data):
115
+ r"""Move all tensors inside data to cpu.
116
+
117
+ Args:
118
+ data (dict, list, or tensor): Input data.
119
+ """
120
+ return to_device(data, 'cpu')
121
+
122
+
123
+ def to_half(data):
124
+ r"""Move all floats to half.
125
+
126
+ Args:
127
+ data (dict, list or tensor): Input data.
128
+ """
129
+ if isinstance(data, torch.Tensor) and torch.is_floating_point(data):
130
+ data = data.half()
131
+ return data
132
+ elif isinstance(data, collections.abc.Mapping):
133
+ return type(data)({key: to_half(data[key]) for key in data})
134
+ elif isinstance(data, collections.abc.Sequence) and not isinstance(data, string_classes):
135
+ return type(data)([to_half(d) for d in data])
136
+ else:
137
+ return data
138
+
139
+
140
+ def to_float(data):
141
+ r"""Move all halfs to float.
142
+
143
+ Args:
144
+ data (dict, list or tensor): Input data.
145
+ """
146
+ if isinstance(data, torch.Tensor) and torch.is_floating_point(data):
147
+ data = data.float()
148
+ return data
149
+ elif isinstance(data, collections.abc.Mapping):
150
+ return type(data)({key: to_float(data[key]) for key in data})
151
+ elif isinstance(data, collections.abc.Sequence) and not isinstance(data, string_classes):
152
+ return type(data)([to_float(d) for d in data])
153
+ else:
154
+ return data
155
+
156
+
157
+ def slice_tensor(data, start, end):
158
+ r"""Slice all tensors from start to end.
159
+ Args:
160
+ data (dict, list or tensor): Input data.
161
+ """
162
+ if isinstance(data, torch.Tensor):
163
+ data = data[start:end]
164
+ return data
165
+ elif isinstance(data, collections.abc.Mapping):
166
+ return type(data)({key: slice_tensor(data[key], start, end) for key in data})
167
+ elif isinstance(data, collections.abc.Sequence) and not isinstance(data, string_classes):
168
+ return type(data)([slice_tensor(d, start, end) for d in data])
169
+ else:
170
+ return data
171
+
172
+
173
+ def get_and_setattr(cfg, name, default):
174
+ r"""Get attribute with default choice. If attribute does not exist, set it
175
+ using the default value.
176
+
177
+ Args:
178
+ cfg (obj) : Config options.
179
+ name (str) : Attribute name.
180
+ default (obj) : Default attribute.
181
+
182
+ Returns:
183
+ (obj) : Desired attribute.
184
+ """
185
+ if not hasattr(cfg, name) or name not in cfg.__dict__:
186
+ setattr(cfg, name, default)
187
+ return getattr(cfg, name)
188
+
189
+
190
+ def get_nested_attr(cfg, attr_name, default):
191
+ r"""Iteratively try to get the attribute from cfg. If not found, return
192
+ default.
193
+
194
+ Args:
195
+ cfg (obj): Config file.
196
+ attr_name (str): Attribute name (e.g. XXX.YYY.ZZZ).
197
+ default (obj): Default return value for the attribute.
198
+
199
+ Returns:
200
+ (obj): Attribute value.
201
+ """
202
+ names = attr_name.split('.')
203
+ atr = cfg
204
+ for name in names:
205
+ if not hasattr(atr, name):
206
+ return default
207
+ atr = getattr(atr, name)
208
+ return atr
209
+
210
+
211
+ def gradient_norm(model):
212
+ r"""Return the gradient norm of model.
213
+
214
+ Args:
215
+ model (PyTorch module): Your network.
216
+
217
+ """
218
+ total_norm = 0
219
+ for p in model.parameters():
220
+ if p.grad is not None:
221
+ param_norm = p.grad.norm(2)
222
+ total_norm += param_norm.item() ** 2
223
+ return total_norm ** (1. / 2)
224
+
225
+
226
+ def random_shift(x, offset=0.05, mode='bilinear', padding_mode='reflection'):
227
+ r"""Randomly shift the input tensor.
228
+
229
+ Args:
230
+ x (4D tensor): The input batch of images.
231
+ offset (int): The maximum offset ratio that is between [0, 1].
232
+ The maximum shift is offset * image_size for each direction.
233
+ mode (str): The resample mode for 'F.grid_sample'.
234
+ padding_mode (str): The padding mode for 'F.grid_sample'.
235
+
236
+ Returns:
237
+ x (4D tensor) : The randomly shifted image.
238
+ """
239
+ assert x.dim() == 4, "Input must be a 4D tensor."
240
+ batch_size = x.size(0)
241
+ theta = torch.eye(2, 3, device=x.device).unsqueeze(0).repeat(
242
+ batch_size, 1, 1)
243
+ theta[:, :, 2] = 2 * offset * torch.rand(batch_size, 2) - offset
244
+ grid = F.affine_grid(theta, x.size())
245
+ x = F.grid_sample(x, grid, mode=mode, padding_mode=padding_mode, align_corners=False)
246
+ return x
247
+
248
+
249
+ # def truncated_gaussian(threshold, size, seed=None, device=None):
250
+ # r"""Apply the truncated gaussian trick to trade diversity for quality
251
+ #
252
+ # Args:
253
+ # threshold (float): Truncation threshold.
254
+ # size (list of integer): Tensor size.
255
+ # seed (int): Random seed.
256
+ # device:
257
+ # """
258
+ # state = None if seed is None else np.random.RandomState(seed)
259
+ # values = truncnorm.rvs(-threshold, threshold,
260
+ # size=size, random_state=state)
261
+ # return torch.tensor(values, device=device).float()
262
+
263
+
264
+ def apply_imagenet_normalization(input):
265
+ r"""Normalize using ImageNet mean and std.
266
+
267
+ Args:
268
+ input (4D tensor NxCxHxW): The input images, assuming to be [-1, 1].
269
+
270
+ Returns:
271
+ Normalized inputs using the ImageNet normalization.
272
+ """
273
+ # normalize the input back to [0, 1]
274
+ normalized_input = (input + 1) / 2
275
+ # normalize the input using the ImageNet mean and std
276
+ mean = normalized_input.new_tensor([0.485, 0.456, 0.406]).view(1, 3, 1, 1)
277
+ std = normalized_input.new_tensor([0.229, 0.224, 0.225]).view(1, 3, 1, 1)
278
+ output = (normalized_input - mean) / std
279
+ return output
280
+
281
+
282
+ def alarm_handler(timeout_period, signum, frame):
283
+ # What to do when the process gets stuck. For now, we simply end the process.
284
+ error_message = f"Timeout error! More than {timeout_period} seconds have passed since the last iteration. Most " \
285
+ f"likely the process has been stuck due to node failure or PBSS error."
286
+ ngc_job_id = os.environ.get('NGC_JOB_ID', None)
287
+ if ngc_job_id is not None:
288
+ error_message += f" Failed NGC job ID: {ngc_job_id}."
289
+ # Let's reserve `wandb.alert` for this purpose.
290
+ wandb.alert(title="Timeout error!", text=error_message, level=wandb.AlertLevel.ERROR)
291
+ exit()
292
+
293
+
294
+ class Timer(object):
295
+
296
+ def __init__(self, cfg):
297
+ self.cfg = cfg
298
+ self.time_iteration = 0
299
+ self.time_epoch = 0
300
+ if is_master():
301
+ # noinspection PyTypeChecker
302
+ signal.signal(signal.SIGALRM, functools.partial(alarm_handler, self.cfg.timeout_period))
303
+
304
+ def reset(self):
305
+ self.accu_forw_iter_time = 0
306
+ self.accu_loss_iter_time = 0
307
+ self.accu_back_iter_time = 0
308
+ self.accu_step_iter_time = 0
309
+ self.accu_avg_iter_time = 0
310
+
311
+ def _time_before_forward(self):
312
+ r"""Record time before applying forward."""
313
+ if self.cfg.speed_benchmark:
314
+ torch.cuda.synchronize()
315
+ self.forw_time = time.time()
316
+
317
+ def _time_before_loss(self):
318
+ r"""Record time before computing loss."""
319
+ if self.cfg.speed_benchmark:
320
+ torch.cuda.synchronize()
321
+ self.loss_time = time.time()
322
+
323
+ def _time_before_backward(self):
324
+ r"""Record time before applying backward."""
325
+ if self.cfg.speed_benchmark:
326
+ torch.cuda.synchronize()
327
+ self.back_time = time.time()
328
+
329
+ def _time_before_step(self):
330
+ r"""Record time before updating the weights"""
331
+ if self.cfg.speed_benchmark:
332
+ torch.cuda.synchronize()
333
+ self.step_time = time.time()
334
+
335
+ def _time_before_model_avg(self):
336
+ r"""Record time before applying model average."""
337
+ if self.cfg.speed_benchmark:
338
+ torch.cuda.synchronize()
339
+ self.avg_time = time.time()
340
+
341
+ def _time_before_leave_gen(self):
342
+ r"""Record forward, backward, loss, and model average time for the network update."""
343
+ if self.cfg.speed_benchmark:
344
+ torch.cuda.synchronize()
345
+ end_time = time.time()
346
+ self.accu_forw_iter_time += self.loss_time - self.forw_time
347
+ self.accu_loss_iter_time += self.back_time - self.loss_time
348
+ self.accu_back_iter_time += self.step_time - self.back_time
349
+ self.accu_step_iter_time += self.avg_time - self.step_time
350
+ self.accu_avg_iter_time += end_time - self.avg_time
351
+
352
+ def _print_speed_benchmark(self, avg_time):
353
+ """Prints the profiling results and resets the timers."""
354
+ print('{:6f}'.format(avg_time))
355
+ print('\tModel FWD time {:6f}'.format(self.accu_forw_iter_time / self.cfg.logging_iter))
356
+ print('\tModel LOS time {:6f}'.format(self.accu_loss_iter_time / self.cfg.logging_iter))
357
+ print('\tModel BCK time {:6f}'.format(self.accu_back_iter_time / self.cfg.logging_iter))
358
+ print('\tModel STP time {:6f}'.format(self.accu_step_iter_time / self.cfg.logging_iter))
359
+ print('\tModel AVG time {:6f}'.format(self.accu_avg_iter_time / self.cfg.logging_iter))
360
+ self.accu_forw_iter_time = 0
361
+ self.accu_loss_iter_time = 0
362
+ self.accu_back_iter_time = 0
363
+ self.accu_step_iter_time = 0
364
+ self.accu_avg_iter_time = 0
365
+
366
+ def checkpoint_tic(self):
367
+ # reset timer
368
+ self.checkpoint_start_time = time.time()
369
+
370
+ def checkpoint_toc(self):
371
+ # return time by minutes
372
+ return (time.time() - self.checkpoint_start_time) / 60
373
+
374
+ @master_only
375
+ def reset_timeout_counter(self):
376
+ signal.alarm(self.cfg.timeout_period)
neuralangelo-main/imaginaire/utils/set_random_seed.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import random
14
+ import numpy as np
15
+ import torch
16
+
17
+ from imaginaire.utils.distributed import get_rank
18
+ from imaginaire.utils.distributed import master_only_print as print
19
+
20
+
21
+ def set_random_seed(seed, by_rank=False):
22
+ r"""Set random seeds for everything, including random, numpy, torch.manual_seed, torch.cuda_manual_seed.
23
+ torch.cuda.manual_seed_all is not necessary (included in torch.manual_seed)
24
+
25
+ Args:
26
+ seed (int): Random seed.
27
+ by_rank (bool): if true, each gpu will use a different random seed.
28
+ """
29
+ if by_rank:
30
+ seed += get_rank()
31
+ print(f"Using random seed {seed}")
32
+ random.seed(seed)
33
+ np.random.seed(seed)
34
+ torch.manual_seed(seed) # sets seed on the current CPU & all GPUs
35
+ torch.cuda.manual_seed(seed) # sets seed on current GPU
36
+ # torch.cuda.manual_seed_all(seed) # included in torch.manual_seed
neuralangelo-main/imaginaire/utils/termcolor.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import pprint
14
+
15
+ import termcolor
16
+
17
+
18
+ def red(x): return termcolor.colored(str(x), color="red")
19
+ def green(x): return termcolor.colored(str(x), color="green")
20
+ def blue(x): return termcolor.colored(str(x), color="blue")
21
+ def cyan(x): return termcolor.colored(str(x), color="cyan")
22
+ def yellow(x): return termcolor.colored(str(x), color="yellow")
23
+ def magenta(x): return termcolor.colored(str(x), color="magenta")
24
+ def grey(x): return termcolor.colored(str(x), color="grey")
25
+
26
+
27
+ COLORS = {
28
+ 'red': red, 'green': green, 'blue': blue, 'cyan': cyan, 'yellow': yellow, 'magenta': magenta, 'grey': grey
29
+ }
30
+
31
+
32
+ def PP(x):
33
+ string = pprint.pformat(x, indent=2)
34
+ if isinstance(x, dict):
35
+ string = '{\n ' + string[1:-1] + '\n}'
36
+ return string
37
+
38
+
39
+ def alert(x, color='red'):
40
+ color = COLORS[color]
41
+ print(color('-' * 32))
42
+ print(color(f'* {x}'))
43
+ print(color('-' * 32))
neuralangelo-main/imaginaire/utils/visualization.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import wandb
14
+ import torch
15
+ import torchvision
16
+
17
+ from matplotlib import pyplot as plt
18
+ from torchvision.transforms import functional as torchvision_F
19
+
20
+
21
+ def wandb_image(images, from_range=(0, 1)):
22
+ images = preprocess_image(images, from_range=from_range)
23
+ image_grid = torchvision.utils.make_grid(images, nrow=1, pad_value=1)
24
+ image_grid = torchvision_F.to_pil_image(image_grid)
25
+ wandb_image = wandb.Image(image_grid)
26
+ return wandb_image
27
+
28
+
29
+ def preprocess_image(images, from_range=(0, 1), cmap="gray"):
30
+ min, max = from_range
31
+ images = (images - min) / (max - min)
32
+ images = images.detach().cpu().float().clamp_(min=0, max=1)
33
+ if images.shape[1] == 1:
34
+ images = get_heatmap(images[:, 0], cmap=cmap)
35
+ return images
36
+
37
+
38
+ def get_heatmap(gray, cmap): # [N,H,W]
39
+ color = plt.get_cmap(cmap)(gray.numpy())
40
+ color = torch.from_numpy(color[..., :3]).permute(0, 3, 1, 2).float() # [N,3,H,W]
41
+ return color
neuralangelo-main/neuralangelo.yaml ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # conda env create --file neuralangelo.yaml
2
+ name: neuralangelo
3
+ channels:
4
+ - conda-forge
5
+ - pytorch
6
+ dependencies:
7
+ # general
8
+ - gpustat
9
+ - gdown
10
+ - cudatoolkit-dev
11
+ - cmake
12
+ # python general
13
+ - python=3.8
14
+ - pip
15
+ - numpy
16
+ - scipy
17
+ - ipython
18
+ - jupyterlab
19
+ - cython
20
+ - ninja
21
+ - diskcache
22
+ # pytorch
23
+ - pytorch
24
+ - torchvision
25
+ - pip:
26
+ - -r requirements.txt
neuralangelo-main/projects/nerf/configs/ingp_blender.yaml ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -----------------------------------------------------------------------------
2
+ # Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
3
+ #
4
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
5
+ # and proprietary rights in and to this software, related documentation
6
+ # and any modifications thereto. Any use, reproduction, disclosure or
7
+ # distribution of this software and related documentation without an express
8
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
9
+ # -----------------------------------------------------------------------------
10
+
11
+ _parent_: projects/nerf/configs/nerf_blender.yaml
12
+
13
+ max_iter: 500000
14
+
15
+ wandb_scalar_iter: 500
16
+ wandb_image_iter: 10000
17
+ validation_iter: 10000
18
+
19
+ model:
20
+ type: projects.nerf.models.ingp
21
+ mlp:
22
+ layers_feat: [null,64,64]
23
+ layers_rgb: [null,64,3]
24
+ voxel:
25
+ levels:
26
+ min: 4
27
+ max: 12
28
+ num: 16
29
+ dict_size: 19
30
+ dim: 4
31
+ range: [-5,5]
32
+ init_scale: 1e-4
33
+ sample_intvs: 256
34
+ fine_sampling: False
35
+
36
+ optim:
37
+ type: Adam
38
+ params:
39
+ lr: 0.01
40
+ sched:
41
+ gamma: 1
neuralangelo-main/projects/nerf/configs/nerf_blender.yaml ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -----------------------------------------------------------------------------
2
+ # Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
3
+ #
4
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
5
+ # and proprietary rights in and to this software, related documentation
6
+ # and any modifications thereto. Any use, reproduction, disclosure or
7
+ # distribution of this software and related documentation without an express
8
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
9
+ # -----------------------------------------------------------------------------
10
+
11
+ aws_credentials_file: scripts/s3/pbss_dir.secret
12
+ logging_iter: 9999999999999 # disable the printing logger
13
+
14
+ max_iter: 500000
15
+
16
+ wandb_scalar_iter: 100
17
+ wandb_image_iter: 1000
18
+ validation_iter: 2000
19
+
20
+ speed_benchmark: False
21
+
22
+ checkpoint:
23
+ save_to_s3: False
24
+ load_from_s3: False
25
+ s3_credentials: scripts/s3/pbss_dir.secret
26
+ s3_bucket: nerf
27
+ save_iter: 10000
28
+
29
+ trainer:
30
+ type: projects.nerf.trainers.nerf
31
+ ema_config:
32
+ enabled: False
33
+ load_ema_checkpoint: False
34
+ loss_weight:
35
+ render: 1.0
36
+ render_fine: 1.0
37
+ init:
38
+ type: xavier
39
+ amp_config:
40
+ enabled: True
41
+
42
+ model:
43
+ type: projects.nerf.models.nerf
44
+ mlp:
45
+ layers_feat: [null,256,256,256,256,256,256,256,256]
46
+ layers_rgb: [null,128,3]
47
+ skip: [4]
48
+ posenc:
49
+ L_3D: 10
50
+ L_view: 4
51
+ density_activ: softplus
52
+ view_dep: True
53
+ dist:
54
+ param: metric
55
+ range: [2,6]
56
+ sample_intvs: 64
57
+ sample_stratified: True
58
+ fine_sampling: True
59
+ sample_intvs_fine: 128
60
+ rand_rays: 1024
61
+ density_noise_reg:
62
+ opaque_background: False
63
+ camera_ndc: False
64
+
65
+ optim:
66
+ type: Adam
67
+ params:
68
+ lr: 0.0005
69
+ betas: [0.9, 0.999]
70
+ sched:
71
+ iteration_mode: False
72
+ type: step
73
+ step_size: 20
74
+ gamma: 0.97724
75
+
76
+ data:
77
+ type: projects.nerf.datasets.nerf_blender
78
+ use_multi_epoch_loader: True
79
+ num_workers: 4
80
+ root: datasets/nerf-synthetic/lego
81
+ image_size: [400,400]
82
+ preload: True
83
+ bgcolor: 1
84
+ train:
85
+ batch_size: 2
86
+ subset:
87
+ val:
88
+ batch_size: 2
89
+ subset: 4
90
+ max_viz_samples: 16
91
+
92
+ test_data:
93
+ type: projects.nerf.datasets.nerf_blender
94
+ num_workers: 4
95
+ root: datasets/nerf-synthetic/lego
96
+ image_size: [400,400]
97
+ preload: True
98
+ bgcolor: 1
99
+ test:
100
+ batch_size: 2
101
+ subset:
neuralangelo-main/projects/nerf/configs/nerf_llff.yaml ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -----------------------------------------------------------------------------
2
+ # Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
3
+ #
4
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
5
+ # and proprietary rights in and to this software, related documentation
6
+ # and any modifications thereto. Any use, reproduction, disclosure or
7
+ # distribution of this software and related documentation without an express
8
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
9
+ # -----------------------------------------------------------------------------
10
+
11
+ _parent_: projects/nerf/configs/nerf_blender.yaml
12
+
13
+ max_iter: 200000
14
+
15
+ wandb_scalar_iter: 50
16
+ wandb_image_iter: 500
17
+ validation_iter: 1000
18
+
19
+ model:
20
+ type: projects.nerf.models.nerf
21
+ dist:
22
+ param: ndc
23
+ range: [0,1]
24
+ sample_intvs: 64
25
+ fine_sampling: True
26
+ sample_intvs_fine: 128
27
+ rand_rays: 1024
28
+ camera_ndc: True
29
+
30
+ optim:
31
+ type: Adam
32
+ params:
33
+ lr: 0.0005
34
+ betas: [0.9, 0.999]
35
+ sched:
36
+ iteration_mode: False
37
+ type: step
38
+ step_size: 100
39
+ gamma: 0.97724
40
+
41
+ data:
42
+ type: projects.nerf.datasets.nerf_llff
43
+ use_multi_epoch_loader: True
44
+ num_workers: 4
45
+ root: datasets/nerf-llff/fern
46
+ image_size: [480,640]
47
+ preload: True
48
+ val_ratio: 0.1
49
+ train:
50
+ batch_size: 2
51
+ subset:
52
+ val:
53
+ batch_size: 2
54
+ subset: 4
55
+
56
+ test_data:
57
+ type: projects.nerf.datasets.nerf_llff
58
+ num_workers: 4
59
+ root: datasets/nerf-llff/fern
60
+ image_size: [480,640]
61
+ preload: True
62
+ test:
63
+ batch_size: 2
64
+ subset:
neuralangelo-main/projects/nerf/datasets/base.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import torch
14
+ import tqdm
15
+ import threading
16
+ import queue
17
+
18
+
19
+ class Dataset(torch.utils.data.Dataset):
20
+
21
+ def __init__(self, cfg, is_inference=False, is_test=False):
22
+ super().__init__()
23
+ self.split = "test" if is_test else "val" if is_inference else "train"
24
+
25
+ def _preload_worker(self, data_list, load_func, q, lock, idx_tqdm):
26
+ # Keep preloading data in parallel.
27
+ while True:
28
+ idx = q.get()
29
+ data_list[idx] = load_func(idx)
30
+ with lock:
31
+ idx_tqdm.update()
32
+ q.task_done()
33
+
34
+ def preload_threading(self, load_func, num_workers, data_str="images"):
35
+ # Use threading to preload data in parallel.
36
+ data_list = [None] * len(self)
37
+ q = queue.Queue(maxsize=len(self))
38
+ idx_tqdm = tqdm.tqdm(range(len(self)), desc=f"preloading {data_str} ({self.split})", leave=False)
39
+ for i in range(len(self)):
40
+ q.put(i)
41
+ lock = threading.Lock()
42
+ for ti in range(num_workers):
43
+ t = threading.Thread(target=self._preload_worker,
44
+ args=(data_list, load_func, q, lock, idx_tqdm), daemon=True)
45
+ t.start()
46
+ q.join()
47
+ idx_tqdm.close()
48
+ assert all(map(lambda x: x is not None, data_list))
49
+ return data_list
50
+
51
+ def __getitem__(self, idx):
52
+ raise NotImplementedError
53
+
54
+ def __len__(self):
55
+ return len(self.list)
neuralangelo-main/projects/nerf/datasets/nerf_blender.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import json
14
+ import numpy as np
15
+ import torch
16
+ import torchvision.transforms.functional as torchvision_F
17
+ from PIL import Image, ImageFile
18
+
19
+ from projects.nerf.datasets import base
20
+ from projects.nerf.utils import camera
21
+
22
+ ImageFile.LOAD_TRUNCATED_IMAGES = True
23
+
24
+
25
+ class Dataset(base.Dataset):
26
+
27
+ def __init__(self, cfg, is_inference=False, is_test=False):
28
+ super().__init__(cfg, is_inference=is_inference, is_test=is_test)
29
+ cfg_data = cfg.test_data if self.split == "test" else cfg.data
30
+ data_info = cfg_data[self.split]
31
+ self.root = cfg_data.root
32
+ self.preload = cfg_data.preload
33
+ self.bgcolor = cfg_data.bgcolor
34
+ self.raw_H, self.raw_W = 800, 800
35
+ self.H, self.W = cfg_data.image_size
36
+ meta_fname = f"{cfg_data.root}/transforms_{self.split}.json"
37
+ with open(meta_fname) as file:
38
+ self.meta = json.load(file)
39
+ self.focal = 0.5 * self.raw_W / np.tan(0.5 * self.meta["camera_angle_x"])
40
+ self.list = self.meta["frames"]
41
+ # Consider only a subset of data.
42
+ if data_info.subset:
43
+ self.list = self.list[:data_info.subset]
44
+ # Preload dataset if possible.
45
+ if cfg_data.preload:
46
+ self.images = self.preload_threading(self.get_image, cfg_data.num_workers)
47
+ self.cameras = self.preload_threading(self.get_camera, cfg_data.num_workers, data_str="cameras")
48
+
49
+ def __getitem__(self, idx):
50
+ """Process raw data and return processed data in a dictionary.
51
+
52
+ Args:
53
+ idx: The index of the sample of the dataset.
54
+ Returns: A dictionary containing the data.
55
+ idx (scalar): The index of the sample of the dataset.
56
+ image (3xHxW tensor): Image with pixel values in [0,1] for supervision.
57
+ intr (3x3 tensor): The camera intrinsics of `image`.
58
+ pose (3x4 tensor): The camera extrinsics [R,t] of `image`.
59
+ """
60
+ # Keep track of sample index for convenience.
61
+ sample = dict(idx=idx)
62
+ # Get the images.
63
+ image = self.images[idx] if self.preload else self.get_image(idx)
64
+ image = self.preprocess_image(image)
65
+ # Get the cameras (intrinsics and pose).
66
+ intr, pose = self.cameras[idx] if self.preload else self.get_camera(idx)
67
+ intr, pose = self.preprocess_camera(intr, pose)
68
+ # Update the data sample.
69
+ sample.update(
70
+ image=image,
71
+ intr=intr,
72
+ pose=pose,
73
+ )
74
+ return sample
75
+
76
+ def get_image(self, idx):
77
+ fpath = self.list[idx]["file_path"][2:]
78
+ image_fname = f"{self.root}/{fpath}.png"
79
+ image = Image.open(image_fname)
80
+ image.load()
81
+ return image
82
+
83
+ def preprocess_image(self, image):
84
+ # Resize the image.
85
+ image = image.resize((self.W, self.H))
86
+ image = torchvision_F.to_tensor(image)
87
+ # Background masking.
88
+ rgb, mask = image[:3], image[3:]
89
+ if self.bgcolor is not None:
90
+ rgb = rgb * mask + self.bgcolor * (1 - mask)
91
+ return rgb
92
+
93
+ def get_camera(self, idx):
94
+ # Camera intrinsics.
95
+ intr = torch.tensor([[self.focal, 0, self.raw_W / 2],
96
+ [0, self.focal, self.raw_H / 2],
97
+ [0, 0, 1]]).float()
98
+ # Camera pose.
99
+ pose_raw = torch.tensor(self.list[idx]["transform_matrix"], dtype=torch.float32)
100
+ pose = self.parse_raw_camera(pose_raw)
101
+ return intr, pose
102
+
103
+ def preprocess_camera(self, intr, pose):
104
+ # Adjust the intrinsics according to the resized image.
105
+ intr = intr.clone()
106
+ intr[0] *= self.W / self.raw_W
107
+ intr[1] *= self.H / self.raw_H
108
+ return intr, pose
109
+
110
+ def parse_raw_camera(self, pose_raw):
111
+ pose_flip = camera.pose(R=torch.diag(torch.tensor([1, -1, -1])))
112
+ pose = camera.pose.compose([pose_flip, pose_raw[:3]])
113
+ pose = camera.pose.invert(pose)
114
+ return pose
neuralangelo-main/projects/nerf/datasets/nerf_llff.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import numpy as np
14
+ import torch
15
+ import torch.nn.functional as torch_F
16
+ import torchvision.transforms.functional as torchvision_F
17
+ from PIL import Image, ImageFile
18
+
19
+ from projects.nerf.datasets import base
20
+ from projects.nerf.utils import camera
21
+
22
+ ImageFile.LOAD_TRUNCATED_IMAGES = True
23
+
24
+
25
+ class Dataset(base.Dataset):
26
+
27
+ def __init__(self, cfg, is_inference=False, is_test=False):
28
+ super().__init__(cfg, is_inference=is_inference, is_test=is_test)
29
+ cfg_data = cfg.test_data if self.split == "test" else cfg.data
30
+ data_info = cfg_data[self.split]
31
+ self.root = cfg_data.root
32
+ self.preload = cfg_data.preload
33
+ self.raw_H, self.raw_W = 3024, 4032
34
+ self.H, self.W = cfg_data.image_size
35
+ list_fname = f"{cfg_data.root}/images.list"
36
+ image_fnames = open(list_fname).read().splitlines()
37
+ poses_raw, bounds = self.parse_cameras_and_bounds(cfg_data)
38
+ self.list = list(zip(image_fnames, poses_raw, bounds))
39
+ # Manually split train/val subsets.
40
+ num_val_split = int(len(self) * cfg_data.val_ratio)
41
+ self.list = self.list[:-num_val_split] if self.split == "train" else self.list[-num_val_split:]
42
+ # Consider only a subset of data.
43
+ if data_info.subset:
44
+ self.list = self.list[:data_info.subset]
45
+ # Preload dataset if possible.
46
+ if cfg_data.preload:
47
+ self.images = self.preload_threading(self.get_image, cfg_data.num_workers)
48
+ self.cameras = self.preload_threading(self.get_camera, cfg_data.num_workers, data_str="cameras")
49
+
50
+ def parse_cameras_and_bounds(self, cfg_data):
51
+ fname = f"{cfg_data.root}/poses_bounds.npy"
52
+ data = torch.tensor(np.load(fname), dtype=torch.float32)
53
+ # Parse cameras (intrinsics and poses).
54
+ cam_data = data[:, :-2].view([-1, 3, 5]) # [N,3,5]
55
+ poses_raw = cam_data[..., :4] # [N,3,4]
56
+ poses_raw[..., 0], poses_raw[..., 1] = poses_raw[..., 1], -poses_raw[..., 0]
57
+ raw_H, raw_W, self.focal = cam_data[0, :, -1]
58
+ assert self.raw_H == raw_H and self.raw_W == raw_W
59
+ # Parse depth bounds.
60
+ bounds = data[:, -2:] # [N,2]
61
+ scale = 1. / (bounds.min() * 0.75) # Not sure how this was determined?
62
+ poses_raw[..., 3] *= scale
63
+ bounds *= scale
64
+ # Roughly center camera poses.
65
+ poses_raw = self.center_camera_poses(poses_raw)
66
+ return poses_raw, bounds
67
+
68
+ def center_camera_poses(self, poses):
69
+ # Compute average pose.
70
+ center = poses[..., 3].mean(dim=0)
71
+ v1 = torch_F.normalize(poses[..., 1].mean(dim=0), dim=0)
72
+ v2 = torch_F.normalize(poses[..., 2].mean(dim=0), dim=0)
73
+ v0 = v1.cross(v2)
74
+ pose_avg = torch.stack([v0, v1, v2, center], dim=-1)[None] # [1,3,4]
75
+ # Apply inverse of averaged pose.
76
+ poses = camera.pose.compose([poses, camera.pose.invert(pose_avg)])
77
+ return poses
78
+
79
+ def __getitem__(self, idx):
80
+ """Process raw data and return processed data in a dictionary.
81
+
82
+ Args:
83
+ idx: The index of the sample of the dataset.
84
+ Returns: A dictionary containing the data.
85
+ idx (scalar): The index of the sample of the dataset.
86
+ image (3xHxW tensor): Image with pixel values in [0,1] for supervision.
87
+ intr (3x3 tensor): The camera intrinsics of `image`.
88
+ pose (3x4 tensor): The camera extrinsics [R,t] of `image`.
89
+ """
90
+ # Keep track of sample index for convenience.
91
+ sample = dict(idx=idx)
92
+ # Get the images.
93
+ image = self.images[idx] if self.preload else self.get_image(idx)
94
+ image = self.preprocess_image(image)
95
+ # Get the cameras (intrinsics and pose).
96
+ intr, pose = self.cameras[idx] if self.preload else self.get_camera(idx)
97
+ intr, pose = self.preprocess_camera(intr, pose)
98
+ # Update the data sample.
99
+ sample.update(
100
+ image=image,
101
+ intr=intr,
102
+ pose=pose,
103
+ )
104
+ return sample
105
+
106
+ def get_image(self, idx):
107
+ image_fname = f"{self.root}/images/{self.list[idx][0]}"
108
+ image = Image.open(image_fname)
109
+ image.load()
110
+ return image
111
+
112
+ def preprocess_image(self, image):
113
+ # Resize the image and convert to Pytorch.
114
+ image = image.resize((self.W, self.H))
115
+ image = torchvision_F.to_tensor(image)
116
+ return image
117
+
118
+ def get_camera(self, idx):
119
+ # Camera intrinsics.
120
+ intr = torch.tensor([[self.focal, 0, self.raw_W / 2],
121
+ [0, self.focal, self.raw_H / 2],
122
+ [0, 0, 1]]).float()
123
+ # Camera pose.
124
+ pose_raw = self.list[idx][1]
125
+ pose = self.parse_raw_camera(pose_raw)
126
+ return intr, pose
127
+
128
+ def preprocess_camera(self, intr, pose):
129
+ # Adjust the intrinsics according to the resized image.
130
+ intr = intr.clone()
131
+ intr[0] *= self.W / self.raw_W
132
+ intr[1] *= self.H / self.raw_H
133
+ return intr, pose
134
+
135
+ def parse_raw_camera(self, pose_raw):
136
+ pose_flip = camera.pose(R=torch.diag(torch.tensor([1, -1, -1])))
137
+ pose = camera.pose.compose([pose_flip, pose_raw[:3]])
138
+ pose = camera.pose.invert(pose)
139
+ pose = camera.pose.compose([pose_flip, pose])
140
+ return pose
neuralangelo-main/projects/nerf/models/ingp.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import numpy as np
14
+ import torch
15
+ import tinycudann as tcnn
16
+
17
+ from projects.nerf.models import nerf
18
+
19
+
20
+ class Model(nerf.Model):
21
+
22
+ def __init__(self, cfg_model, cfg_data):
23
+ super().__init__(cfg_model, cfg_data)
24
+ self.fine_sampling = False
25
+ self.density_reg = cfg_model.density_noise_reg
26
+ # Define models.
27
+ self.nerf = InstantNGP(cfg_model)
28
+
29
+
30
+ class InstantNGP(nerf.NeRF):
31
+
32
+ def __init__(self, cfg_model):
33
+ self.voxel = cfg_model.voxel
34
+ super().__init__(cfg_model)
35
+
36
+ def set_input_dims(self, cfg_model):
37
+ # Define the input encoding dimensions.
38
+ self.input_3D_dim = 3 + cfg_model.voxel.dim * cfg_model.voxel.levels.num
39
+ self.input_view_dim = 3 if cfg_model.view_dep else None
40
+
41
+ def build_model(self, cfg_model):
42
+ super().build_model(cfg_model)
43
+ # Build the tcnn hash grid.
44
+ l_min, l_max = self.voxel.levels.min, self.voxel.levels.max
45
+ r_min, r_max = 2 ** l_min, 2 ** l_max
46
+ num_levels = self.voxel.levels.num
47
+ growth_rate = np.exp((np.log(r_max) - np.log(r_min)) / (num_levels - 1))
48
+ config = dict(
49
+ otype="HashGrid",
50
+ n_levels=cfg_model.voxel.levels.num,
51
+ n_features_per_level=cfg_model.voxel.dim,
52
+ log2_hashmap_size=cfg_model.voxel.dict_size,
53
+ base_resolution=2 ** cfg_model.voxel.levels.min,
54
+ per_level_scale=growth_rate,
55
+ )
56
+ self.tiny_cuda_encoding = tcnn.Encoding(3, config)
57
+ # Compute resolutions of all levels.
58
+ self.resolutions = []
59
+ for lv in range(0, num_levels):
60
+ size = np.floor(r_min * growth_rate ** lv).astype(int) + 1
61
+ self.resolutions.append(size)
62
+
63
+ def forward(self, points_3D, ray_unit, density_reg=None):
64
+ return super().forward(points_3D, ray_unit, density_reg)
65
+
66
+ def _encode_3D(self, points_3D):
67
+ # Tri-linear interpolate the corresponding embeddings from the dictionary.
68
+ vol_min, vol_max = self.voxel.range
69
+ points_3D_normalized = (points_3D - vol_min) / (vol_max - vol_min) # Normalize to [0,1].
70
+ tcnn_input = points_3D_normalized.view(-1, 3)
71
+ tcnn_output = self.tiny_cuda_encoding(tcnn_input)
72
+ points_enc = tcnn_output.view(*points_3D_normalized.shape[:-1], tcnn_output.shape[-1])
73
+ points_enc = torch.cat([points_enc, points_3D], dim=-1) # [B,R,N,LD+3]
74
+ return points_enc
75
+
76
+ def _encode_view(self, ray_unit):
77
+ return ray_unit
neuralangelo-main/projects/nerf/models/nerf.py ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import torch
14
+ import torch.nn.functional as torch_F
15
+ import copy
16
+ from functools import partial
17
+ from collections import defaultdict
18
+
19
+ from imaginaire.models.base import Model as BaseModel
20
+ from projects.nerf.utils import camera, render, nerf_util
21
+
22
+
23
+ class Model(BaseModel):
24
+
25
+ def __init__(self, cfg_model, cfg_data):
26
+ super().__init__(cfg_model, cfg_data)
27
+ self.num_rays = cfg_model.rand_rays
28
+ self.image_size = cfg_data.image_size
29
+ self.fine_sampling = cfg_model.fine_sampling
30
+ self.stratified = cfg_model.sample_stratified
31
+ self.density_reg = cfg_model.density_noise_reg
32
+ self.opaque_background = cfg_model.opaque_background
33
+ self.bgcolor = getattr(cfg_data, "bgcolor", 1.)
34
+ # Define models.
35
+ self.nerf = NeRF(cfg_model)
36
+ if self.fine_sampling:
37
+ self.nerf_fine = NeRF(cfg_model)
38
+ # Define functions.
39
+ self.ray_generator = partial(nerf_util.ray_generator,
40
+ image_size=cfg_data.image_size,
41
+ camera_ndc=cfg_model.camera_ndc,
42
+ num_rays=cfg_model.rand_rays)
43
+ self.sample_dists = partial(nerf_util.sample_dists,
44
+ dist_range=cfg_model.dist.range,
45
+ intvs=cfg_model.sample_intvs)
46
+ self.sample_dists_from_pdf = partial(nerf_util.sample_dists_from_pdf,
47
+ intvs_fine=cfg_model.sample_intvs_fine)
48
+ self.reparametrize_dist = partial(nerf_util.reparametrize_dist,
49
+ param_type=cfg_model.dist.param)
50
+ self.get_inverse_depth = partial(nerf_util.get_inverse_depth,
51
+ camera_ndc=cfg_model.camera_ndc)
52
+ self.to_full_image = lambda vec, image_size=cfg_data.image_size: \
53
+ vec.moveaxis(1, -1).unflatten(dim=-1, sizes=image_size)
54
+
55
+ def forward(self, data):
56
+ # Randomly sample and render the pixels.
57
+ ray_idx = self._sample_random_rays(data)
58
+ output = self.render_pixels(data["pose"], data["intr"], full_image=False, ray_idx=ray_idx,
59
+ stratified=self.stratified, density_reg=self.density_reg)
60
+ output.update(ray_idx=ray_idx) # [B,R]
61
+ return output
62
+
63
+ def _sample_random_rays(self, data):
64
+ batch_size = len(data["pose"])
65
+ num_pixels = self.image_size[0] * self.image_size[1]
66
+ ray_idx = torch.rand(batch_size, num_pixels, device=data["pose"].device).argsort(dim=1)[:, :self.num_rays]
67
+ return ray_idx # [B,R]
68
+
69
+ @torch.no_grad()
70
+ def inference(self, data):
71
+ self.eval()
72
+ # Render the full images.
73
+ output = self.render_image(data["pose"], data["intr"], stratified=False) # [B,N,C]
74
+ # Get full rendered RGB and depth images.
75
+ inv_depth = self.get_inverse_depth(output["depth"], opacity=output["opacity"])
76
+ output.update(
77
+ rgb_map=self.to_full_image(output["rgb"]), # [B,3,H,W]
78
+ inv_depth_map=self.to_full_image(inv_depth), # [B,1,H,W]
79
+ )
80
+ if self.fine_sampling:
81
+ inv_depth_fine = self.get_inverse_depth(output["depth_fine"], opacity=output["opacity_fine"])
82
+ output.update(
83
+ rgb_map_fine=self.to_full_image(output["rgb_fine"]), # [B,3,H,W]
84
+ inv_depth_map_fine=self.to_full_image(inv_depth_fine), # [B,1,H,W]
85
+ )
86
+ return output
87
+
88
+ def render_image(self, pose, intr, stratified=False):
89
+ """ Render the rays given the camera intrinsics and poses.
90
+ Args:
91
+ pose (tensor [batch,3,4]): Camera poses ([R,t]).
92
+ intr (tensor [batch,3,3]): Camera intrinsics.
93
+ stratified (bool): Whether to stratify the depth sampling.
94
+ Returns:
95
+ output: A dictionary containing the outputs.
96
+ """
97
+ output = defaultdict(list)
98
+ for center, ray, _ in self.ray_generator(pose, intr, full_image=True):
99
+ ray_unit = torch_F.normalize(ray, dim=-1) # [B,R,3]
100
+ output_batch = self.render_rays(center, ray_unit, stratified=stratified)
101
+ if not self.training:
102
+ depth = output_batch["dist"] / ray.norm(dim=-1, keepdim=True)
103
+ output_batch.update(depth=depth)
104
+ if self.fine_sampling:
105
+ depth_fine = output_batch["dist_fine"] / ray.norm(dim=-1, keepdim=True)
106
+ output_batch.update(depth_fine=depth_fine)
107
+ for key, value in output_batch.items():
108
+ output[key].append(value.detach())
109
+ # Concat each item (list) in output into one tensor. Concatenate along the ray dimension (1)
110
+ for key, value in output.items():
111
+ output[key] = torch.cat(value, dim=1)
112
+ return output
113
+
114
+ def render_pixels(self, pose, intr, full_image=False, ray_idx=None, stratified=False, density_reg=None):
115
+ center, ray = camera.get_center_and_ray(pose, intr, self.image_size) # [B,HW,3]
116
+ center = nerf_util.slice_by_ray_idx(center, ray_idx) # [B,R,3]
117
+ ray = nerf_util.slice_by_ray_idx(ray, ray_idx) # [B,R,3]
118
+ ray_unit = torch_F.normalize(ray, dim=-1) # [B,R,3]
119
+ output = self.render_rays(center, ray_unit, stratified=stratified, density_reg=density_reg)
120
+ return output
121
+
122
+ def render_rays(self, center, ray_unit, sample_idx=None, stratified=False, density_reg=None):
123
+ with torch.no_grad():
124
+ dists = self.sample_dists(ray_unit.shape[:2], stratified=stratified) # [B,R,N,1]
125
+ dists = self.reparametrize_dist(dists) # [B,R,N,1]
126
+ points = camera.get_3D_points_from_dist(center, ray_unit, dists) # [B,R,N,3]
127
+ rays_unit = ray_unit[..., None, :].expand_as(points) # [B,R,N,3]
128
+ rgbs, densities = self.nerf.forward(points, rays_unit, density_reg=density_reg)
129
+ weights = render.volume_rendering_weights_dist(densities, dists) # [B,R,N,1]
130
+ opacity = render.composite(1., weights) # [B,R,1]
131
+ rgb = render.composite(rgbs, weights) # [B,R,3]
132
+ if self.opaque_background:
133
+ rgb = rgb + self.bgcolor * (1 - opacity)
134
+ dist = render.composite(dists, weights) # [B,R,1]
135
+ # Collect output.
136
+ output = dict(
137
+ rgb=rgb, # [B,R,3]
138
+ dist=dist, # [B,R,1]
139
+ opacity=opacity, # [B,R,1]
140
+ )
141
+ if self.fine_sampling:
142
+ with torch.no_grad():
143
+ # Resample depth according to coarse empirical distribution.
144
+ dists_mid = 0.5 * (dists[..., :-1, :] + dists[..., 1:, :]) # [B,R,N-1,1]
145
+ dists_fine = self.sample_dists_from_pdf(dists_mid, weights=weights[..., 1:-1, 0]) # [B,R,Nf,1]
146
+ dists = torch.cat([dists, dists_fine], dim=2) # [B,R,N+Nf,1]
147
+ dists = dists.sort(dim=2).values
148
+ points = camera.get_3D_points_from_dist(center, ray_unit, dists) # [B,R,N,3]
149
+ rays_unit = ray_unit[..., None, :].expand_as(points) # [B,R,N,3]
150
+ rgbs, densities = self.nerf_fine.forward(points, rays_unit, density_reg=density_reg)
151
+ weights = render.volume_rendering_weights_dist(densities, dists) # [B,R,N,1]
152
+ opacity = render.composite(1., weights) # [B,R,1]
153
+ rgb = render.composite(rgbs, weights) # [B,R,3]
154
+ if self.opaque_background:
155
+ rgb = rgb + self.bgcolor * (1 - opacity)
156
+ dist = render.composite(dists, weights) # [B,R,1]
157
+ # Collect output.
158
+ output.update(
159
+ rgb_fine=rgb, # [B,R,3]
160
+ dist_fine=dist, # [B,R,1]
161
+ opacity_fine=opacity, # [B,R,1]
162
+ )
163
+ return output
164
+
165
+
166
+ class NeRF(torch.nn.Module):
167
+
168
+ def __init__(self, cfg_model):
169
+ super().__init__()
170
+ self.view_dep = cfg_model.view_dep
171
+ self.posenc = cfg_model.posenc
172
+ # Define learnable parameters.
173
+ self.set_input_dims(cfg_model)
174
+ self.build_model(cfg_model)
175
+
176
+ def set_input_dims(self, cfg_model):
177
+ # Define the input encoding dimensions.
178
+ self.input_3D_dim = 3 + 6 * cfg_model.posenc.L_3D if cfg_model.posenc.L_3D else 3
179
+ if cfg_model.view_dep:
180
+ self.input_view_dim = 3 + 6 * cfg_model.posenc.L_view if cfg_model.posenc.L_view else 3
181
+ else:
182
+ self.input_view_dim = None
183
+
184
+ def build_model(self, cfg_model):
185
+ # Point-wise feature.
186
+ layers_feat = copy.copy(cfg_model.mlp.layers_feat)
187
+ layers_feat[0] = self.input_3D_dim
188
+ layers_feat[-1] += 1 # For predicting the volume density.
189
+ self.mlp_feat = nerf_util.MLPwithSkipConnection(layers_feat, skip_connection=cfg_model.mlp.skip)
190
+ # RGB prediction.
191
+ layers_rgb = copy.copy(cfg_model.mlp.layers_rgb)
192
+ layers_rgb[0] = cfg_model.mlp.layers_feat[-1] + (self.input_view_dim if cfg_model.view_dep else 0)
193
+ self.mlp_rgb = nerf_util.MLPwithSkipConnection(layers_rgb)
194
+ self.density_activ = dict(
195
+ relu=torch_F.relu,
196
+ relu_=torch_F.relu_,
197
+ abs=torch.abs,
198
+ abs_=torch.abs_,
199
+ sigmoid=torch.sigmoid,
200
+ sigmoid_=torch.sigmoid_,
201
+ exp=torch.exp,
202
+ exp_=torch.exp_,
203
+ softplus=torch_F.softplus,
204
+ identity=lambda x: x,
205
+ )[cfg_model.density_activ]
206
+
207
+ def forward(self, points_3D, ray_unit, density_reg=None):
208
+ """ Forward pass the NeRF (MLP).
209
+ Args:
210
+ points_3D (tensor [batch,...,3]): 3D points in world space.
211
+ ray_unit (tensor [batch,...,3]): Unit ray direction in world space.
212
+ density_reg (float or None): Density regularization.
213
+ Returns:
214
+ rgb (tensor [batch,...,3]): Predicted RGB values in [0,1].
215
+ density (tensor [batch,...,3]): Predicted volume density values.
216
+ """
217
+ density, feat = self.get_density(points_3D, density_reg=density_reg)
218
+ rgb = self.get_color(feat, ray_unit)
219
+ return rgb, density
220
+
221
+ def get_density(self, points_3D, density_reg=None):
222
+ points_enc = self._encode_3D(points_3D)
223
+ out = self.mlp_feat(points_enc)
224
+ density, feat = out[..., 0], out[..., 1:].relu_()
225
+ if density_reg is not None:
226
+ density = density + torch.randn_like(density) * density_reg
227
+ density = self.density_activ(density)
228
+ return density, feat
229
+
230
+ def get_color(self, feat, ray_unit=None):
231
+ if self.view_dep:
232
+ ray_enc = self._encode_view(ray_unit)
233
+ feat = torch.cat([feat, ray_enc], dim=-1)
234
+ rgb = self.mlp_rgb(feat).sigmoid_() # [B,...,3]
235
+ return rgb
236
+
237
+ def _encode_3D(self, points_3D):
238
+ if self.posenc.L_3D:
239
+ points_enc = nerf_util.positional_encoding(points_3D, num_freq_bases=self.posenc.L_3D)
240
+ points_enc = torch.cat([points_3D, points_enc], dim=-1) # [B,...,6L+3]
241
+ else:
242
+ points_enc = points_3D
243
+ return points_enc
244
+
245
+ def _encode_view(self, ray_unit):
246
+ if self.posenc.L_view:
247
+ ray_enc = nerf_util.positional_encoding(ray_unit, num_freq_bases=self.posenc.L_view)
248
+ ray_enc = torch.cat([ray_unit, ray_enc], dim=-1) # [B,...,6L+3]
249
+ else:
250
+ ray_enc = ray_unit
251
+ return ray_enc
neuralangelo-main/projects/nerf/trainers/base.py ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import torch
14
+ import wandb
15
+ from imaginaire.trainers.base import BaseTrainer
16
+ from imaginaire.utils.distributed import is_master, master_only
17
+ from tqdm import tqdm
18
+
19
+ from projects.nerf.utils.misc import collate_test_data_batches, get_unique_test_data, trim_test_samples
20
+
21
+
22
+ class BaseTrainer(BaseTrainer):
23
+ """
24
+ A customized BaseTrainer.
25
+ """
26
+
27
+ def __init__(self, cfg, is_inference=True, seed=0):
28
+ super().__init__(cfg, is_inference=is_inference, seed=seed)
29
+ self.metrics = dict()
30
+ # The below configs should be properly overridden.
31
+ cfg.setdefault("wandb_scalar_iter", 9999999999999)
32
+ cfg.setdefault("wandb_image_iter", 9999999999999)
33
+ cfg.setdefault("validation_epoch", 9999999999999)
34
+ cfg.setdefault("validation_iter", 9999999999999)
35
+
36
+ def init_losses(self, cfg):
37
+ super().init_losses(cfg)
38
+ self.weights = {key: value for key, value in cfg.trainer.loss_weight.items() if value}
39
+
40
+ def _end_of_iteration(self, data, current_epoch, current_iteration):
41
+ # Log to wandb.
42
+ if current_iteration % self.cfg.wandb_scalar_iter == 0:
43
+ # Compute the elapsed time (as in the original base trainer).
44
+ self.timer.time_iteration = self.elapsed_iteration_time / self.cfg.wandb_scalar_iter
45
+ self.elapsed_iteration_time = 0
46
+ # Log scalars.
47
+ self.log_wandb_scalars(data, mode="train")
48
+ # Exit if the training loss has gone to NaN/inf.
49
+ if is_master() and self.losses["total"].isnan():
50
+ self.finalize(self.cfg)
51
+ raise ValueError("Training loss has gone to NaN!!!")
52
+ if is_master() and self.losses["total"].isinf():
53
+ self.finalize(self.cfg)
54
+ raise ValueError("Training loss has gone to infinity!!!")
55
+ if current_iteration % self.cfg.wandb_image_iter == 0:
56
+ self.log_wandb_images(data, mode="train")
57
+ # Run validation on val set.
58
+ if current_iteration % self.cfg.validation_iter == 0:
59
+ data_all = self.test(self.eval_data_loader, mode="val")
60
+ # Log the results to W&B.
61
+ if is_master():
62
+ self.log_wandb_scalars(data_all, mode="val")
63
+ self.log_wandb_images(data_all, mode="val", max_samples=self.cfg.data.val.max_viz_samples)
64
+
65
+ def _end_of_epoch(self, data, current_epoch, current_iteration):
66
+ # Run validation on val set.
67
+ if current_epoch % self.cfg.validation_epoch == 0:
68
+ data_all = self.test(self.eval_data_loader, mode="val")
69
+ # Log the results to W&B.
70
+ if is_master():
71
+ self.log_wandb_scalars(data_all, mode="val")
72
+ self.log_wandb_images(data_all, mode="val", max_samples=self.cfg.data.val.max_viz_samples)
73
+
74
+ @master_only
75
+ def log_wandb_scalars(self, data, mode=None):
76
+ scalars = dict()
77
+ # Log scalars (basic info & losses).
78
+ if mode == "train":
79
+ scalars.update({"optim/lr": self.sched.get_last_lr()[0]})
80
+ scalars.update({"time/iteration": self.timer.time_iteration})
81
+ scalars.update({"time/epoch": self.timer.time_epoch})
82
+ scalars.update({f"{mode}/loss/{key}": value for key, value in self.losses.items()})
83
+ scalars.update(iteration=self.current_iteration, epoch=self.current_epoch)
84
+ wandb.log(scalars, step=self.current_iteration)
85
+
86
+ @master_only
87
+ def log_wandb_images(self, data, mode=None, max_samples=None):
88
+ trim_test_samples(data, max_samples=max_samples)
89
+
90
+ def model_forward(self, data):
91
+ # Model forward.
92
+ output = self.model(data) # data = self.model(data) will not return the same data in the case of DDP.
93
+ data.update(output)
94
+ # Compute loss.
95
+ self.timer._time_before_loss()
96
+ self._compute_loss(data, mode="train")
97
+ total_loss = self._get_total_loss()
98
+ return total_loss
99
+
100
+ def _compute_loss(self, data, mode=None):
101
+ raise NotImplementedError
102
+
103
+ def train(self, cfg, data_loader, single_gpu=False, profile=False, show_pbar=False):
104
+ self.current_epoch = self.checkpointer.resume_epoch or self.current_epoch
105
+ self.current_iteration = self.checkpointer.resume_iteration or self.current_iteration
106
+ if ((self.current_epoch % self.cfg.validation_epoch == 0 or
107
+ self.current_iteration % self.cfg.validation_iter == 0)):
108
+ # Do an initial validation.
109
+ data_all = self.test(self.eval_data_loader, mode="val", show_pbar=show_pbar)
110
+ # Log the results to W&B.
111
+ if is_master():
112
+ self.log_wandb_scalars(data_all, mode="val")
113
+ self.log_wandb_images(data_all, mode="val", max_samples=self.cfg.data.val.max_viz_samples)
114
+ # Train.
115
+ super().train(cfg, data_loader, single_gpu, profile, show_pbar)
116
+
117
+ @torch.no_grad()
118
+ def test(self, data_loader, output_dir=None, inference_args=None, mode="test", show_pbar=False):
119
+ """The evaluation/inference engine.
120
+ Args:
121
+ data_loader: The data loader.
122
+ output_dir: Output directory to dump the test results.
123
+ inference_args: (unused)
124
+ mode: Evaluation mode {"val", "test"}. Can be other modes, but will only gather the data.
125
+ Returns:
126
+ data_all: A dictionary of all the data.
127
+ """
128
+ if self.cfg.trainer.ema_config.enabled:
129
+ model = self.model.module.averaged_model
130
+ else:
131
+ model = self.model.module
132
+ model.eval()
133
+ if show_pbar:
134
+ data_loader = tqdm(data_loader, desc="Evaluating", leave=False)
135
+ data_batches = []
136
+ for it, data in enumerate(data_loader):
137
+ data = self.start_of_iteration(data, current_iteration=self.current_iteration)
138
+ output = model.inference(data)
139
+ data.update(output)
140
+ data_batches.append(data)
141
+ # Aggregate the data from all devices and process the results.
142
+ data_gather = collate_test_data_batches(data_batches)
143
+ # Only the master process should process the results; slaves will just return.
144
+ if is_master():
145
+ data_all = get_unique_test_data(data_gather, data_gather["idx"])
146
+ tqdm.write(f"Evaluating with {len(data_all['idx'])} samples.")
147
+ # Validate/test.
148
+ if mode == "val":
149
+ self._compute_loss(data_all, mode=mode)
150
+ _ = self._get_total_loss()
151
+ if mode == "test":
152
+ # Dump the test results for postprocessing.
153
+ self.dump_test_results(data_all, output_dir)
154
+ return data_all
155
+ else:
156
+ return
157
+
158
+ def dump_test_results(self, data_all, output_dir):
159
+ raise NotImplementedError
neuralangelo-main/projects/nerf/trainers/nerf.py ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import torch
14
+ import torch.nn.functional as torch_F
15
+ import wandb
16
+ import skvideo.io
17
+
18
+ from imaginaire.utils.distributed import master_only
19
+ from projects.nerf.trainers.base import BaseTrainer
20
+ from imaginaire.utils.visualization import wandb_image, preprocess_image
21
+
22
+
23
+ class Trainer(BaseTrainer):
24
+
25
+ def __init__(self, cfg, is_inference=True, seed=0):
26
+ super().__init__(cfg, is_inference=is_inference, seed=seed)
27
+ self.batch_idx, _ = torch.meshgrid(torch.arange(cfg.data.train.batch_size),
28
+ torch.arange(cfg.model.rand_rays), indexing="ij") # [B,R]
29
+ self.batch_idx = self.batch_idx.cuda()
30
+
31
+ def _init_loss(self, cfg):
32
+ self.criteria["render"] = self.criteria["render_fine"] = torch.nn.MSELoss()
33
+
34
+ def _compute_loss(self, data, mode=None):
35
+ if mode == "train":
36
+ # Extract the corresponding sampled rays.
37
+ batch_size = len(data["image"])
38
+ image_vec = data["image"].permute(0, 2, 3, 1).view(batch_size, -1, 3) # [B,HW,3]
39
+ image_sampled = image_vec[self.batch_idx, data["ray_idx"]] # [B,R,3]
40
+ # Compute loss only on randomly sampled rays.
41
+ self.losses["render"] = self.criteria["render"](data["rgb"], image_sampled)
42
+ self.metrics["psnr"] = -10 * torch_F.mse_loss(data["rgb"], image_sampled).log10()
43
+ if self.cfg.model.fine_sampling:
44
+ self.losses["render_fine"] = self.criteria["render_fine"](data["rgb_fine"], image_sampled)
45
+ self.metrics["psnr_fine"] = -10 * torch_F.mse_loss(data["rgb_fine"], image_sampled).log10()
46
+ else:
47
+ # Compute loss on the entire image.
48
+ self.losses["render"] = self.criteria["render"](data["rgb_map"], data["image"])
49
+ self.metrics["psnr"] = -10 * torch_F.mse_loss(data["rgb_map"], data["image"]).log10()
50
+ if self.cfg.model.fine_sampling:
51
+ self.losses["render_fine"] = self.criteria["render_fine"](data["rgb_map_fine"], data["image"])
52
+ self.metrics["psnr_fine"] = -10 * torch_F.mse_loss(data["rgb_map_fine"], data["image"]).log10()
53
+
54
+ @master_only
55
+ def log_wandb_scalars(self, data, mode=None):
56
+ super().log_wandb_scalars(data, mode=mode)
57
+ scalars = {f"{mode}/PSNR/nerf": self.metrics["psnr"].detach()}
58
+ if "render_fine" in self.losses:
59
+ scalars.update({f"{mode}/PSNR/nerf_fine": self.metrics["psnr_fine"].detach()})
60
+ wandb.log(scalars, step=self.current_iteration)
61
+
62
+ @master_only
63
+ def log_wandb_images(self, data, mode=None, max_samples=None):
64
+ super().log_wandb_images(data, mode=mode, max_samples=max_samples)
65
+ images = {f"{mode}/image_target": wandb_image(data["image"])}
66
+ if mode == "val":
67
+ images_error = (data["rgb_map"] - data["image"]).abs()
68
+ images.update({
69
+ f"{mode}/images": wandb_image(data["rgb_map"]),
70
+ f"{mode}/images_error": wandb_image(images_error),
71
+ f"{mode}/inv_depth": wandb_image(data["inv_depth_map"]),
72
+ })
73
+ if self.cfg.model.fine_sampling:
74
+ images_error_fine = (data["rgb_map_fine"] - data["image"]).abs()
75
+ images.update({
76
+ f"{mode}/images_fine": wandb_image(data["rgb_map_fine"]),
77
+ f"{mode}/images_error_fine": wandb_image(images_error_fine),
78
+ f"{mode}/inv_depth_fine": wandb_image(data["inv_depth_map_fine"]),
79
+ })
80
+ images.update({"iteration": self.current_iteration})
81
+ images.update({"epoch": self.current_epoch})
82
+ wandb.log(images, step=self.current_iteration)
83
+
84
+ def dump_test_results(self, data_all, output_dir):
85
+ results = dict(
86
+ images_target=preprocess_image(data_all["images_target"]),
87
+ image=preprocess_image(data_all["rgb_map"]),
88
+ inv_depth=preprocess_image(data_all["inv_depth_map"]),
89
+ )
90
+ if self.cfg.model.fine_sampling:
91
+ results.update(
92
+ image_fine=preprocess_image(data_all["rgb_map_fine"]),
93
+ inv_depth_fine=preprocess_image(data_all["inv_depth_map_fine"]),
94
+ )
95
+ # Write results as videos.
96
+ inputdict, outputdict = self._get_ffmpeg_dicts()
97
+ for key, image_list in results.items():
98
+ print(f"writing video ({key})...")
99
+ video_fname = f"{output_dir}/{key}.mp4"
100
+ video_writer = skvideo.io.FFmpegWriter(video_fname, inputdict=inputdict, outputdict=outputdict)
101
+ for image in image_list:
102
+ image = (image * 255).byte().permute(1, 2, 0).numpy()
103
+ video_writer.writeFrame(image)
104
+ video_writer.close()
105
+
106
+ def _get_ffmpeg_dicts(self):
107
+ inputdict = {"-r": str(30)}
108
+ outputdict = {"-crf": str(10), "-pix_fmt": "yuv420p"}
109
+ return inputdict, outputdict
neuralangelo-main/projects/nerf/utils/camera.py ADDED
@@ -0,0 +1,514 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import numpy as np
14
+ import torch
15
+
16
+
17
+ class Pose():
18
+ """
19
+ A class of operations on camera poses (PyTorch tensors with shape [...,3,4]).
20
+ Each [3,4] camera pose takes the form of [R|t].
21
+ """
22
+
23
+ def __call__(self, R=None, t=None):
24
+ # Construct a camera pose from the given R and/or t.
25
+ assert R is not None or t is not None
26
+ if R is None:
27
+ if not isinstance(t, torch.Tensor):
28
+ t = torch.tensor(t)
29
+ R = torch.eye(3, device=t.device).repeat(*t.shape[:-1], 1, 1)
30
+ elif t is None:
31
+ if not isinstance(R, torch.Tensor):
32
+ R = torch.tensor(R)
33
+ t = torch.zeros(R.shape[:-1], device=R.device)
34
+ else:
35
+ if not isinstance(R, torch.Tensor):
36
+ R = torch.tensor(R)
37
+ if not isinstance(t, torch.Tensor):
38
+ t = torch.tensor(t)
39
+ assert R.shape[:-1] == t.shape and R.shape[-2:] == (3, 3)
40
+ R = R.float()
41
+ t = t.float()
42
+ pose = torch.cat([R, t[..., None]], dim=-1) # [...,3,4]
43
+ assert pose.shape[-2:] == (3, 4)
44
+ return pose
45
+
46
+ def invert(self, pose, use_inverse=False):
47
+ # Invert a camera pose.
48
+ R, t = pose[..., :3], pose[..., 3:]
49
+ R_inv = R.inverse() if use_inverse else R.transpose(-1, -2)
50
+ t_inv = (-R_inv @ t)[..., 0]
51
+ pose_inv = self(R=R_inv, t=t_inv)
52
+ return pose_inv
53
+
54
+ def compose(self, pose_list):
55
+ # Compose a sequence of poses together.
56
+ # pose_new(x) = poseN o ... o pose2 o pose1(x)
57
+ pose_new = pose_list[0]
58
+ for pose in pose_list[1:]:
59
+ pose_new = self.compose_pair(pose_new, pose)
60
+ return pose_new
61
+
62
+ def compose_pair(self, pose_a, pose_b):
63
+ # pose_new(x) = pose_b o pose_a(x)
64
+ R_a, t_a = pose_a[..., :3], pose_a[..., 3:]
65
+ R_b, t_b = pose_b[..., :3], pose_b[..., 3:]
66
+ R_new = R_b @ R_a
67
+ t_new = (R_b @ t_a + t_b)[..., 0]
68
+ pose_new = self(R=R_new, t=t_new)
69
+ return pose_new
70
+
71
+ def scale_center(self, pose, scale):
72
+ """Scale the camera center from the origin.
73
+ 0 = R@c+t --> c = -R^T@t (camera center in world coordinates)
74
+ 0 = R@(sc)+t' --> t' = -R@(sc) = -R@(-R^T@st) = st
75
+ """
76
+ R, t = pose[..., :3], pose[..., 3:]
77
+ pose_new = torch.cat([R, t * scale], dim=-1)
78
+ return pose_new
79
+
80
+ def interpolate(self, pose_a, pose_b, alpha):
81
+ """Interpolate between two poses with Slerp.
82
+ Args:
83
+ pose_a (tensor [...,3,4]): Pose at time t=0.
84
+ pose_b (tensor [...,3,4]): Pose at time t=1.
85
+ alpha (tensor [...,1]): Interpolation parameter.
86
+ Returns:
87
+ pose (tensor [...,3,4]): Pose at time t.
88
+ """
89
+ R_a, t_a = pose_a[..., :3], pose_a[..., 3:]
90
+ R_b, t_b = pose_b[..., :3], pose_b[..., 3:]
91
+ q_a = quaternion.R_to_q(R_a) # [...,4]
92
+ q_b = quaternion.R_to_q(R_b) # [...,4]
93
+ q_intp = quaternion.interpolate(q_a, q_b, alpha) # [...,4]
94
+ R_intp = quaternion.q_to_R(q_intp) # [...,3,3]
95
+ t_intp = (1 - alpha) * t_a + alpha * t_b # [...,3]
96
+ pose_intp = torch.cat([R_intp, t_intp], dim=-1) # [...,3,4]
97
+ return pose_intp
98
+
99
+
100
+ class Lie():
101
+ """
102
+ Lie algebra for SO(3) and SE(3) operations in PyTorch.
103
+ """
104
+
105
+ def so3_to_SO3(self, w): # [..., 3]
106
+ wx = self.skew_symmetric(w)
107
+ theta = w.norm(dim=-1)[..., None, None]
108
+ eye = torch.eye(3, device=w.device, dtype=torch.float32)
109
+ A = self.taylor_A(theta)
110
+ B = self.taylor_B(theta)
111
+ R = eye + A * wx + B * wx @ wx
112
+ return R
113
+
114
+ def SO3_to_so3(self, R, eps=1e-7): # [..., 3, 3]
115
+ trace = R[..., 0, 0] + R[..., 1, 1] + R[..., 2, 2]
116
+ theta = ((trace - 1) / 2).clamp(-1 + eps, 1 - eps).acos_()[
117
+ ..., None, None] % np.pi # ln(R) will explode if theta==pi
118
+ lnR = 1 / (2 * self.taylor_A(theta) + 1e-8) * (R - R.transpose(-2, -1)) # FIXME: wei-chiu finds it weird
119
+ w0, w1, w2 = lnR[..., 2, 1], lnR[..., 0, 2], lnR[..., 1, 0]
120
+ w = torch.stack([w0, w1, w2], dim=-1)
121
+ return w
122
+
123
+ def se3_to_SE3(self, wu): # [...,3]
124
+ w, u = wu.split([3, 3], dim=-1)
125
+ wx = self.skew_symmetric(w)
126
+ theta = w.norm(dim=-1)[..., None, None]
127
+ eye = torch.eye(3, device=w.device, dtype=torch.float32)
128
+ A = self.taylor_A(theta)
129
+ B = self.taylor_B(theta)
130
+ C = self.taylor_C(theta)
131
+ R = eye + A * wx + B * wx @ wx
132
+ V = eye + B * wx + C * wx @ wx
133
+ Rt = torch.cat([R, (V @ u[..., None])], dim=-1)
134
+ return Rt
135
+
136
+ def SE3_to_se3(self, Rt, eps=1e-8): # [...,3,4]
137
+ R, t = Rt.split([3, 1], dim=-1)
138
+ w = self.SO3_to_so3(R)
139
+ wx = self.skew_symmetric(w)
140
+ theta = w.norm(dim=-1)[..., None, None]
141
+ eye = torch.eye(3, device=w.device, dtype=torch.float32)
142
+ A = self.taylor_A(theta)
143
+ B = self.taylor_B(theta)
144
+ invV = eye - 0.5 * wx + (1 - A / (2 * B)) / (theta ** 2 + eps) * wx @ wx
145
+ u = (invV @ t)[..., 0]
146
+ wu = torch.cat([w, u], dim=-1)
147
+ return wu
148
+
149
+ def skew_symmetric(self, w):
150
+ w0, w1, w2 = w.unbind(dim=-1)
151
+ zero = torch.zeros_like(w0)
152
+ wx = torch.stack([torch.stack([zero, -w2, w1], dim=-1),
153
+ torch.stack([w2, zero, -w0], dim=-1),
154
+ torch.stack([-w1, w0, zero], dim=-1)], dim=-2)
155
+ return wx
156
+
157
+ def taylor_A(self, x, nth=10):
158
+ # Taylor expansion of sin(x)/x.
159
+ ans = torch.zeros_like(x)
160
+ denom = 1.
161
+ for i in range(nth + 1):
162
+ if i > 0:
163
+ denom *= (2 * i) * (2 * i + 1)
164
+ ans = ans + (-1) ** i * x ** (2 * i) / denom
165
+ return ans
166
+
167
+ def taylor_B(self, x, nth=10):
168
+ # Taylor expansion of (1-cos(x))/x**2.
169
+ ans = torch.zeros_like(x)
170
+ denom = 1.
171
+ for i in range(nth + 1):
172
+ denom *= (2 * i + 1) * (2 * i + 2)
173
+ ans = ans + (-1) ** i * x ** (2 * i) / denom
174
+ return ans
175
+
176
+ def taylor_C(self, x, nth=10):
177
+ # Taylor expansion of (x-sin(x))/x**3.
178
+ ans = torch.zeros_like(x)
179
+ denom = 1.
180
+ for i in range(nth + 1):
181
+ denom *= (2 * i + 2) * (2 * i + 3)
182
+ ans = ans + (-1) ** i * x ** (2 * i) / denom
183
+ return ans
184
+
185
+
186
+ class Quaternion():
187
+
188
+ def q_to_R(self, q): # [...,4]
189
+ # https://en.wikipedia.org/wiki/Rotation_matrix#Quaternion
190
+ qa, qb, qc, qd = q.unbind(dim=-1)
191
+ R = torch.stack(
192
+ [torch.stack([1 - 2 * (qc ** 2 + qd ** 2), 2 * (qb * qc - qa * qd), 2 * (qa * qc + qb * qd)], dim=-1),
193
+ torch.stack([2 * (qb * qc + qa * qd), 1 - 2 * (qb ** 2 + qd ** 2), 2 * (qc * qd - qa * qb)], dim=-1),
194
+ torch.stack([2 * (qb * qd - qa * qc), 2 * (qa * qb + qc * qd), 1 - 2 * (qb ** 2 + qc ** 2)], dim=-1)],
195
+ dim=-2)
196
+ return R
197
+
198
+ def R_to_q(self, R, eps=1e-6): # [...,3,3]
199
+ # https://en.wikipedia.org/wiki/Rotation_matrix#Quaternion
200
+ row0, row1, row2 = R.unbind(dim=-2)
201
+ R00, R01, R02 = row0.unbind(dim=-1)
202
+ R10, R11, R12 = row1.unbind(dim=-1)
203
+ R20, R21, R22 = row2.unbind(dim=-1)
204
+ t = R[..., 0, 0] + R[..., 1, 1] + R[..., 2, 2]
205
+ r = (1 + t + eps).sqrt()
206
+ qa = 0.5 * r
207
+ qb = (R21 - R12).sign() * 0.5 * (1 + R00 - R11 - R22 + eps).sqrt()
208
+ qc = (R02 - R20).sign() * 0.5 * (1 - R00 + R11 - R22 + eps).sqrt()
209
+ qd = (R10 - R01).sign() * 0.5 * (1 - R00 - R11 + R22 + eps).sqrt()
210
+ q = torch.stack([qa, qb, qc, qd], dim=-1)
211
+ return q
212
+
213
+ def invert(self, q): # [...,4]
214
+ qa, qb, qc, qd = q.unbind(dim=-1)
215
+ norm = q.norm(dim=-1, keepdim=True)
216
+ q_inv = torch.stack([qa, -qb, -qc, -qd], dim=-1) / norm ** 2
217
+ return q_inv
218
+
219
+ def product(self, q1, q2): # [...,4]
220
+ q1a, q1b, q1c, q1d = q1.unbind(dim=-1)
221
+ q2a, q2b, q2c, q2d = q2.unbind(dim=-1)
222
+ hamil_prod = torch.stack([q1a * q2a - q1b * q2b - q1c * q2c - q1d * q2d,
223
+ q1a * q2b + q1b * q2a + q1c * q2d - q1d * q2c,
224
+ q1a * q2c - q1b * q2d + q1c * q2a + q1d * q2b,
225
+ q1a * q2d + q1b * q2c - q1c * q2b + q1d * q2a], dim=-1)
226
+ return hamil_prod
227
+
228
+ def interpolate(self, q1, q2, alpha): # [...,4],[...,4],[...,1]
229
+ # https://en.wikipedia.org/wiki/Slerp
230
+ cos_angle = (q1 * q2).sum(dim=-1, keepdim=True) # [...,1]
231
+ flip = cos_angle < 0
232
+ q1 = q1 * (~flip) - q1 * flip # [...,4]
233
+ theta = cos_angle.abs().acos() # [...,1]
234
+ slerp = (((1 - alpha) * theta).sin() * q1 + (alpha * theta).sin() * q2) / theta.sin() # [...,4]
235
+ return slerp
236
+
237
+
238
+ pose = Pose()
239
+ lie = Lie()
240
+ quaternion = Quaternion()
241
+
242
+
243
+ def to_hom(X):
244
+ # Get homogeneous coordinates of the input.
245
+ X_hom = torch.cat([X, torch.ones_like(X[..., :1])], dim=-1)
246
+ return X_hom
247
+
248
+
249
+ # Basic operations of transforming 3D points between world/camera/image coordinates.
250
+ def world2cam(X, pose): # [B,N,3]
251
+ X_hom = to_hom(X)
252
+ return X_hom @ pose.transpose(-1, -2)
253
+
254
+
255
+ def cam2img(X, cam_intr):
256
+ return X @ cam_intr.transpose(-1, -2)
257
+
258
+
259
+ def img2cam(X, cam_intr):
260
+ return X @ cam_intr.inverse().transpose(-1, -2)
261
+
262
+
263
+ def cam2world(X, pose):
264
+ X_hom = to_hom(X)
265
+ pose_inv = Pose().invert(pose)
266
+ return X_hom @ pose_inv.transpose(-1, -2)
267
+
268
+
269
+ def angle_to_rotation_matrix(a, axis):
270
+ # Get the rotation matrix from Euler angle around specific axis.
271
+ roll = dict(X=1, Y=2, Z=0)[axis]
272
+ if isinstance(a, float):
273
+ a = torch.tensor(a)
274
+ zero = torch.zeros_like(a)
275
+ eye = torch.ones_like(a)
276
+ M = torch.stack([torch.stack([a.cos(), -a.sin(), zero], dim=-1),
277
+ torch.stack([a.sin(), a.cos(), zero], dim=-1),
278
+ torch.stack([zero, zero, eye], dim=-1)], dim=-2)
279
+ M = M.roll((roll, roll), dims=(-2, -1))
280
+ return M
281
+
282
+
283
+ def get_center_and_ray(pose, intr, image_size):
284
+ """
285
+ Args:
286
+ pose (tensor [3,4]/[B,3,4]): Camera pose.
287
+ intr (tensor [3,3]/[B,3,3]): Camera intrinsics.
288
+ image_size (list of int): Image size.
289
+ Returns:
290
+ center_3D (tensor [HW,3]/[B,HW,3]): Center of the camera.
291
+ ray (tensor [HW,3]/[B,HW,3]): Ray of the camera with depth=1 (note: not unit ray).
292
+ """
293
+ H, W = image_size
294
+ # Given the intrinsic/extrinsic matrices, get the camera center and ray directions.
295
+ with torch.no_grad():
296
+ # Compute image coordinate grid.
297
+ y_range = torch.arange(H, dtype=torch.float32, device=pose.device).add_(0.5)
298
+ x_range = torch.arange(W, dtype=torch.float32, device=pose.device).add_(0.5)
299
+ Y, X = torch.meshgrid(y_range, x_range, indexing="ij") # [H,W]
300
+ xy_grid = torch.stack([X, Y], dim=-1).view(-1, 2) # [HW,2]
301
+ # Compute center and ray.
302
+ if len(pose.shape) == 3:
303
+ batch_size = len(pose)
304
+ xy_grid = xy_grid.repeat(batch_size, 1, 1) # [B,HW,2]
305
+ grid_3D = img2cam(to_hom(xy_grid), intr) # [HW,3]/[B,HW,3]
306
+ center_3D = torch.zeros_like(grid_3D) # [HW,3]/[B,HW,3]
307
+ # Transform from camera to world coordinates.
308
+ grid_3D = cam2world(grid_3D, pose) # [HW,3]/[B,HW,3]
309
+ center_3D = cam2world(center_3D, pose) # [HW,3]/[B,HW,3]
310
+ ray = grid_3D - center_3D # [B,HW,3]
311
+ return center_3D, ray
312
+
313
+
314
+ def get_3D_points_from_dist(center, ray_unit, dist, multi=True):
315
+ # Two possible use cases: (1) center + ray_unit * dist, or (2) center + ray * depth
316
+ if multi:
317
+ center, ray_unit = center[..., None, :], ray_unit[..., None, :] # [...,1,3]
318
+ # x = c+dv
319
+ points_3D = center + ray_unit * dist # [...,3]/[...,N,3]
320
+ return points_3D
321
+
322
+
323
+ def convert_NDC(center, ray, intr, near=1):
324
+ # Shift camera center (ray origins) to near plane (z=1).
325
+ # (Unlike conventional NDC, we assume the cameras are facing towards the +z direction.)
326
+ center = center + (near - center[..., 2:]) / ray[..., 2:] * ray
327
+ # Projection.
328
+ cx, cy, cz = center.unbind(dim=-1) # [...,R]
329
+ rx, ry, rz = ray.unbind(dim=-1) # [...,R]
330
+ scale_x = intr[..., 0, 0] / intr[..., 0, 2] # [...]
331
+ scale_y = intr[..., 1, 1] / intr[..., 1, 2] # [...]
332
+ cnx = scale_x[..., None] * (cx / cz)
333
+ cny = scale_y[..., None] * (cy / cz)
334
+ cnz = 1 - 2 * near / cz
335
+ rnx = scale_x[..., None] * (rx / rz - cx / cz)
336
+ rny = scale_y[..., None] * (ry / rz - cy / cz)
337
+ rnz = 2 * near / cz
338
+ center_ndc = torch.stack([cnx, cny, cnz], dim=-1) # [...,R,3]
339
+ ray_ndc = torch.stack([rnx, rny, rnz], dim=-1) # [...,R,3]
340
+ return center_ndc, ray_ndc
341
+
342
+
343
+ def convert_NDC2(center, ray, intr):
344
+ # Similar to convert_NDC() but shift the ray origins to its own image plane instead of the global near plane.
345
+ # Also this version is much more interpretable.
346
+ scale_x = intr[..., 0, 0] / intr[..., 0, 2] # [...]
347
+ scale_y = intr[..., 1, 1] / intr[..., 1, 2] # [...]
348
+ # Get the metric image plane (i.e. new "center"): (sx*cx/cz, sy*cy/cz, 1-2/cz).
349
+ center = center + ray # This is the key difference.
350
+ cx, cy, cz = center.unbind(dim=-1) # [...,R]
351
+ image_plane = torch.stack([scale_x[..., None] * cx / cz,
352
+ scale_x[..., None] * cy / cz,
353
+ 1 - 2 / cz], dim=-1)
354
+ # Get the infinity plane: (sx*rx/rz, sy*ry/rz, 1).
355
+ rx, ry, rz = ray.unbind(dim=-1) # [...,R]
356
+ inf_plane = torch.stack([scale_x[..., None] * rx / rz,
357
+ scale_y[..., None] * ry / rz,
358
+ torch.ones_like(rz)], dim=-1)
359
+ # The NDC ray is the difference between the two planes, assuming t \in [0,1].
360
+ ndc_ray = inf_plane - image_plane
361
+ return image_plane, ndc_ray
362
+
363
+
364
+ def rotation_distance(R1, R2, eps=1e-7):
365
+ # http://www.boris-belousov.net/2016/12/01/quat-dist/
366
+ R_diff = R1 @ R2.transpose(-2, -1)
367
+ trace = R_diff[..., 0, 0] + R_diff[..., 1, 1] + R_diff[..., 2, 2]
368
+ angle = ((trace - 1) / 2).clamp(-1 + eps, 1 - eps).acos_() # numerical stability near -1/+1
369
+ return angle
370
+
371
+
372
+ def get_oscil_novel_view_poses(N=60, angle=0.05, dist=5):
373
+ # Create circular viewpoints (small oscillations).
374
+ theta = torch.arange(N) / N * 2 * np.pi
375
+ R_x = angle_to_rotation_matrix((theta.sin() * angle).asin(), "X")
376
+ R_y = angle_to_rotation_matrix((theta.cos() * angle).asin(), "Y")
377
+ pose_rot = pose(R=R_y @ R_x)
378
+ pose_shift = pose(t=[0, 0, dist])
379
+ pose_oscil = pose.compose([pose.invert(pose_shift), pose_rot, pose_shift])
380
+ return pose_oscil
381
+
382
+
383
+ def cross_product_matrix(x):
384
+ """Matrix form of cross product opertaion.
385
+
386
+ param x: [3,] tensor.
387
+ return: [3, 3] tensor representing the matrix form of cross product.
388
+ """
389
+ return torch.tensor(
390
+ [[0, -x[2], x[1]],
391
+ [x[2], 0, -x[0]],
392
+ [-x[1], x[0], 0, ]]
393
+ )
394
+
395
+
396
+ def essential_matrix(poses):
397
+ """Compute Essential Matrix from a relative pose.
398
+
399
+ param poses: [views, 3, 4] tensor representing relative poses.
400
+ return: [views, 3, 3] tensor representing Essential Matrix.
401
+ """
402
+ r = poses[..., 0:3]
403
+ t = poses[..., 3]
404
+ tx = torch.stack([cross_product_matrix(tt) for tt in t], axis=0)
405
+ return tx @ r
406
+
407
+
408
+ def fundamental_matrix(poses, intr1, intr2):
409
+ """Compute Fundamental Matrix from a relative pose and intrinsics.
410
+
411
+ param poses: [views, 3, 4] tensor representing relative poses.
412
+ intr1: [3, 3] tensor. Camera intrinsic of reference image.
413
+ intr2: [views, 3, 3] tensor. Camera Intrinsic of target image.
414
+ return: [views, 3, 3] tensor representing Fundamental Matrix.
415
+ """
416
+ return intr2.inverse().transpose(-1, -2) @ essential_matrix(poses) @ intr1.inverse()
417
+
418
+
419
+ def get_ray_depth_plane_intersection(center, ray, depths):
420
+ """Compute the intersection of a ray with a depth plane.
421
+ Args:
422
+ center (tensor [B,HW,3]): Camera center of the target pose.
423
+ ray (tensor [B,HW,3]): Ray direction of the target pose.
424
+ depth (tensor [L]): The depth values from the source view (e.g. for MPI planes).
425
+ Returns:
426
+ intsc_points (tensor [B,HW,L,3]): Intersecting 3D points with the MPI.
427
+ """
428
+ # Each 3D point x along the ray v from center c can be written as x = c+t*v.
429
+ # Plane equation: n@x = d, where normal n = (0,0,1), d = depth.
430
+ # --> t = (d-n@c)/(n@v).
431
+ # --> x = c+t*v = c+(d-n@c)/(n@v)*v.
432
+ center, ray = center[:, :, None], ray[:, :, None] # [B,HW,L,3], [B,HW,1,3]
433
+ depths = depths[None, None, :, None] # [1,1,L,1]
434
+ intsc_points = center + (depths - center[..., 2:]) / ray[..., 2:] * ray # [B,HW,L,3]
435
+ return intsc_points
436
+
437
+
438
+ def unit_view_vector_to_rotation_matrix(v, axes="ZYZ"):
439
+ """
440
+ Args:
441
+ v (tensor [...,3]): Unit vectors on the view sphere.
442
+ axes: rotation axis order.
443
+
444
+ Returns:
445
+ rotation_matrix (tensor [...,3,3]): rotation matrix R @ v + [0, 0, 1] = 0.
446
+ """
447
+ alpha = torch.arctan2(v[..., 1], v[..., 0]) # [...]
448
+ beta = np.pi - v[..., 2].arccos() # [...]
449
+ euler_angles = torch.stack([torch.ones_like(alpha) * np.pi / 2, -beta, alpha], dim=-1) # [...,3]
450
+ rot2 = angle_to_rotation_matrix(euler_angles[..., 2], axes[2]) # [...,3,3]
451
+ rot1 = angle_to_rotation_matrix(euler_angles[..., 1], axes[1]) # [...,3,3]
452
+ rot0 = angle_to_rotation_matrix(euler_angles[..., 0], axes[0]) # [...,3,3]
453
+ rot = rot2 @ rot1 @ rot0 # [...,3,3]
454
+ return rot.transpose(-2, -1)
455
+
456
+
457
+ def sample_on_spherical_cap(anchor, N, max_angle):
458
+ """Sample n points on the view hemisphere within the angle to x.
459
+ Args:
460
+ anchor (tensor [...,3]): Reference 3-D unit vector on the view hemisphere.
461
+ N (int): Number of sampled points.
462
+ max_angle (float): Sampled points should have max angle to x.
463
+ Returns:
464
+ sampled_points (tensor [...,N,3]): Sampled points on the spherical caps.
465
+ """
466
+ batch_shape = anchor.shape[:-1]
467
+ # First, sample uniformly on a unit 2D disk.
468
+ radius = torch.rand(*batch_shape, N, device=anchor.device) # [...,N]
469
+ theta = torch.rand(*batch_shape, N, device=anchor.device) * 2 * np.pi # [...,N]
470
+ x = radius.sqrt() * theta.cos() # [...,N]
471
+ y = radius.sqrt() * theta.sin() # [...,N]
472
+ # Reparametrize to a unit spherical cap with height h.
473
+ # http://marc-b-reynolds.github.io/distribution/2016/11/28/Uniform.html
474
+ h = 1 - np.cos(max_angle) # spherical cap height
475
+ k = h * radius # [...,N]
476
+ s = (h * (2 - k)).sqrt() # [...,N]
477
+ points = torch.stack([s * x, s * y, 1 - k], dim=-1) # [...,N,3]
478
+ # Transform to center around the anchor.
479
+ ref_z = torch.tensor([0., 0., 1.], device=anchor.device)
480
+ v = -anchor.cross(ref_z) # [...,3]
481
+ ss_v = lie.skew_symmetric(v) # [...,3,3]
482
+ R = torch.eye(3, device=anchor.device) + ss_v + ss_v @ ss_v / (1 + anchor @ ref_z)[..., None, None] # [...,3,3]
483
+ points = points @ R.transpose(-2, -1) # [...,N,3]
484
+ return points
485
+
486
+
487
+ def sample_on_spherical_cap_northern(anchor, N, max_angle, away_from=None, max_reject_count=None):
488
+ """Sample n points only the northern view hemisphere within the angle to x."""
489
+
490
+ def find_invalid_points(points):
491
+ southern = points[..., 2] < 0 # [...,N]
492
+ if away_from is not None:
493
+ cosine_ab = (away_from * anchor).sum(dim=-1, keepdim=True) # [...,1]
494
+ cosine_ac = (away_from[..., None, :] * points).sum(dim=-1) # [...,N]
495
+ not_outwards = cosine_ab < cosine_ac # [...,N]
496
+ invalid = southern | not_outwards
497
+ else:
498
+ invalid = southern
499
+ return invalid
500
+
501
+ assert (anchor[..., 2] > 0).all()
502
+ assert anchor.norm(dim=-1).allclose(torch.ones_like(anchor[..., 0]))
503
+ points = sample_on_spherical_cap(anchor, N, max_angle) # [...,N,3]
504
+ invalid = find_invalid_points(points)
505
+ count = 0
506
+ while invalid.any():
507
+ # Reject and resample.
508
+ points_resample = sample_on_spherical_cap(anchor, N, max_angle)
509
+ points[invalid] = points_resample[invalid]
510
+ invalid = find_invalid_points(points)
511
+ count += 1
512
+ if max_reject_count and count > max_reject_count:
513
+ points = anchor.repeat(N, 1)
514
+ return points
neuralangelo-main/projects/nerf/utils/misc.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import torch
14
+
15
+ from imaginaire.utils.distributed import dist_all_gather_tensor
16
+
17
+
18
+ def collate_test_data_batches(data_batches):
19
+ """Aggregate the list of test data from all devices and process the results.
20
+ Args:
21
+ data_batches (list): List of (hierarchical) dictionaries, where leaf entries are tensors.
22
+ Returns:
23
+ data_gather (dict): (hierarchical) dictionaries, where leaf entries are concatenated tensors.
24
+ """
25
+ data_gather = dict()
26
+ for key in data_batches[0].keys():
27
+ data_list = [data[key] for data in data_batches]
28
+ if isinstance(data_batches[0][key], dict):
29
+ data_gather[key] = collate_test_data_batches(data_list)
30
+ elif isinstance(data_batches[0][key], torch.Tensor):
31
+ data_gather[key] = torch.cat(data_list, dim=0)
32
+ data_gather[key] = torch.cat(dist_all_gather_tensor(data_gather[key].contiguous()), dim=0)
33
+ else:
34
+ raise TypeError
35
+ return data_gather
36
+
37
+
38
+ def get_unique_test_data(data_gather, idx):
39
+ """Aggregate the list of test data from all devices and process the results.
40
+ Args:
41
+ data_gather (dict): (hierarchical) dictionaries, where leaf entries are tensors.
42
+ idx (tensor): sample indices.
43
+ Returns:
44
+ data_all (dict): (hierarchical) dictionaries, where leaf entries are tensors ordered by idx.
45
+ """
46
+ data_all = dict()
47
+ for key, value in data_gather.items():
48
+ if isinstance(value, dict):
49
+ data_all[key] = get_unique_test_data(value, idx)
50
+ elif isinstance(value, torch.Tensor):
51
+ data_all[key] = []
52
+ for i in range(max(idx) + 1):
53
+ # If multiple occurrences of the same idx, just choose the first one. If no occurrence, just ignore.
54
+ matches = (idx == i).nonzero(as_tuple=True)[0]
55
+ if matches.numel() != 0:
56
+ data_all[key].append(value[matches[0]])
57
+ data_all[key] = torch.stack(data_all[key], dim=0)
58
+ else:
59
+ raise TypeError
60
+ return data_all
61
+
62
+
63
+ def trim_test_samples(data, max_samples=None):
64
+ for key, value in data.items():
65
+ if isinstance(value, dict):
66
+ data[key] = trim_test_samples(value, max_samples=max_samples)
67
+ elif isinstance(value, torch.Tensor):
68
+ if max_samples is not None:
69
+ data[key] = value[:max_samples]
70
+ else:
71
+ raise TypeError
neuralangelo-main/projects/nerf/utils/nerf_util.py ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import numpy as np
14
+ import torch
15
+ import torch.nn.functional as torch_F
16
+
17
+ from projects.nerf.utils import camera
18
+
19
+
20
+ def sample_dists(ray_size, dist_range, intvs, stratified, device="cuda"):
21
+ """Sample points on ray shooting from pixels using distance.
22
+ Args:
23
+ ray_size (int [2]): Integers for [batch size, number of rays].
24
+ range (float [2]): Range of distance (depth) [min, max] to be sampled on rays.
25
+ intvs: (int): Number of points sampled on a ray.
26
+ stratified: (bool): Use stratified sampling or constant 0.5 sampling.
27
+ Returns:
28
+ dists (tensor [batch_size, num_ray, intvs, 1]): Sampled distance for all rays in a batch.
29
+ """
30
+ batch_size, num_rays = ray_size
31
+ dist_min, dist_max = dist_range
32
+ if stratified:
33
+ rands = torch.rand(batch_size, num_rays, intvs, 1, device=device)
34
+ else:
35
+ rands = torch.empty(batch_size, num_rays, intvs, 1, device=device).fill_(0.5)
36
+ rands += torch.arange(intvs, dtype=torch.float, device=device)[None, None, :, None] # [B,R,N,1]
37
+ dists = rands / intvs * (dist_max - dist_min) + dist_min # [B,R,N,1]
38
+ return dists
39
+
40
+
41
+ def sample_dists_from_pdf(bin, weights, intvs_fine):
42
+ """Sample points on ray shooting from pixels using the weights from the coarse NeRF.
43
+ Args:
44
+ bin (tensor [batch_size, num_rays, intvs]): bins of distance values from the coarse NeRF.
45
+ weights (tensor [batch_size, num_rays, intvs]): weights from the coarse NeRF.
46
+ intvs_fine: (int): Number of fine-grained points sampled on a ray.
47
+ Returns:
48
+ dists (tensor [batch_size, num_ray, intvs, 1]): Sampled distance for all rays in a batch.
49
+ """
50
+ pdf = torch_F.normalize(weights, p=1, dim=-1)
51
+ # Get CDF from PDF (along last dimension).
52
+ cdf = pdf.cumsum(dim=-1) # [B,R,N]
53
+ cdf = torch.cat([torch.zeros_like(cdf[..., :1]), cdf], dim=-1) # [B,R,N+1]
54
+ # Take uniform samples.
55
+ grid = torch.linspace(0, 1, intvs_fine + 1, device=pdf.device) # [Nf+1]
56
+ unif = 0.5 * (grid[:-1] + grid[1:]).repeat(*cdf.shape[:-1], 1) # [B,R,Nf]
57
+ idx = torch.searchsorted(cdf, unif, right=True) # [B,R,Nf] \in {1...N}
58
+ # Inverse transform sampling from CDF.
59
+ low = (idx - 1).clamp(min=0) # [B,R,Nf]
60
+ high = idx.clamp(max=cdf.shape[-1] - 1) # [B,R,Nf]
61
+ dist_min = bin[..., 0].gather(dim=2, index=low) # [B,R,Nf]
62
+ dist_max = bin[..., 0].gather(dim=2, index=high) # [B,R,Nf]
63
+ cdf_low = cdf.gather(dim=2, index=low) # [B,R,Nf]
64
+ cdf_high = cdf.gather(dim=2, index=high) # [B,R,Nf]
65
+ # Linear interpolation.
66
+ t = (unif - cdf_low) / (cdf_high - cdf_low + 1e-8) # [B,R,Nf]
67
+ dists = dist_min + t * (dist_max - dist_min) # [B,R,Nf]
68
+ return dists[..., None] # [B,R,Nf,1]
69
+
70
+
71
+ def reparametrize_dist(dist, param_type="metric"):
72
+ """Reparametrize the sampled distance values according to param_type.
73
+ Args:
74
+ dist (tensor): Sampled distance values.
75
+ param_type (str): Reparametrization type.
76
+ Returns:
77
+ dist_new (tensor): Reparametrized distance values.
78
+ """
79
+ return dict(
80
+ metric=dist,
81
+ ndc=dist,
82
+ inverse=1 / (dist + 1e-8),
83
+ )[param_type]
84
+
85
+
86
+ def ray_generator(pose, intr, image_size, num_rays, full_image=False, camera_ndc=False,
87
+ ray_indices=None):
88
+ """Yield sampled rays for coordinate-based model to predict NeRF.
89
+ Args:
90
+ pose (tensor [bs,3,4]): Camera poses ([R,t]).
91
+ intr (tensor [bs,3,3]): Camera intrinsics.
92
+ image_size: (tensor [bs,2]): Image size [height, width].
93
+ num_rays (int): Number of rays to sample (random rays unless full_image=True).
94
+ full_image (bool): Sample rays from the full image.
95
+ camera_ndc (bool): Use normalized device coordinate for camera.
96
+ Returns:
97
+ center_slice (tensor [bs, ray, 3]): Sampled 3-D center in the world coordinate.
98
+ ray_slice (tensor [bs, ray, 3]): Sampled 3-D ray in the world coordinate.
99
+ ray_idx (tensor [bs, ray]): Sampled indices to index sampled pixels on images.
100
+ """
101
+ # Create a grid of centers and rays on an image.
102
+ batch_size = pose.shape[0]
103
+ # We used to randomly sample ray indices here. Now, we assume they are pre-generated and passed in.
104
+ if ray_indices is None:
105
+ num_pixels = image_size[0] * image_size[1]
106
+ if full_image:
107
+ # Sample rays from the full image.
108
+ ray_indices = torch.arange(0, num_pixels, device=pose.device).repeat(batch_size, 1) # [B,HW]
109
+ else:
110
+ # Sample rays randomly. The below is equivalent to batched torch.randperm().
111
+ ray_indices = torch.rand(batch_size, num_pixels, device=pose.device).argsort(dim=1)[:, :num_rays] # [B,R]
112
+ center, ray = camera.get_center_and_ray(pose, intr, image_size) # [B,HW,3]
113
+ # Convert center/ray representations to NDC if necessary.
114
+ if camera_ndc == "new":
115
+ center, ray = camera.convert_NDC2(center, ray, intr=intr)
116
+ elif camera_ndc:
117
+ center, ray = camera.convert_NDC(center, ray, intr=intr)
118
+ # Yield num_rays of sampled rays in each iteration (when random, the loop will only iterate once).
119
+ for c in range(0, ray_indices.shape[1], num_rays):
120
+ ray_idx = ray_indices[:, c:c + num_rays] # [B,R]
121
+ batch_idx = torch.arange(batch_size, device=pose.device).repeat(ray_idx.shape[1], 1).t() # [B,R]
122
+ center_slice = center[batch_idx, ray_idx] # [B,R,3]
123
+ ray_slice = ray[batch_idx, ray_idx] # [B,R,3]
124
+ yield center_slice, ray_slice, ray_idx
125
+
126
+
127
+ def slice_by_ray_idx(var, ray_idx):
128
+ batch_size, num_rays = ray_idx.shape[:2]
129
+ batch_idx = torch.arange(batch_size, device=ray_idx.device).repeat(num_rays, 1).t() # [B,R]
130
+ var_slice = var[batch_idx, ray_idx] # [B,R,...]
131
+ return var_slice
132
+
133
+
134
+ def positional_encoding(input, num_freq_bases):
135
+ """Encode input into position codes.
136
+ Args:
137
+ input (tensor [bs, ..., N]): A batch of data with N dimension.
138
+ num_freq_bases: (int): The number of frequency base of the code.
139
+ Returns:
140
+ input_enc (tensor [bs, ..., 2*N*num_freq_bases]): Positional codes for input.
141
+ """
142
+ freq = 2 ** torch.arange(num_freq_bases, dtype=torch.float32, device=input.device) * np.pi # [L].
143
+ spectrum = input[..., None] * freq # [B,...,N,L].
144
+ sin, cos = spectrum.sin(), spectrum.cos() # [B,...,N,L].
145
+ input_enc = torch.stack([sin, cos], dim=-2) # [B,...,N,2,L].
146
+ input_enc = input_enc.view(*input.shape[:-1], -1) # [B,...,2NL].
147
+ return input_enc
148
+
149
+
150
+ def get_inverse_depth(depth, opacity=None, camera_ndc=False, eps=1e-10):
151
+ # Compute inverse depth for visualization.
152
+ if opacity is not None:
153
+ return (1 - depth) / opacity if camera_ndc else 1 / (depth / opacity + eps)
154
+ else:
155
+ return (1 - depth) if camera_ndc else 1 / (depth + eps)
156
+
157
+
158
+ class MLPwithSkipConnection(torch.nn.Module):
159
+
160
+ def __init__(self, layer_dims, skip_connection=[], activ=None, use_layernorm=False, use_weightnorm=False):
161
+ """Initialize a multi-layer perceptron with skip connection.
162
+ Args:
163
+ layer_dims: A list of integers representing the number of channels in each layer.
164
+ skip_connection: A list of integers representing the index of layers to add skip connection.
165
+ """
166
+ super().__init__()
167
+ self.skip_connection = skip_connection
168
+ self.use_layernorm = use_layernorm
169
+ self.linears = torch.nn.ModuleList()
170
+ if use_layernorm:
171
+ self.layer_norm = torch.nn.ModuleList()
172
+ layer_dim_pairs = list(zip(layer_dims[:-1], layer_dims[1:]))
173
+ for li, (k_in, k_out) in enumerate(layer_dim_pairs):
174
+ if li in self.skip_connection:
175
+ k_in += layer_dims[0]
176
+ linear = torch.nn.Linear(k_in, k_out)
177
+ if use_weightnorm:
178
+ linear = torch.nn.utils.weight_norm(linear)
179
+ self.linears.append(linear)
180
+ if use_layernorm and li != len(layer_dim_pairs) - 1:
181
+ self.layer_norm.append(torch.nn.LayerNorm(k_out))
182
+ if li == len(layer_dim_pairs) - 1:
183
+ self.linears[-1].bias.data.fill_(0.0)
184
+ self.activ = activ or torch_F.relu_
185
+
186
+ def forward(self, input):
187
+ feat = input
188
+ for li, linear in enumerate(self.linears):
189
+ if li in self.skip_connection:
190
+ feat = torch.cat([feat, input], dim=-1)
191
+ feat = linear(feat)
192
+ if li != len(self.linears) - 1:
193
+ if self.use_layernorm:
194
+ feat = self.layer_norm[li](feat)
195
+ feat = self.activ(feat)
196
+ return feat
197
+
198
+
199
+ def intersect_with_sphere(center, ray_unit, radius=1.0):
200
+ ctc = (center * center).sum(dim=-1, keepdim=True) # [...,1]
201
+ ctv = (center * ray_unit).sum(dim=-1, keepdim=True) # [...,1]
202
+ b2_minus_4ac = ctv ** 2 - (ctc - radius ** 2)
203
+ dist_near = -ctv - b2_minus_4ac.sqrt()
204
+ dist_far = -ctv + b2_minus_4ac.sqrt()
205
+ return dist_near, dist_far
206
+
207
+
208
+ def get_pixel_radii(intr):
209
+ # fx and fy should be very close.
210
+ focal = (intr[..., 0, 0] + intr[..., 1, 1]) / 2
211
+ radii = 1. / focal / np.sqrt(3)
212
+ return radii
213
+
214
+
215
+ def contract(x, r_in=1, r_out=2, eps=1e-8):
216
+ """ Contract function in mip-NeRF 360 (eq 10).
217
+ Args:
218
+ x (tensor [...,3]): The input points.
219
+ Returns:
220
+ x_warp (tensor [...,3]): The warped points.
221
+ """
222
+ x_norm = x.norm(dim=-1, keepdim=True) # [...,1]
223
+ scale = r_out - r_in * (r_out - r_in) / (x_norm + eps) # [...,1]
224
+ x_contract = scale * torch_F.normalize(x, dim=-1) # [...,3]
225
+ # No effect if within r_in.
226
+ inside = x_norm <= r_in
227
+ x_warp = torch.where(inside, x, x_contract) # [...,3]
228
+ return x_warp
229
+
230
+
231
+ def contract_jacobian(x, r_in=1, r_out=2, eps=1e-8):
232
+ """ Jacobian of the contract function in mip-NeRF 360.
233
+ Args:
234
+ x (tensor [...,3]): The input points.
235
+ Returns:
236
+ jacobian (tensor [...,3,3]): The Jacobian at the input points.
237
+ """
238
+ x_norm = x.norm(dim=-1)[..., None, None] # [...,1,1]
239
+ x_norm_sq = (x ** 2).sum(dim=-1)[..., None, None] # [...,1,1] should be numerically more stable
240
+ b = r_in * (r_out - r_in)
241
+ scale = r_out - b / (x_norm + eps) # [...,1,1]
242
+ x_outer_prod = x[..., None] * x[..., None, :] # [...,3,3]
243
+ eye = torch.eye(3, device=x.device).repeat(*x_norm.shape) # [...,3,3]
244
+ term1 = b * x_outer_prod / (x_norm_sq ** 2 + eps) # [...,3,3]
245
+ term2 = scale * (eye - x_outer_prod / x_norm_sq + eps) / (x_norm + eps) # [...,3,3]
246
+ jacobian_contract = term1 + term2 # [...,3,3]
247
+ # No effect if within r_in.
248
+ inside = x_norm <= r_in
249
+ jacobian = torch.where(inside, eye, jacobian_contract) # [...,3]
250
+ return jacobian
251
+
252
+
253
+ def contract_mip(mean, cov, r_in=1, r_out=2, diag=False):
254
+ """ Contraction function on mip-NeRF 360 Gaussians.
255
+ Args:
256
+ mean (tensor [...,3]): The mean values.
257
+ cov (tensor [...,3,3]): The covariance values.
258
+ r_in (float): The radius of unaffected region.
259
+ r_out (float): The radius of contracted region. r_in < r_out.
260
+ Returns:
261
+ mean_warp (tensor [...,3]): The contracted mean values.
262
+ cov_warp (tensor [...,3,3]): The contracted covariance values.
263
+ """
264
+ mean_warp = contract(mean, r_in=r_in, r_out=r_out) # [...,3]
265
+ jacobian = contract_jacobian(mean, r_in=r_in, r_out=r_out) # [...,3,3]
266
+ if diag:
267
+ cov_warp = (jacobian * cov[..., None, :]) @ jacobian.transpose(-2, -1)
268
+ else:
269
+ cov_warp = jacobian @ cov @ jacobian.transpose(-2, -1)
270
+ return mean_warp, cov_warp
neuralangelo-main/projects/nerf/utils/render.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import torch
14
+ from torch.cuda.amp import autocast
15
+
16
+
17
+ def volume_rendering_weights(ray, densities, depths, depth_far=None):
18
+ """The volume rendering function. Details can be found in the NeRF paper.
19
+ Args:
20
+ ray (tensor [batch,ray,3]): The ray directions in world space.
21
+ densities (tensor [batch,ray,samples]): The predicted volume density samples.
22
+ depths (tensor [batch,ray,samples,1]): The corresponding depth samples.
23
+ depth_far (tensor [batch,ray,1,1]): The farthest depth for computing the last interval.
24
+ Returns:
25
+ weights (tensor [batch,ray,samples,1]): The predicted weight of each sampled point along the ray (in [0,1]).
26
+ """
27
+ ray_length = ray.norm(dim=-1, keepdim=True) # [B,R,1]
28
+ if depth_far is None:
29
+ depth_far = torch.empty_like(depths[..., :1, :]).fill_(1e10) # [B,R,1,1]
30
+ depths_aug = torch.cat([depths, depth_far], dim=2) # [B,R,N+1,1]
31
+ dists = depths_aug * ray_length[..., None] # [B,R,N+1,1]
32
+ # Volume rendering: compute rendering weights (using quadrature).
33
+ dist_intvs = dists[..., 1:, 0] - dists[..., :-1, 0] # [B,R,N]
34
+ sigma_delta = densities * dist_intvs # [B,R,N]
35
+ sigma_delta_0 = torch.cat([torch.zeros_like(sigma_delta[..., :1]),
36
+ sigma_delta[..., :-1]], dim=2) # [B,R,N]
37
+ T = (-sigma_delta_0.cumsum(dim=2)).exp_() # [B,R,N]
38
+ alphas = 1 - (-sigma_delta).exp_() # [B,R,N]
39
+ # Compute weights for compositing samples.
40
+ weights = (T * alphas)[..., None] # [B,R,N,1]
41
+ return weights
42
+
43
+
44
+ def volume_rendering_weights_dist(densities, dists, dist_far=None):
45
+ """The volume rendering function. Details can be found in the NeRF paper.
46
+ Args:
47
+ densities (tensor [batch,ray,samples]): The predicted volume density samples.
48
+ dists (tensor [batch,ray,samples,1]): The corresponding distance samples.
49
+ dist_far (tensor [batch,ray,1,1]): The farthest distance for computing the last interval.
50
+ Returns:
51
+ weights (tensor [batch,ray,samples,1]): The predicted weight of each sampled point along the ray (in [0,1]).
52
+ """
53
+ # TODO: re-consolidate!!
54
+ if dist_far is None:
55
+ dist_far = torch.empty_like(dists[..., :1, :]).fill_(1e10) # [B,R,1,1]
56
+ dists = torch.cat([dists, dist_far], dim=2) # [B,R,N+1,1]
57
+ # Volume rendering: compute rendering weights (using quadrature).
58
+ dist_intvs = dists[..., 1:, 0] - dists[..., :-1, 0] # [B,R,N]
59
+ sigma_delta = densities * dist_intvs # [B,R,N]
60
+ sigma_delta_0 = torch.cat([torch.zeros_like(sigma_delta[..., :1]), sigma_delta[..., :-1]], dim=2) # [B,R,N]
61
+ T = (-sigma_delta_0.cumsum(dim=2)).exp_() # [B,R,N]
62
+ alphas = 1 - (-sigma_delta).exp_() # [B,R,N]
63
+ # Compute weights for compositing samples.
64
+ weights = (T * alphas)[..., None] # [B,R,N,1]
65
+ return weights
66
+
67
+
68
+ def volume_rendering_alphas_dist(densities, dists, dist_far=None):
69
+ """The volume rendering function. Details can be found in the NeRF paper.
70
+ Args:
71
+ densities (tensor [batch,ray,samples]): The predicted volume density samples.
72
+ dists (tensor [batch,ray,samples,1]): The corresponding distance samples.
73
+ dist_far (tensor [batch,ray,1,1]): The farthest distance for computing the last interval.
74
+ Returns:
75
+ alphas (tensor [batch,ray,samples,1]): The occupancy of each sampled point along the ray (in [0,1]).
76
+ """
77
+ if dist_far is None:
78
+ dist_far = torch.empty_like(dists[..., :1, :]).fill_(1e10) # [B,R,1,1]
79
+ dists = torch.cat([dists, dist_far], dim=2) # [B,R,N+1,1]
80
+ # Volume rendering: compute rendering weights (using quadrature).
81
+ dist_intvs = dists[..., 1:, 0] - dists[..., :-1, 0] # [B,R,N]
82
+ sigma_delta = densities * dist_intvs # [B,R,N]
83
+ alphas = 1 - (-sigma_delta).exp_() # [B,R,N]
84
+ return alphas
85
+
86
+
87
+ def alpha_compositing_weights(alphas):
88
+ """Alpha compositing of (sampled) MPIs given their RGBs and alphas.
89
+ Args:
90
+ alphas (tensor [batch,ray,samples]): The predicted opacity values.
91
+ Returns:
92
+ weights (tensor [batch,ray,samples,1]): The predicted weight of each MPI (in [0,1]).
93
+ """
94
+ alphas_front = torch.cat([torch.zeros_like(alphas[..., :1]),
95
+ alphas[..., :-1]], dim=2) # [B,R,N]
96
+ with autocast(enabled=False): # TODO: may be unstable in some cases.
97
+ visibility = (1 - alphas_front).cumprod(dim=2) # [B,R,N]
98
+ weights = (alphas * visibility)[..., None] # [B,R,N,1]
99
+ return weights
100
+
101
+
102
+ def composite(quantities, weights):
103
+ """Composite the samples to render the RGB/depth/opacity of the corresponding pixels.
104
+ Args:
105
+ quantities (tensor [batch,ray,samples,k]): The quantity to be weighted summed.
106
+ weights (tensor [batch,ray,samples,1]): The predicted weight of each sampled point along the ray.
107
+ Returns:
108
+ quantity (tensor [batch,ray,k]): The expected (rendered) quantity.
109
+ """
110
+ # Integrate RGB and depth weighted by probability.
111
+ quantity = (quantities * weights).sum(dim=2) # [B,R,K]
112
+ return quantity
neuralangelo-main/projects/nerf/utils/visualize.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ -----------------------------------------------------------------------------
3
+ Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
4
+
5
+ NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ and proprietary rights in and to this software, related documentation
7
+ and any modifications thereto. Any use, reproduction, disclosure or
8
+ distribution of this software and related documentation without an express
9
+ license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+ -----------------------------------------------------------------------------
11
+ '''
12
+
13
+ import numpy as np
14
+ import torch
15
+ import matplotlib.pyplot as plt
16
+ import k3d
17
+
18
+ from projects.nerf.utils import camera
19
+
20
+
21
+ def get_camera_mesh(pose, depth=1):
22
+ vertices = torch.tensor([[-0.5, -0.5, 1],
23
+ [0.5, -0.5, 1],
24
+ [0.5, 0.5, 1],
25
+ [-0.5, 0.5, 1],
26
+ [0, 0, 0]]) * depth # [6,3]
27
+ faces = torch.tensor([[0, 1, 2],
28
+ [0, 2, 3],
29
+ [0, 1, 4],
30
+ [1, 2, 4],
31
+ [2, 3, 4],
32
+ [3, 0, 4]]) # [6,3]
33
+ vertices = camera.cam2world(vertices[None], pose) # [N,6,3]
34
+ wireframe = vertices[:, [0, 1, 2, 3, 0, 4, 1, 2, 4, 3]] # [N,10,3]
35
+ return vertices, faces, wireframe
36
+
37
+
38
+ def merge_meshes(vertices, faces):
39
+ mesh_N, vertex_N = vertices.shape[:2]
40
+ faces_merged = torch.cat([faces + i * vertex_N for i in range(mesh_N)], dim=0)
41
+ vertices_merged = vertices.view(-1, vertices.shape[-1])
42
+ return vertices_merged, faces_merged
43
+
44
+
45
+ def merge_wireframes(wireframe):
46
+ wf_first, wf_last, wf_dummy = wireframe[:, :1], wireframe[:, -1:], wireframe[:, :1] * np.nan
47
+ wireframe_merged = torch.cat([wf_first, wireframe, wf_last, wf_dummy], dim=1)
48
+ return wireframe_merged
49
+
50
+
51
+ def get_xyz_indicators(pose, length=0.1):
52
+ xyz = torch.eye(4, 3)[None] * length
53
+ xyz = camera.cam2world(xyz, pose)
54
+ return xyz
55
+
56
+
57
+ def merge_xyz_indicators(xyz): # [N,4,3]
58
+ xyz = xyz[:, [[-1, 0], [-1, 1], [-1, 2]]] # [N,3,2,3]
59
+ xyz_0, xyz_1 = xyz.unbind(dim=2) # [N,3,3]
60
+ xyz_dummy = xyz_0 * np.nan
61
+ xyz_merged = torch.stack([xyz_0, xyz_0, xyz_1, xyz_1, xyz_dummy], dim=2) # [N,3,5,3]
62
+ return xyz_merged
63
+
64
+
65
+ def k3d_visualize_pose(poses, vis_depth=0.5, xyz_length=0.1, center_size=0.1, xyz_width=0.02):
66
+ # poses has shape [N,3,4] potentially in sequential order
67
+ N = len(poses)
68
+ centers_cam = torch.zeros(N, 1, 3)
69
+ centers_world = camera.cam2world(centers_cam, poses)
70
+ centers_world = centers_world[:, 0]
71
+ # Get the camera wireframes.
72
+ vertices, faces, wireframe = get_camera_mesh(poses, depth=vis_depth)
73
+ xyz = get_xyz_indicators(poses, length=xyz_length)
74
+ vertices_merged, faces_merged = merge_meshes(vertices, faces)
75
+ wireframe_merged = merge_wireframes(wireframe)
76
+ xyz_merged = merge_xyz_indicators(xyz)
77
+ # Set the color map for the camera trajectory and the xyz indicators.
78
+ color_map = plt.get_cmap("gist_rainbow")
79
+ center_color = []
80
+ vertices_merged_color = []
81
+ wireframe_color = []
82
+ xyz_color = []
83
+ x_hex, y_hex, z_hex = int(255) << 16, int(255) << 8, int(255)
84
+ for i in range(N):
85
+ # Set the camera pose colors (with a smooth gradient color map).
86
+ r, g, b, _ = color_map(i / (N - 1))
87
+ r, g, b = r * 0.8, g * 0.8, b * 0.8
88
+ pose_rgb_hex = (int(r * 255) << 16) + (int(g * 255) << 8) + int(b * 255)
89
+ center_color += [pose_rgb_hex]
90
+ vertices_merged_color += [pose_rgb_hex] * 5
91
+ wireframe_color += [pose_rgb_hex] * 13
92
+ # Set the xyz indicator colors.
93
+ xyz_color += [x_hex] * 5 + [y_hex] * 5 + [z_hex] * 5
94
+ # Plot in K3D.
95
+ plot = k3d.plot(name="poses",
96
+ height=800,
97
+ camera_rotate_speed=5.0,
98
+ camera_zoom_speed=3.0,
99
+ camera_pan_speed=1.0,
100
+ )
101
+ plot += k3d.points(centers_world,
102
+ colors=center_color,
103
+ point_size=center_size,
104
+ shader="3d",
105
+ )
106
+ plot += k3d.mesh(vertices_merged, faces_merged,
107
+ colors=vertices_merged_color,
108
+ side="double",
109
+ opacity=0.05,
110
+ )
111
+ plot += k3d.line(wireframe_merged,
112
+ colors=wireframe_color,
113
+ shader="simple",
114
+ )
115
+ plot += k3d.line(xyz_merged,
116
+ colors=xyz_color,
117
+ shader="thick",
118
+ width=xyz_width,
119
+ )
120
+ return plot
neuralangelo-main/projects/neuralangelo/configs/base.yaml ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -----------------------------------------------------------------------------
2
+ # Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
3
+ #
4
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
5
+ # and proprietary rights in and to this software, related documentation
6
+ # and any modifications thereto. Any use, reproduction, disclosure or
7
+ # distribution of this software and related documentation without an express
8
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
9
+ # -----------------------------------------------------------------------------
10
+
11
+ logging_iter: 9999999999999 # disable the printing logger
12
+
13
+ max_iter: 500000
14
+
15
+ wandb_scalar_iter: 100
16
+ wandb_image_iter: 10000
17
+ validation_iter: 5000
18
+ speed_benchmark: False
19
+
20
+ checkpoint:
21
+ save_iter: 20000
22
+
23
+ trainer:
24
+ type: projects.neuralangelo.trainer
25
+ ema_config:
26
+ enabled: False
27
+ load_ema_checkpoint: False
28
+ loss_weight:
29
+ render: 1.0
30
+ eikonal: 0.1
31
+ curvature: 5e-4
32
+ init:
33
+ type: none
34
+ amp_config:
35
+ enabled: False
36
+ depth_vis_scale: 0.5
37
+
38
+ model:
39
+ type: projects.neuralangelo.model
40
+ object:
41
+ sdf:
42
+ mlp:
43
+ num_layers: 2
44
+ hidden_dim: 256
45
+ skip: []
46
+ activ: softplus
47
+ activ_params:
48
+ beta: 100
49
+ geometric_init: True
50
+ weight_norm: True
51
+ out_bias: 0.5
52
+ inside_out: False
53
+ encoding:
54
+ type: hashgrid
55
+ levels: 16
56
+ hashgrid:
57
+ min_logres: 5
58
+ max_logres: 11
59
+ dict_size: 22
60
+ dim: 8
61
+ range: [-2,2]
62
+ coarse2fine:
63
+ enabled: True
64
+ init_active_level: 4
65
+ step: 5000
66
+ gradient:
67
+ mode: numerical
68
+ taps: 4
69
+ rgb:
70
+ mlp:
71
+ num_layers: 4
72
+ hidden_dim: 256
73
+ skip: []
74
+ activ: relu_
75
+ activ_params: {}
76
+ weight_norm: True
77
+ mode: idr
78
+ encoding_view:
79
+ type: spherical
80
+ levels: 3
81
+ s_var:
82
+ init_val: 3.
83
+ anneal_end: 0.1
84
+ background:
85
+ enabled: True
86
+ white: False
87
+ mlp:
88
+ num_layers: 8
89
+ hidden_dim: 256
90
+ skip: [4]
91
+ num_layers_rgb: 2
92
+ hidden_dim_rgb: 128
93
+ skip_rgb: []
94
+ activ: relu
95
+ activ_params: {}
96
+ activ_density: softplus
97
+ activ_density_params: {}
98
+ view_dep: True
99
+ encoding:
100
+ type: fourier
101
+ levels: 10
102
+ encoding_view:
103
+ type: spherical
104
+ levels: 3
105
+ render:
106
+ rand_rays: 512
107
+ num_samples:
108
+ coarse: 64
109
+ fine: 16
110
+ background: 32
111
+ num_sample_hierarchy: 4
112
+ stratified: True
113
+ appear_embed:
114
+ enabled: False
115
+ dim: 8
116
+
117
+ optim:
118
+ type: AdamW
119
+ params:
120
+ lr: 1e-3
121
+ weight_decay: 1e-3
122
+ sched:
123
+ iteration_mode: True
124
+ type: two_steps_with_warmup
125
+ warm_up_end: 5000
126
+ two_steps: [300000,400000]
127
+ gamma: 10.0
128
+
129
+ data:
130
+ type: projects.nerf.datasets.nerf_blender
131
+ root: datasets/nerf-synthetic/lego
132
+ use_multi_epoch_loader: True
133
+ num_workers: 4
134
+ preload: True
135
+ num_images: # The number of training images.
136
+ train:
137
+ image_size: [800,800]
138
+ batch_size: 2
139
+ subset:
140
+ val:
141
+ image_size: [400,400]
142
+ batch_size: 2
143
+ subset: 4
144
+ max_viz_samples: 16
145
+ readjust:
146
+ center: [0.,0.,0.]
147
+ scale: 1.
neuralangelo-main/projects/neuralangelo/configs/custom/template.yaml ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -----------------------------------------------------------------------------
2
+ # Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
3
+ #
4
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
5
+ # and proprietary rights in and to this software, related documentation
6
+ # and any modifications thereto. Any use, reproduction, disclosure or
7
+ # distribution of this software and related documentation without an express
8
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
9
+ # -----------------------------------------------------------------------------
10
+
11
+ _parent_: projects/neuralangelo/configs/base.yaml
12
+
13
+ model:
14
+ object:
15
+ sdf:
16
+ mlp:
17
+ inside_out: False
18
+ encoding:
19
+ coarse2fine:
20
+ init_active_level: 8
21
+ appear_embed:
22
+ enabled: True
23
+ dim: 8
24
+
25
+ data:
26
+ type: projects.neuralangelo.data
27
+ root: # The root path of the dataset.
28
+ num_images: # The number of training images.
29
+ train:
30
+ image_size: [1200,1600]
31
+ batch_size: 1
32
+ subset:
33
+ val:
34
+ image_size: [300,400]
35
+ batch_size: 1
36
+ subset: 1
37
+ max_viz_samples:
38
+ readjust:
39
+ center: [0.,0.,0.]
40
+ scale: 1.
neuralangelo-main/projects/neuralangelo/configs/dtu.yaml ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -----------------------------------------------------------------------------
2
+ # Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
3
+ #
4
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
5
+ # and proprietary rights in and to this software, related documentation
6
+ # and any modifications thereto. Any use, reproduction, disclosure or
7
+ # distribution of this software and related documentation without an express
8
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
9
+ # -----------------------------------------------------------------------------
10
+
11
+ _parent_: projects/neuralangelo/configs/base.yaml
12
+
13
+ model:
14
+ object:
15
+ sdf:
16
+ mlp:
17
+ inside_out: False
18
+ encoding:
19
+ coarse2fine:
20
+ init_active_level: 4
21
+ s_var:
22
+ init_val: 1.4
23
+ appear_embed:
24
+ enabled: False
25
+
26
+ data:
27
+ type: projects.neuralangelo.data
28
+ root: datasets/dtu/dtu_scan24
29
+ train:
30
+ image_size: [1200,1600]
31
+ batch_size: 1
32
+ subset:
33
+ val:
34
+ image_size: [300,400]
35
+ batch_size: 1
36
+ subset: 1
37
+ max_viz_samples: 16