Upload 72 files
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +1 -0
- neuralangelo-main/.DS_Store +0 -0
- neuralangelo-main/.gitignore +199 -0
- neuralangelo-main/.gitmodules +3 -0
- neuralangelo-main/.pre-commit-config.yaml +7 -0
- neuralangelo-main/DATA_PROCESSING.md +126 -0
- neuralangelo-main/LICENSE.md +55 -0
- neuralangelo-main/README.md +79 -0
- neuralangelo-main/assets/teaser.gif +3 -0
- neuralangelo-main/docker/Dockerfile-colmap +47 -0
- neuralangelo-main/docker/Dockerfile-neuralangelo +33 -0
- neuralangelo-main/imaginaire/config.py +223 -0
- neuralangelo-main/imaginaire/config_base.yaml +115 -0
- neuralangelo-main/imaginaire/datasets/base.py +603 -0
- neuralangelo-main/imaginaire/datasets/utils/dataloader.py +49 -0
- neuralangelo-main/imaginaire/datasets/utils/get_dataloader.py +181 -0
- neuralangelo-main/imaginaire/datasets/utils/sampler.py +117 -0
- neuralangelo-main/imaginaire/models/base.py +30 -0
- neuralangelo-main/imaginaire/models/utils/init_weight.py +91 -0
- neuralangelo-main/imaginaire/models/utils/model_average.py +113 -0
- neuralangelo-main/imaginaire/trainers/base.py +685 -0
- neuralangelo-main/imaginaire/trainers/utils/get_trainer.py +223 -0
- neuralangelo-main/imaginaire/trainers/utils/logging.py +72 -0
- neuralangelo-main/imaginaire/trainers/utils/meters.py +147 -0
- neuralangelo-main/imaginaire/utils/cudnn.py +30 -0
- neuralangelo-main/imaginaire/utils/distributed.py +153 -0
- neuralangelo-main/imaginaire/utils/gpu_affinity.py +78 -0
- neuralangelo-main/imaginaire/utils/misc.py +376 -0
- neuralangelo-main/imaginaire/utils/set_random_seed.py +36 -0
- neuralangelo-main/imaginaire/utils/termcolor.py +43 -0
- neuralangelo-main/imaginaire/utils/visualization.py +41 -0
- neuralangelo-main/neuralangelo.yaml +26 -0
- neuralangelo-main/projects/nerf/configs/ingp_blender.yaml +41 -0
- neuralangelo-main/projects/nerf/configs/nerf_blender.yaml +101 -0
- neuralangelo-main/projects/nerf/configs/nerf_llff.yaml +64 -0
- neuralangelo-main/projects/nerf/datasets/base.py +55 -0
- neuralangelo-main/projects/nerf/datasets/nerf_blender.py +114 -0
- neuralangelo-main/projects/nerf/datasets/nerf_llff.py +140 -0
- neuralangelo-main/projects/nerf/models/ingp.py +77 -0
- neuralangelo-main/projects/nerf/models/nerf.py +251 -0
- neuralangelo-main/projects/nerf/trainers/base.py +159 -0
- neuralangelo-main/projects/nerf/trainers/nerf.py +109 -0
- neuralangelo-main/projects/nerf/utils/camera.py +514 -0
- neuralangelo-main/projects/nerf/utils/misc.py +71 -0
- neuralangelo-main/projects/nerf/utils/nerf_util.py +270 -0
- neuralangelo-main/projects/nerf/utils/render.py +112 -0
- neuralangelo-main/projects/nerf/utils/visualize.py +120 -0
- neuralangelo-main/projects/neuralangelo/configs/base.yaml +147 -0
- neuralangelo-main/projects/neuralangelo/configs/custom/template.yaml +40 -0
- 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
|
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
|