Merge branch 'main' into gaussian-splatting

This commit is contained in:
Justin Kerr committed 2023-10-19 11:11:35 -07:00
commit 7480242e71
25 files changed
+362 -69

No files matched your search

+3 -1
View File
@@ -148,7 +148,9 @@ RUN git clone --branch v1.0 --recursive https://github.com/cvg/pyceres.git && \
cd ..
# Install pixel perfect sfm.
RUN git clone --branch v1.0 --recursive https://github.com/cvg/pixel-perfect-sfm.git && \
RUN git clone --recursive https://github.com/cvg/pixel-perfect-sfm.git && \
git reset --hard 40f7c1339328b2a0c7cf71f76623fb848e0c0357 && \
git clean -df && \
cd pixel-perfect-sfm && \
python3.10 -m pip install -e . && \
cd ..
+16 -15
View File
@@ -23,10 +23,10 @@
<p align="center">
<!-- pypi-strip -->
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://docs.nerf.studio/en/latest/_images/logo-dark.png">
<source media="(prefers-color-scheme: light)" srcset="https://docs.nerf.studio/en/latest/_images/logo.png">
<source media="(prefers-color-scheme: dark)" srcset="https://docs.nerf.studio/_images/logo-dark.png">
<source media="(prefers-color-scheme: light)" srcset="https://docs.nerf.studio/_images/logo.png">
<!-- /pypi-strip -->
<img alt="nerfstudio" src="https://docs.nerf.studio/en/latest/_images/logo.png" width="400">
<img alt="nerfstudio" src="https://docs.nerf.studio/_images/logo.png" width="400">
<!-- pypi-strip -->
</picture>
<!-- /pypi-strip -->
@@ -34,7 +34,7 @@
<!-- Use this for pypi package (and disable above). Hacky workaround -->
<!-- <p align="center">
<img alt="nerfstudio" src="https://docs.nerf.studio/en/latest/_images/logo.png" width="400">
<img alt="nerfstudio" src="https://docs.nerf.studio/_images/logo.png" width="400">
</p> -->
<p align="center"> A collaboration friendly studio for NeRFs </p>
@@ -70,7 +70,7 @@ Nerfstudio initially launched as an opensource project by Berkeley students in [
We are committed to providing learning resources to help you understand the basics of (if you're just getting started), and keep up-to-date with (if you're a seasoned veteran) all things NeRF. As researchers, we know just how hard it is to get onboarded with this next-gen technology. So we're here to help with tutorials, documentation, and more!
Have feature requests? Want to add your brand-spankin'-new NeRF model? Have a new dataset? **We welcome [contributions](https://docs.nerf.studio/en/latest/reference/contributing.html)!** Please do not hesitate to reach out to the nerfstudio team with any questions via [Discord](https://discord.gg/uMbNqcraFc).
Have feature requests? Want to add your brand-spankin'-new NeRF model? Have a new dataset? **We welcome [contributions](https://docs.nerf.studio/reference/contributing.html)!** Please do not hesitate to reach out to the nerfstudio team with any questions via [Discord](https://discord.gg/uMbNqcraFc).
Have feedback? We'd love for you to fill out our [Nerfstudio Feedback Form](https://forms.gle/sqN5phJN7LfQVwnP9) if you want to let us know who you are, why you are interested in Nerfstudio, or provide any feedback!
@@ -118,7 +118,7 @@ You must have an NVIDIA video card with CUDA installed on the system. This libra
### Create environment
Nerfstudio requires `python >= 3.8`. We recommend using conda to manage dependencies. Make sure to install [Conda](https://docs.conda.io/en/latest/miniconda.html) before proceeding.
Nerfstudio requires `python >= 3.8`. We recommend using conda to manage dependencies. Make sure to install [Conda](https://docs.conda.io/miniconda.html) before proceeding.
```bash
conda create --name nerfstudio -y python=3.8
@@ -250,6 +250,7 @@ Using an existing dataset is great, but likely you want to use your own data! We
| 📱 [Record3D](https://docs.nerf.studio/quickstart/custom_dataset.html#record3d-capture) | IOS with LiDAR | [Record3D app](https://record3d.app/) | 🐇 |
| 🖥 [Metashape](https://docs.nerf.studio/quickstart/custom_dataset.html#metashape) | Any | [Metashape](https://www.agisoft.com/) | 🐇 |
| 🖥 [RealityCapture](https://docs.nerf.studio/quickstart/custom_dataset.html#realitycapture) | Any | [RealityCapture](https://www.capturingreality.com/realitycapture) | 🐇 |
| 🖥 [ODM](https://docs.nerf.studio/quickstart/custom_dataset.html#ODM) | Any | [ODM](https://github.com/OpenDroneMap/ODM) | 🐇 |
| 🛠 [Custom](https://docs.nerf.studio/quickstart/data_conventions.html) | Any | Camera Poses | 🐇 |
## 5. Advanced Options
@@ -280,23 +281,23 @@ We support four different methods to track training progress, using the viewer[t
And that's it for getting started with the basics of nerfstudio.
If you're interested in learning more on how to create your own pipelines, develop with the viewer, run benchmarks, and more, please check out some of the quicklinks below or visit our [documentation](https://docs.nerf.studio/en/latest/) directly.
If you're interested in learning more on how to create your own pipelines, develop with the viewer, run benchmarks, and more, please check out some of the quicklinks below or visit our [documentation](https://docs.nerf.studio/) directly.
| Section | Description |
| -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| [Documentation](https://docs.nerf.studio/en/latest/) | Full API documentation and tutorials |
| [Documentation](https://docs.nerf.studio/) | Full API documentation and tutorials |
| [Viewer](https://viewer.nerf.studio/) | Home page for our web viewer |
| 🎒 **Educational** |
| [Model Descriptions](https://docs.nerf.studio/en/latest/nerfology/methods/index.html) | Description of all the models supported by nerfstudio and explanations of component parts. |
| [Component Descriptions](https://docs.nerf.studio/en/latest/nerfology/model_components/index.html) | Interactive notebooks that explain notable/commonly used modules in various models. |
| [Model Descriptions](https://docs.nerf.studio/nerfology/methods/index.html) | Description of all the models supported by nerfstudio and explanations of component parts. |
| [Component Descriptions](https://docs.nerf.studio/nerfology/model_components/index.html) | Interactive notebooks that explain notable/commonly used modules in various models. |
| 🏃 **Tutorials** |
| [Getting Started](https://docs.nerf.studio/en/latest/quickstart/installation.html) | A more in-depth guide on how to get started with nerfstudio from installation to contributing. |
| [Using the Viewer](https://docs.nerf.studio/en/latest/quickstart/viewer_quickstart.html) | A quick demo video on how to navigate the viewer. |
| [Getting Started](https://docs.nerf.studio/quickstart/installation.html) | A more in-depth guide on how to get started with nerfstudio from installation to contributing. |
| [Using the Viewer](https://docs.nerf.studio/quickstart/viewer_quickstart.html) | A quick demo video on how to navigate the viewer. |
| [Using Record3D](https://www.youtube.com/watch?v=XwKq7qDQCQk) | Demo video on how to run nerfstudio without using COLMAP. |
| 💻 **For Developers** |
| [Creating pipelines](https://docs.nerf.studio/en/latest/developer_guides/pipelines/index.html) | Learn how to easily build new neural rendering pipelines by using and/or implementing new modules. |
| [Creating datasets](https://docs.nerf.studio/en/latest/quickstart/custom_dataset.html) | Have a new dataset? Learn how to run it with nerfstudio. |
| [Contributing](https://docs.nerf.studio/en/latest/reference/contributing.html) | Walk-through for how you can start contributing now. |
| [Creating pipelines](https://docs.nerf.studio/developer_guides/pipelines/index.html) | Learn how to easily build new neural rendering pipelines by using and/or implementing new modules. |
| [Creating datasets](https://docs.nerf.studio/quickstart/custom_dataset.html) | Have a new dataset? Learn how to run it with nerfstudio. |
| [Contributing](https://docs.nerf.studio/reference/contributing.html) | Walk-through for how you can start contributing now. |
| 💖 **Community** |
| [Discord](https://discord.gg/uMbNqcraFc) | Join our community to discuss more. We would love to hear from you! |
| [Twitter](https://twitter.com/nerfstudioteam) | Follow us on Twitter @nerfstudioteam to see cool updates and announcements |
+1 -1
View File
@@ -34,7 +34,7 @@ Instant-NGP breaks NeRF training into 3 pillars and proposes improvements to eac
The core idea behind the improved sampling technique is that sampling over empty space should be skipped and sampling behind high density areas should also be skipped. This is achieved by maintaining a set of multiscale occupancy grids which coarsely mark empty and non-empty space. Occupancy is stored as a single bit, and a sample on a ray is skipped if its occupancy is too low. These occupancy grids are stored independently of the trainable encoding and are updated throughout training based on the updated density predictions. The authors find they can increase sampling speed by 10-100x compared to naive approaches.
Nerfstudio uses [NerfAcc](https://www.nerfacc.com/en/latest/index.html) as the sampling algorithm implementation. The details on NerfAcc's sampling and occupancy grid is discussed [here](https://www.nerfacc.com/en/stable/methodology/sampling.html#occupancy-grid-estimator).
Nerfstudio uses [NerfAcc](https://www.nerfacc.com/index.html) as the sampling algorithm implementation. The details on NerfAcc's sampling and occupancy grid is discussed [here](https://www.nerfacc.com/en/stable/methodology/sampling.html#occupancy-grid-estimator).
Another major bottleneck for NeRF's training speed has been querying the neural network. The authors of this work implement the network such that it runs entirely on a single CUDA kernel. The network is also shrunk down to be just 4 layers with 64 neurons in each layer. They show that their fully-fused neural network is 5-10x faster than a Tensorflow implementation.
+31 -2
View File
@@ -21,6 +21,7 @@ We Currently support the following custom data types:
| 📱 [Record3D](record3d) | IOS with LiDAR | [Record3D app](https://record3d.app/) | 🐇 |
| 🖥 [Metashape](metashape) | Any | [Metashape](https://www.agisoft.com/) | 🐇 |
| 🖥 [RealityCapture](realitycapture) | Any | [RealityCapture](https://www.capturingreality.com/realitycapture) | 🐇 |
| 🖥 [ODM](odm) | Any | [ODM](https://github.com/OpenDroneMap/ODM) | 🐇 |
(images_and_video)=
@@ -319,6 +320,34 @@ ns-process-data realitycapture --data {data directory} --csv {csv file} --output
ns-train nerfacto --data {output directory}
```
(odm)=
## ODM
All images/videos must be captured with the same camera.
1. Process a dataset using [ODM](https://github.com/OpenDroneMap/ODM#quickstart)
```bash
$ ls /path/to/dataset
images
odm_report
odm_orthophoto
...
```
2. Convert to nerfstudio format.
```bash
ns-process-data odm --data /path/to/dataset --output-dir {output directory}
```
4. Train!
```bash
ns-train nerfacto --data {output directory}
```
(360_data)=
## 360 Data (Equirectangular)
@@ -377,7 +406,7 @@ This outputs two 180 deg equirectangular renders horizontally stacked, one for e
</center>
### Setup instructions
To render for VR video it is essential to adjust the NeRF to have an approximately true-to-life real world scale (adjustable in the camera path) to ensure that the scene depth and IPD (distance between the eyes) is appropriate for the render to be viewable in VR. You can adjust the scene scale with the [Nerfstudio Blender Add-on](https://docs.nerf.studio/en/latest/extensions/blender_addon.html) by appropriately scaling a point cloud representation of the NeRF.
To render for VR video it is essential to adjust the NeRF to have an approximately true-to-life real world scale (adjustable in the camera path) to ensure that the scene depth and IPD (distance between the eyes) is appropriate for the render to be viewable in VR. You can adjust the scene scale with the [Nerfstudio Blender Add-on](https://docs.nerf.studio/extensions/blender_addon.html) by appropriately scaling a point cloud representation of the NeRF.
Results may be unviewable if the scale is not set appropriately. The IPD is set at 64mm by default but only is accurate when the NeRF scene is true to scale.
For good quality renders, it is recommended to render at high resolutions (For ODS: 4096x2048 per eye, or 2048x1024 per eye. For VR180: 4096x4096 per eye or 2048x2048 per eye). Render resolutions for a single eye are specified in the camera path. For VR180, resolutions must be in a 1:1 aspect ratio. For ODS, resolutions must be in a 2:1 aspect ratio. The final stacked render output will automatically be constructed (with aspect ratios for VR180 as 2:1 and ODS as 1:1).
@@ -388,7 +417,7 @@ If you are rendering an image sequence, it is recommended to render as png inste
:::
To render with the VR videos camera:
1. Use the [Nerfstudio Blender Add-on](https://docs.nerf.studio/en/latest/extensions/blender_addon.html) to set the scale of the NeRF scene and create the camera path
1. Use the [Nerfstudio Blender Add-on](https://docs.nerf.studio/extensions/blender_addon.html) to set the scale of the NeRF scene and create the camera path
- Export a point cloud representation of the NeRF
- Import the point cloud representation in Blender and enable the Nerfstudio Blender Add-on
- Create a reference object such as a cube which may be 1x1x1 meter. You could also create a cylinder and scale it to an appropriate height of a viewer.
+4 -4
View File
@@ -31,10 +31,10 @@ If all of the images share the same camera intrinsics, the values can be placed
"cy": 1000.0, // principal point y
"w": 3008, // image width
"h": 2000, // image height
"k1": 0.0312, // first radial distorial parameter, used by [OPENCV, OPENCV_FISHEYE]
"k2": 0.0051, // second radial distorial parameter, used by [OPENCV, OPENCV_FISHEYE]
"k3": 0.0006, // third radial distorial parameter, used by [OPENCV_FISHEYE]
"k4": 0.0001, // fourth radial distorial parameter, used by [OPENCV_FISHEYE]
"k1": 0.0312, // first radial distortion parameter, used by [OPENCV, OPENCV_FISHEYE]
"k2": 0.0051, // second radial distortion parameter, used by [OPENCV, OPENCV_FISHEYE]
"k3": 0.0006, // third radial distortion parameter, used by [OPENCV_FISHEYE]
"k4": 0.0001, // fourth radial distortion parameter, used by [OPENCV_FISHEYE]
"p1": -6.47e-5, // first tangential distortion parameter, used by [OPENCV]
"p2": -1.37e-7, // second tangential distortion parameter, used by [OPENCV]
"frames": // ... per-frame intrinsics and extrinsics parameters
+1 -1
View File
@@ -94,7 +94,7 @@ python nerfstudio/scripts/docs/build_docs.py
### Auto build
As you change or add models/components, the auto-generated [Reference API](https://docs.nerf.studio/en/latest/reference/api/index.html) may change.
As you change or add models/components, the auto-generated [Reference API](https://docs.nerf.studio/reference/api/index.html) may change.
If you want the code to build on save you can use [sphinx autobuild](https://github.com/executablebooks/sphinx-autobuild).
:::{admonition} Tip
+6 -6
View File
@@ -45,7 +45,7 @@ external_methods = []
external_methods.append(
ExternalMethod(
"""[bold yellow]Instruct-NeRF2NeRF[/bold yellow]
For more information visit: https://docs.nerf.studio/en/latest/nerfology/methods/in2n.html
For more information visit: https://docs.nerf.studio/nerfology/methods/in2n.html
To enable Instruct-NeRF2NeRF, you must install it first by running:
[grey]pip install git+https://github.com/ayaanzhaque/instruct-nerf2nerf[/grey]""",
@@ -62,7 +62,7 @@ To enable Instruct-NeRF2NeRF, you must install it first by running:
external_methods.append(
ExternalMethod(
"""[bold yellow]K-Planes[/bold yellow]
For more information visit https://docs.nerf.studio/en/latest/nerfology/methods/kplanes.html
For more information visit https://docs.nerf.studio/nerfology/methods/kplanes.html
To enable K-Planes, you must install it first by running:
[grey]pip install kplanes-nerfstudio[/grey]""",
@@ -78,7 +78,7 @@ To enable K-Planes, you must install it first by running:
external_methods.append(
ExternalMethod(
"""[bold yellow]LERF[/bold yellow]
For more information visit: https://docs.nerf.studio/en/latest/nerfology/methods/lerf.html
For more information visit: https://docs.nerf.studio/nerfology/methods/lerf.html
To enable LERF, you must install it first by running:
[grey]pip install git+https://github.com/kerrj/lerf[/grey]""",
@@ -95,7 +95,7 @@ To enable LERF, you must install it first by running:
external_methods.append(
ExternalMethod(
"""[bold yellow]Tetra-NeRF[/bold yellow]
For more information visit: https://docs.nerf.studio/en/latest/nerfology/methods/tetranerf.html
For more information visit: https://docs.nerf.studio/nerfology/methods/tetranerf.html
To enable Tetra-NeRF, you must install it first. Please follow the instructions here:
https://github.com/jkulhanek/tetra-nerf/blob/master/README.md#installation""",
@@ -110,7 +110,7 @@ To enable Tetra-NeRF, you must install it first. Please follow the instructions
external_methods.append(
ExternalMethod(
"""[bold yellow]NeRFPlayer[/bold yellow]
For more information visit: https://docs.nerf.studio/en/latest/nerfology/methods/nerfplayer.html
For more information visit: https://docs.nerf.studio/nerfology/methods/nerfplayer.html
To enable NeRFPlayer, you must install it first by running:
[grey]pip install git+https://github.com/lsongx/nerfplayer-nerfstudio[/grey]""",
@@ -125,7 +125,7 @@ To enable NeRFPlayer, you must install it first by running:
external_methods.append(
ExternalMethod(
"""[bold yellow]Volinga[/bold yellow]
For more information visit: https://docs.nerf.studio/en/latest/extensions/unreal_engine.html
For more information visit: https://docs.nerf.studio/extensions/unreal_engine.html
To enable Volinga, you must install it first by running:
[grey]pip install git+https://github.com/Volinga/volinga-model[/grey]""",
@@ -555,9 +555,13 @@ class VanillaDataManager(DataManager, Generic[TDataset]):
raise ValueError("No more eval images")
def get_train_rays_per_batch(self) -> int:
if self.train_pixel_sampler is not None:
return self.train_pixel_sampler.num_rays_per_batch
return self.config.train_num_rays_per_batch
def get_eval_rays_per_batch(self) -> int:
if self.eval_pixel_sampler is not None:
return self.eval_pixel_sampler.num_rays_per_batch
return self.config.eval_num_rays_per_batch
def get_datapath(self) -> Path:
@@ -303,10 +303,14 @@ class ParallelDataManager(DataManager, Generic[TDataset]):
def get_train_rays_per_batch(self) -> int:
"""Returns the number of rays per batch for training."""
if self.train_pixel_sampler is not None:
return self.train_pixel_sampler.num_rays_per_batch
return self.config.train_num_rays_per_batch
def get_eval_rays_per_batch(self) -> int:
"""Returns the number of rays per batch for evaluation."""
if self.eval_pixel_sampler is not None:
return self.eval_pixel_sampler.num_rays_per_batch
return self.config.eval_num_rays_per_batch
def get_datapath(self) -> Path:
+1 -1
View File
@@ -249,7 +249,7 @@ def generate_mesh_with_multires_marching_cubes(
)
verts = verts + np.array([x_min, y_min, z_min])
meshcrop = trimesh.Trimesh(verts, faces, normals)
meshcrop = trimesh.Trimesh(verts, faces, normals) # type: ignore
meshes.append(meshcrop)
combined_mesh: trimesh.Trimesh = trimesh.util.concatenate(meshes) # type: ignore
+15 -14
View File
@@ -29,7 +29,7 @@ from nerfstudio.utils.rich_utils import CONSOLE
try:
# TODO(1480) un-hide pycolmap import
import pycolmap
from hloc import (
from hloc import ( # type: ignore
extract_features,
match_features,
pairs_from_exhaustive,
@@ -38,6 +38,7 @@ try:
)
except ImportError:
_HAS_HLOC = False
else:
_HAS_HLOC = True
@@ -95,24 +96,24 @@ def run_hloc(
features = outputs / "features.h5"
matches = outputs / "matches.h5"
retrieval_conf = extract_features.confs["netvlad"]
feature_conf = extract_features.confs[feature_type]
matcher_conf = match_features.confs[matcher_type]
retrieval_conf = extract_features.confs["netvlad"] # type: ignore
feature_conf = extract_features.confs[feature_type] # type: ignore
matcher_conf = match_features.confs[matcher_type] # type: ignore
references = [p.relative_to(image_dir).as_posix() for p in image_dir.iterdir()]
extract_features.main(feature_conf, image_dir, image_list=references, feature_path=features)
extract_features.main(feature_conf, image_dir, image_list=references, feature_path=features) # type: ignore
if matching_method == "exhaustive":
pairs_from_exhaustive.main(sfm_pairs, image_list=references)
pairs_from_exhaustive.main(sfm_pairs, image_list=references) # type: ignore
else:
retrieval_path = extract_features.main(retrieval_conf, image_dir, outputs)
retrieval_path = extract_features.main(retrieval_conf, image_dir, outputs) # type: ignore
if num_matched >= len(references):
num_matched = len(references)
pairs_from_retrieval.main(retrieval_path, sfm_pairs, num_matched=num_matched)
match_features.main(matcher_conf, sfm_pairs, features=features, matches=matches)
pairs_from_retrieval.main(retrieval_path, sfm_pairs, num_matched=num_matched) # type: ignore
match_features.main(matcher_conf, sfm_pairs, features=features, matches=matches) # type: ignore
image_options = pycolmap.ImageReaderOptions(camera_model=camera_model.value)
image_options = pycolmap.ImageReaderOptions(camera_model=camera_model.value) # type: ignore
if refine_pixsfm:
sfm = PixSfM(
sfm = PixSfM( # type: ignore
conf={
"dense_features": {"use_cache": True},
"KA": {"dense_features": {"use_cache": True}, "max_kps_per_problem": 1000},
@@ -126,20 +127,20 @@ def run_hloc(
features,
matches,
image_list=references,
camera_mode=pycolmap.CameraMode.SINGLE,
camera_mode=pycolmap.CameraMode.SINGLE, # type: ignore
image_options=image_options,
verbose=verbose,
)
print("Refined", refined.summary())
else:
reconstruction.main(
reconstruction.main( # type: ignore
sfm_dir,
image_dir,
sfm_pairs,
features,
matches,
camera_mode=pycolmap.CameraMode.SINGLE,
camera_mode=pycolmap.CameraMode.SINGLE, # type: ignore
image_options=image_options,
verbose=verbose,
)
+148
View File
@@ -0,0 +1,148 @@
# Copyright 2022 the Regents of the University of California, Nerfstudio Team and contributors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Helper utils for processing ODM data into the nerfstudio format."""
import json
from pathlib import Path
from typing import Dict, List
import os
import sys
import math
import numpy as np
from nerfstudio.process_data.process_data_utils import CAMERA_MODELS
def rodrigues_vec_to_rotation_mat(rodrigues_vec: np.ndarray) -> np.ndarray:
theta = np.linalg.norm(rodrigues_vec)
if theta < sys.float_info.epsilon:
rotation_mat = np.eye(3, dtype=float)
else:
r = rodrigues_vec / theta
ident = np.eye(3, dtype=float)
r_rT = np.array(
[
[r[0] * r[0], r[0] * r[1], r[0] * r[2]],
[r[1] * r[0], r[1] * r[1], r[1] * r[2]],
[r[2] * r[0], r[2] * r[1], r[2] * r[2]],
]
)
r_cross = np.array([[0, -r[2], r[1]], [r[2], 0, -r[0]], [-r[1], r[0], 0]], dtype=float)
rotation_mat = math.cos(theta) * ident + (1 - math.cos(theta)) * r_rT + math.sin(theta) * r_cross
return rotation_mat
def cameras2nerfds(
image_filename_map: Dict[str, Path],
cameras_file: Path,
shots_file: Path,
output_dir: Path,
verbose: bool = False,
) -> List[str]:
"""Convert ODM cameras into a nerfstudio dataset.
Args:
image_filename_map: Mapping of original image filenames to their saved locations.
shots_file: Path to ODM's shots.geojson
output_dir: Path to the output directory.
verbose: Whether to print verbose output.
Returns:
Summary of the conversion.
"""
with open(cameras_file, "r", encoding="utf-8") as f:
cameras = json.loads(f.read())
with open(shots_file, "r", encoding="utf-8") as f:
shots = json.loads(f.read())
camera_ids = list(cameras.keys())
if len(camera_ids) > 1:
raise ValueError("Only one camera is supported")
camera_id = camera_ids[0]
camera = cameras[camera_id]
data = {}
if camera["projection_type"] in ["brown", "perspective"]:
data["camera_model"] = CAMERA_MODELS["perspective"].value
elif camera["projection_type"] in ["fisheye", "fisheye_opencv"]:
data["camera_model"] = CAMERA_MODELS["fisheye"].value
elif camera["projection_type"] in ["spherical", "equirectangular"]:
data["camera_model"] = CAMERA_MODELS["equirectangular"].value
else:
raise ValueError("Unsupported ODM camera model: " + data["camera_model"])
sensor_dict = {}
s = {"w": int(camera["width"]), "h": int(camera["height"])}
s["fl_x"] = camera.get("focal_x", camera.get("focal")) * max(s["w"], s["h"])
s["fl_y"] = camera.get("focal_y", camera.get("focal")) * max(s["w"], s["h"])
s["cx"] = camera["c_x"] + (s["w"] - 1.0) / 2.0
s["cy"] = camera["c_y"] + (s["h"] - 1.0) / 2.0
for p in ["k1", "k2", "p1", "p2", "k3"]:
if p in camera:
s[p] = camera[p]
sensor_dict[camera_id] = s
shots = shots["features"]
shots_dict = {}
for shot in shots:
props = shot["properties"]
filename = props["filename"]
rotation = rodrigues_vec_to_rotation_mat(np.array(props["rotation"]) * -1)
translation = np.array(props["translation"])
m = np.eye(4)
m[:3, :3] = rotation
m[:3, 3] = translation
name, ext = os.path.splitext(filename)
shots_dict[name] = m
frames = []
num_skipped = 0
for fname in shots_dict:
transform = shots_dict[fname]
if fname not in image_filename_map:
num_skipped += 1
continue
frame = {}
frame["file_path"] = image_filename_map[fname].as_posix()
frame.update(sensor_dict[camera_id])
transform = transform[[2, 0, 1, 3], :]
transform[:, 1:3] *= -1
frame["transform_matrix"] = transform.tolist()
frames.append(frame)
data["frames"] = frames
with open(output_dir / "transforms.json", "w", encoding="utf-8") as f:
json.dump(data, f, indent=4)
summary = []
if num_skipped == 1:
summary.append(f"{num_skipped} image skipped because it was missing its camera pose.")
if num_skipped > 1:
summary.append(f"{num_skipped} images were skipped because they were missing camera poses.")
summary.append(f"Final dataset is {len(data['frames'])} frames.")
return summary
+89
View File
@@ -32,6 +32,7 @@ from nerfstudio.process_data import (
process_data_utils,
realitycapture_utils,
record3d_utils,
odm_utils,
)
from nerfstudio.process_data.colmap_converter_to_nerfstudio_dataset import BaseConverterToNerfstudioDataset
from nerfstudio.process_data.images_to_nerfstudio_dataset import ImagesToNerfstudioDataset
@@ -387,6 +388,93 @@ class ProcessRealityCapture(BaseConverterToNerfstudioDataset, _NoDefaultProcessR
CONSOLE.rule()
@dataclass
class ProcessODM(BaseConverterToNerfstudioDataset):
"""Process ODM data into a nerfstudio dataset.
This script does the following:
1. Scales images to a specified size.
2. Converts ODM poses into the nerfstudio format.
"""
num_downscales: int = 3
"""Number of times to downscale the images. Downscales by 2 each time. For example a value of 3
will downscale the images by 2x, 4x, and 8x."""
max_dataset_size: int = 600
"""Max number of images to train on. If the dataset has more, images will be sampled approximately evenly. If -1,
use all images."""
def main(self) -> None:
"""Process images into a nerfstudio dataset."""
orig_images_dir = self.data / "images"
cameras_file = self.data / "cameras.json"
shots_file = self.data / "odm_report" / "shots.geojson"
if not shots_file.exists:
raise ValueError(f"shots file {shots_file} doesn't exist")
if not shots_file.exists:
raise ValueError(f"cameras file {cameras_file} doesn't exist")
if not orig_images_dir.exists:
raise ValueError(f"Images dir {orig_images_dir} doesn't exist")
if self.eval_data is not None:
raise ValueError("Cannot use eval_data since cameras were already aligned with ODM.")
self.output_dir.mkdir(parents=True, exist_ok=True)
image_dir = self.output_dir / "images"
image_dir.mkdir(parents=True, exist_ok=True)
summary_log = []
# Copy images to output directory
image_filenames, num_orig_images = process_data_utils.get_image_filenames(
orig_images_dir, self.max_dataset_size
)
copied_image_paths = process_data_utils.copy_images_list(
image_filenames,
image_dir=image_dir,
verbose=self.verbose,
num_downscales=self.num_downscales,
)
num_frames = len(copied_image_paths)
copied_image_paths = [Path("images/" + copied_image_path.name) for copied_image_path in copied_image_paths]
original_names = [image_path.stem for image_path in image_filenames]
image_filename_map = dict(zip(original_names, copied_image_paths))
if self.max_dataset_size > 0 and num_frames != num_orig_images:
summary_log.append(f"Started with {num_frames} images out of {num_orig_images} total")
summary_log.append(
"To change the size of the dataset add the argument [yellow]--max_dataset_size[/yellow] to "
f"larger than the current value ({self.max_dataset_size}), or -1 to use all images."
)
else:
summary_log.append(f"Started with {num_frames} images")
# Save json
if num_frames == 0:
CONSOLE.print("[bold red]No images found, exiting")
sys.exit(1)
summary_log.extend(
odm_utils.cameras2nerfds(
image_filename_map=image_filename_map,
cameras_file=cameras_file,
shots_file=shots_file,
output_dir=self.output_dir,
verbose=self.verbose,
)
)
CONSOLE.rule("[bold green]:tada: :tada: :tada: All DONE :tada: :tada: :tada:")
for summary in summary_log:
CONSOLE.print(summary, justify="center")
CONSOLE.rule()
Commands = Union[
Annotated[ImagesToNerfstudioDataset, tyro.conf.subcommand(name="images")],
Annotated[VideoToNerfstudioDataset, tyro.conf.subcommand(name="video")],
@@ -394,6 +482,7 @@ Commands = Union[
Annotated[ProcessMetashape, tyro.conf.subcommand(name="metashape")],
Annotated[ProcessRealityCapture, tyro.conf.subcommand(name="realitycapture")],
Annotated[ProcessRecord3D, tyro.conf.subcommand(name="record3d")],
Annotated[ProcessODM, tyro.conf.subcommand(name="odm")],
]
+10 -4
View File
@@ -18,12 +18,12 @@ render.py
"""
from __future__ import annotations
import gzip
import json
import os
import struct
import shutil
import struct
import sys
import gzip
from contextlib import ExitStack, contextmanager
from dataclasses import dataclass, field
from pathlib import Path
@@ -54,11 +54,14 @@ from nerfstudio.cameras.camera_paths import (
get_spiral_path,
)
from nerfstudio.cameras.cameras import Cameras, CameraType, RayBundle
from nerfstudio.data.datamanagers.base_datamanager import (
VanillaDataManager,
VanillaDataManagerConfig,
)
from nerfstudio.data.datasets.base_dataset import Dataset
from nerfstudio.data.datamanagers.base_datamanager import VanillaDataManager, VanillaDataManagerConfig
from nerfstudio.data.scene_box import OrientedBox
from nerfstudio.data.utils.dataloaders import FixedIndicesEvalDataloader
from nerfstudio.engine.trainer import TrainerConfig
from nerfstudio.data.scene_box import OrientedBox
from nerfstudio.model_components import renderers
from nerfstudio.pipelines.base_pipeline import Pipeline
from nerfstudio.utils import colormaps, install_checks
@@ -322,6 +325,9 @@ def get_crop_from_json(camera_json: Dict[str, Any]) -> Optional[CropData]:
center = camera_json["crop"]["crop_center"]
scale = camera_json["crop"]["crop_scale"]
rot = (0.0, 0.0, 0.0) if "crop_rot" not in camera_json["crop"] else tuple(camera_json["crop"]["crop_rot"])
assert len(center) == 3
assert len(scale) == 3
assert len(rot) == 3
return CropData(
background_color=torch.Tensor([bg_color["r"] / 255.0, bg_color["g"] / 255.0, bg_color["b"] / 255.0]),
obb=OrientedBox.from_params(center, rot, scale),
+1
View File
@@ -239,6 +239,7 @@ def get_cube(
if isinstance(side_length, float):
pts *= side_length / 2.0
else:
assert isinstance(side_length, torch.Tensor)
pts[0] *= side_length[0].item() / 2.0
pts[1] *= side_length[1].item() / 2.0
pts[2] *= side_length[2].item() / 2.0
@@ -62,7 +62,7 @@ export default function Banner() {
<div className="banner-logo">
<img
style={{ height: 30, margin: 'auto' }}
src="https://docs.nerf.studio/en/latest/_images/logo-dark.png"
src="https://docs.nerf.studio/_images/logo-dark.png"
alt="The favicon."
/>
</div>
@@ -100,7 +100,7 @@ export default function LandingModel(props: LandingModalProps) {
<center>
<img
style={{ height: 37, margin: 'auto' }}
src="https://docs.nerf.studio/en/latest/_images/logo-dark.png"
src="https://docs.nerf.studio/_images/logo-dark.png"
alt="The favicon."
/>
</center>
@@ -30,7 +30,7 @@ export default function ControlsModal() {
<center>
<img
style={{ height: 37, margin: 'auto' }}
src="https://docs.nerf.studio/en/latest/_images/logo-dark.png"
src="https://docs.nerf.studio/_images/logo-dark.png"
alt="The favicon."
/>
<img
+1 -1
View File
@@ -196,7 +196,7 @@ class ControlPanel:
self._split_output_render.set_options(new_options)
self._split_output_render.value = new_options[-1]
def add_element(self, e: ViewerElement, additional_tags: Tuple[str] = tuple()) -> None:
def add_element(self, e: ViewerElement, additional_tags: Tuple[str, ...] = tuple()) -> None:
"""Adds an element to the control panel
Args:
+4 -4
View File
@@ -198,8 +198,8 @@ class ViewerState:
self.viser_server.send_crop_params(
crop_enabled=self.control_panel.crop_viewport,
crop_bg_color=self.control_panel.background_color,
crop_scale=tuple(crop_scale.tolist()),
crop_center=tuple(crop_center.tolist()),
crop_scale=tuple(crop_scale.tolist()), # type: ignore
crop_center=tuple(crop_center.tolist()), # type: ignore
)
if self.camera_message is not None:
self.render_statemachine.action(RenderAction("rerender", self.camera_message))
@@ -258,8 +258,8 @@ class ViewerState:
scale = np.array(message.crop_scale)
crop_min = center - scale / 2.0
crop_max = center + scale / 2.0
self.control_panel.crop_min = tuple(crop_min.tolist())
self.control_panel.crop_max = tuple(crop_max.tolist())
self.control_panel.crop_min = tuple(crop_min.tolist()) # type: ignore
self.control_panel.crop_max = tuple(crop_max.tolist()) # type: ignore
def _handle_click_message(self, message: NerfstudioMessage) -> None:
"""Handle click message from viewer."""
+2 -2
View File
@@ -501,8 +501,8 @@ class MessageApi(abc.ABC):
"""
self._queue(
messages.SceneBoxMessage(
min=tuple(scene_box.aabb[0].tolist()),
max=tuple(scene_box.aabb[1].tolist()),
min=tuple(scene_box.aabb[0].tolist()), # type: ignore
max=tuple(scene_box.aabb[1].tolist()), # type: ignore
)
)
+5 -4
View File
@@ -19,6 +19,8 @@ from typing import Callable, DefaultDict, List, Tuple, get_args
import numpy as np
import torch
import viser.transforms as vtf
from viser import ViserServer
from nerfstudio.data.scene_box import OrientedBox
from nerfstudio.utils.colormaps import ColormapOptions, Colormaps
from nerfstudio.viewer_beta.viewer_elements import ( # ViewerButtonGroup,
@@ -32,7 +34,6 @@ from nerfstudio.viewer_beta.viewer_elements import ( # ViewerButtonGroup,
ViewerSlider,
ViewerVec3,
)
from viser import ViserServer
class ControlPanel:
@@ -147,7 +148,7 @@ class ControlPanel:
self._crop_handle = self.viser_server.add_transform_controls("Crop", depth_test=False, line_width=4.0)
def update_center(han):
self._crop_handle.position = tuple(p * self.viser_scale_ratio for p in han.value)
self._crop_handle.position = tuple(p * self.viser_scale_ratio for p in han.value) # type: ignore
self._crop_center = ViewerVec3(
"Crop Center",
@@ -175,7 +176,7 @@ class ControlPanel:
@self._crop_handle.on_update
def _update_crop_handle(han):
pos = self._crop_handle.position
self._crop_center.value = tuple(p / self.viser_scale_ratio for p in pos)
self._crop_center.value = tuple(p / self.viser_scale_ratio for p in pos) # type: ignore
rpy = vtf.SO3(self._crop_handle.wxyz).as_rpy_radians()
self._crop_rot.value = (float(rpy.roll), float(rpy.pitch), float(rpy.yaw))
@@ -281,7 +282,7 @@ class ControlPanel:
self._split_output_render.set_options(new_options)
self._split_output_render.value = new_options[-1]
def add_element(self, e: ViewerElement, additional_tags: Tuple[str] = tuple()) -> None:
def add_element(self, e: ViewerElement, additional_tags: Tuple[str, ...] = tuple()) -> None:
"""Adds an element to the control panel
Args:
+8 -1
View File
@@ -297,6 +297,13 @@ def populate_render_tab(
"""Update the aspect ratio for all cameras when the resolution changes."""
camera_path.update_aspect(resolution.value[0] / resolution.value[1])
camera_type = server.add_gui_dropdown(
"Camera Type",
("Perspective", "Fisheye", "Equirectangular"),
initial_value="Perspective",
hint="Camera model to render with.",
)
add_button = server.add_gui_button(
"Add keyframe",
icon=viser.Icon.PLUS,
@@ -584,7 +591,7 @@ def populate_render_tab(
}
)
json_data["keyframes"] = keyframes
json_data["camera_type"] = "perspective"
json_data["camera_type"] = camera_type.value.lower()
json_data["render_height"] = resolution.value[1]
json_data["render_width"] = resolution.value[0]
json_data["fps"] = framerate_slider.value
+3 -3
View File
@@ -124,8 +124,8 @@ class Viewer:
),
)
image = viser.theme.TitlebarImage(
image_url_light="https://docs.nerf.studio/en/latest/_static/imgs/logo.png",
image_url_dark="https://docs.nerf.studio/en/latest/_static/imgs/logo-dark.png",
image_url_light="https://docs.nerf.studio/_static/imgs/logo.png",
image_url_dark="https://docs.nerf.studio/_static/imgs/logo-dark.png",
image_alt="NerfStudio Logo",
href="https://docs.nerf.studio/",
)
@@ -328,7 +328,7 @@ class Viewer:
)
@camera_handle.on_click
def _(event: viser.ClickEvent[viser.CameraFrustumHandle]) -> None:
def _(event: viser.SceneNodePointerEvent[viser.CameraFrustumHandle]) -> None:
assert self.client is not None
with self.client.atomic():
self.client.camera.position = event.target.position
+2 -2
View File
@@ -18,7 +18,7 @@ dependencies = [
"av>=9.2.0",
"comet_ml>=3.33.8",
"cryptography>=38",
"tyro>=0.5.3",
"tyro>=0.5.10",
"gdown>=4.6.0",
"ninja>=1.10",
"h5py>=2.9.0",
@@ -91,7 +91,7 @@ dev = [
"diffusers==0.16.1",
"opencv-stubs==0.0.7",
"transformers==4.29.2",
"pyright==1.1.308",
"pyright==1.1.331",
]
# Documentation related packages