diff --git a/docs/conf.py b/docs/conf.py
index 177b8b410..d987c8e2c 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -16,7 +16,7 @@ import sys
sys.path.insert(0, os.path.abspath(".."))
import nerfactory.configs.base
-import nerfactory.dataloaders.base
+import nerfactory.datamanagers.base
# -- Project information -----------------------------------------------------
diff --git a/docs/reference/api/data.rst b/docs/reference/api/data.rst
index b3759b89c..fff8e624d 100644
--- a/docs/reference/api/data.rst
+++ b/docs/reference/api/data.rst
@@ -6,34 +6,34 @@ Data
Data Parsers
----------------
-.. automodule:: nerfactory.dataloaders.data_parsers
+.. automodule:: nerfactory.datamanagers.dataparsers
:members:
:show-inheritance:
-Image Dataset
+Datasets
----------------
-.. automodule:: nerfactory.dataloaders.image_dataset
+.. automodule:: nerfactory.datamanagers.datasets
:members:
:show-inheritance:
-Image Dataloader
+Dataloaders
----------------
-.. automodule:: nerfactory.dataloaders.image_dataloader
+.. automodule:: nerfactory.datamanagers.dataloaders
:members:
:show-inheritance:
-Pixel Sampler
+Pixel Samplers
----------------
-.. automodule:: nerfactory.dataloaders.pixel_sampler
+.. automodule:: nerfactory.datamanagers.pixel_sampler
:members:
:show-inheritance:
Structures
----------------
-.. automodule:: nerfactory.dataloaders.structs
+.. automodule:: nerfactory.datamanagers.structs
:members:
:show-inheritance:
\ No newline at end of file
diff --git a/docs/tutorials/pipelines/creating_pipelines.ipynb b/docs/tutorials/pipelines/creating_pipelines.ipynb
index b9fad4c24..294bcb953 100644
--- a/docs/tutorials/pipelines/creating_pipelines.ipynb
+++ b/docs/tutorials/pipelines/creating_pipelines.ipynb
@@ -72,14 +72,12 @@
"DataManager Components\n",
"
\n",
"Datasets Parsers:
\n",
- " Takes a data location and returns a DatasetInputs instance. A DatasetInputs will contain all data needed throughout the pipeline.\n",
+ " Takes a data location and returns a DatsetInputs instance. A DatsetInputs will contain all data needed throughout the pipeline.\n",
"
Vanilla Image DataParser\n",
- "Panoptic Image DataParser\n",
"
\n",
"Image Datasets:
\n",
" Takes image filename information and can return images.\n",
"Vanilla Image Dataset\n",
- "Panoptic Image Dataset\n",
"
\n",
"Image Dataloader:
\n",
" Takes an Image Dataset and samples images from it.\n",
@@ -119,8 +117,8 @@
"import torch\n",
"\n",
"from nerfactory.utils.config import DataManagerConfig, ModelConfig, PipelineConfig, ViewerConfig\n",
- "from nerfactory.dataloaders.base import DataManager\n",
- "from nerfactory.dataloaders.structs import DatasetInputs\n",
+ "from nerfactory.datamanagers.base import DataManager\n",
+ "from nerfactory.datamanagers.structs import DatasetInputs\n",
"from nerfactory.models.base import Model\n",
"from nerfactory.pipelines.base import Pipeline\n",
"from nerfactory.models.instant_ngp import NGPModel\n",
diff --git a/nerfactory/configs/base.py b/nerfactory/configs/base.py
index 11a4ae65a..93677be81 100644
--- a/nerfactory/configs/base.py
+++ b/nerfactory/configs/base.py
@@ -24,16 +24,18 @@ from typing import Any, Dict, List, Literal, Optional, Tuple, Type
import torch
from nerfactory.configs.utils import to_immutable_dict
-from nerfactory.dataloaders.base import VanillaDataManager
-from nerfactory.dataloaders.data_parsers import (
- Blender,
- DataParser,
- Friends,
- InstantNGP,
- Mipnerf360,
- Nerfactory,
- Record3D,
-)
+
+# data instances
+from nerfactory.datamanagers.base import VanillaDataManager
+from nerfactory.datamanagers.dataparsers.base import DataParser
+from nerfactory.datamanagers.dataparsers.blender_parser import Blender
+from nerfactory.datamanagers.dataparsers.friends_parser import Friends
+from nerfactory.datamanagers.dataparsers.instant_ngp_parser import InstantNGP
+from nerfactory.datamanagers.dataparsers.mipnerf_parser import Mipnerf360
+from nerfactory.datamanagers.dataparsers.nerfactory_parser import Nerfactory
+from nerfactory.datamanagers.dataparsers.record3d_parser import Record3D
+
+# model instances
from nerfactory.models.base import Model
from nerfactory.models.instant_ngp import NGPModel
from nerfactory.models.nerfw import NerfWModel
@@ -238,11 +240,10 @@ class VanillaDataManagerConfig(InstantiateConfig):
"""Configuration for data manager instantiation"""
_target: Type = VanillaDataManager
- train_dataset: DataParserConfig = BlenderDataParserConfig()
- image_dataset_type: str = "rgb"
+ train_dataparser: DataParserConfig = BlenderDataParserConfig()
train_num_rays_per_batch: int = 1024
train_num_images_to_sample_from: int = -1
- eval_dataset: Optional[InstantiateConfig] = None
+ eval_dataparser: Optional[InstantiateConfig] = None
eval_image_indices: Optional[Tuple[int, ...]] = (0,)
eval_num_rays_per_chunk: int = 4096
@@ -252,8 +253,7 @@ class FriendsDataManagerConfig(VanillaDataManagerConfig):
"""Friends data manager config"""
_target: Type = VanillaDataManager
- train_dataset: DataParserConfig = FriendsDataParserConfig()
- image_dataset_type: str = "panoptic"
+ train_dataparser: DataParserConfig = FriendsDataParserConfig()
# Model related configs
@@ -322,7 +322,7 @@ class PipelineConfig(InstantiateConfig):
"""Configuration for pipeline instantiation"""
_target: Type = Pipeline
- data_manager: VanillaDataManagerConfig = VanillaDataManagerConfig()
+ datamanager: VanillaDataManagerConfig = VanillaDataManagerConfig()
model: ModelConfig = ModelConfig()
diff --git a/nerfactory/configs/base_configs.py b/nerfactory/configs/base_configs.py
index ea6eff6a3..a708c1d5c 100644
--- a/nerfactory/configs/base_configs.py
+++ b/nerfactory/configs/base_configs.py
@@ -43,8 +43,8 @@ base_configs["instant_ngp"] = Config(
method_name="instant_ngp",
trainer=TrainerConfig(mixed_precision=True),
pipeline=PipelineConfig(
- data_manager=VanillaDataManagerConfig(
- train_dataset=BlenderDataParserConfig(),
+ datamanager=VanillaDataManagerConfig(
+ train_dataparser=BlenderDataParserConfig(),
train_num_rays_per_batch=8192,
eval_num_rays_per_chunk=8192,
),
@@ -63,8 +63,8 @@ base_configs["mipnerf_360"] = Config(
method_name="mipnerf_360",
trainer=TrainerConfig(steps_per_test=200),
pipeline=PipelineConfig(
- data_manager=VanillaDataManagerConfig(
- train_dataset=MipNerf360DataParserConfig(),
+ datamanager=VanillaDataManagerConfig(
+ train_dataparser=MipNerf360DataParserConfig(),
train_num_rays_per_batch=8192,
eval_num_rays_per_chunk=8192,
),
@@ -81,8 +81,8 @@ base_configs["mipnerf_360"] = Config(
base_configs["mipnerf"] = Config(
method_name="mipnerf",
pipeline=PipelineConfig(
- data_manager=VanillaDataManagerConfig(
- train_dataset=BlenderDataParserConfig(),
+ datamanager=VanillaDataManagerConfig(
+ train_dataparser=BlenderDataParserConfig(),
train_num_rays_per_batch=8192,
eval_num_rays_per_chunk=8192,
),
@@ -98,14 +98,14 @@ base_configs["mipnerf"] = Config(
base_configs["nerfw"] = Config(
experiment_name="friends_TBBT-big_living_room",
method_name="nerfw",
- pipeline=PipelineConfig(data_manager=FriendsDataManagerConfig(), model=NerfWModelConfig()),
+ pipeline=PipelineConfig(datamanager=FriendsDataManagerConfig(), model=NerfWModelConfig()),
)
base_configs["semantic_nerf"] = Config(
experiment_name="friends_TBBT-big_living_room",
method_name="semantic_nerf",
pipeline=PipelineConfig(
- data_manager=FriendsDataManagerConfig(),
+ datamanager=FriendsDataManagerConfig(),
model=ModelConfig(
_target=SemanticNerfModel,
loss_coefficients={"rgb_loss_coarse": 1.0, "rgb_loss_fine": 1.0, "semantic_loss_fine": 0.05},
@@ -118,8 +118,8 @@ base_configs["semantic_nerf"] = Config(
base_configs["vanilla_nerf"] = Config(
method_name="vanilla_nerf",
pipeline=PipelineConfig(
- data_manager=VanillaDataManagerConfig(
- train_dataset=BlenderDataParserConfig(),
+ datamanager=VanillaDataManagerConfig(
+ train_dataparser=BlenderDataParserConfig(),
),
model=ModelConfig(_target=NeRFModel),
),
@@ -129,8 +129,8 @@ base_configs["tensorf"] = Config(
method_name="tensorf",
trainer=TrainerConfig(mixed_precision=True),
pipeline=PipelineConfig(
- data_manager=VanillaDataManagerConfig(
- train_dataset=BlenderDataParserConfig(),
+ datamanager=VanillaDataManagerConfig(
+ train_dataparser=BlenderDataParserConfig(),
),
model=TensoRFModelConfig(),
),
diff --git a/nerfactory/dataloaders/data_parsers.py b/nerfactory/dataloaders/data_parsers.py
deleted file mode 100644
index f44fdccfb..000000000
--- a/nerfactory/dataloaders/data_parsers.py
+++ /dev/null
@@ -1,740 +0,0 @@
-# Copyright 2022 The Plenoptix Team. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""A set of standard datasets."""
-
-from __future__ import annotations
-
-import dataclasses
-import logging
-from abc import abstractmethod
-from dataclasses import dataclass
-from pathlib import Path
-
-import imageio
-import numpy as np
-import torch
-from scipy.spatial.transform import Rotation
-
-from nerfactory.cameras import utils as camera_utils
-from nerfactory.cameras.cameras import Cameras, CameraType
-from nerfactory.configs import base as cfg
-from nerfactory.dataloaders.structs import (
- DatasetInputs,
- PointCloud,
- SceneBounds,
- Semantics,
-)
-from nerfactory.utils.colmap_utils import (
- read_cameras_binary,
- read_images_binary,
- read_points3d_binary,
-)
-from nerfactory.utils.colors import get_color
-from nerfactory.utils.io import (
- get_absolute_path,
- load_from_json,
- load_from_pkl,
- write_to_pkl,
-)
-from nerfactory.utils.misc import get_hash_str_from_dict
-
-
-@dataclass
-class DataParser:
- """A dataset."""
-
- def __init__(self, config: cfg.DataParserConfig):
- super().__init__()
- self.config = config
-
- @abstractmethod
- def _generate_dataset_inputs(self, split: str = "train") -> DatasetInputs:
- """Returns the dataset inputs for the given split.
-
- Args:
- split: Which dataset split to generate.
-
- Returns:
- DatasetInputs
- """
-
- def get_dataset_inputs(self, split: str = "train", use_preprocessing_cache: bool = False) -> DatasetInputs:
- """Returns the dataset inputs for the given split.
-
- Args:
- split: Which dataset split to generate.
- use_preprocessing_cache: Whether to use the cached dataset inputs. Defaults to False.
-
- Returns:
- DatasetInputs
- """
- if use_preprocessing_cache:
- dataset_inputs = self._get_dataset_inputs_from_cache(split)
- else:
- dataset_inputs = self._generate_dataset_inputs(split)
- return dataset_inputs
-
- def _get_cache_filename(self, split: str) -> Path:
- """Creates a cache filename from the dataset inputs arguments.
-
- Args:
- split: Which dataset split to generate filename for.
-
- Returns:
- filename for cache.
- """
- dataset_config_hash = get_hash_str_from_dict(dataclasses.asdict(self))
- dataset_config_hash_filename = get_absolute_path(
- f"cache/dataset_inputs/{dataset_config_hash}-{split}.pkl"
- ).parent.mkdir()
- assert dataset_config_hash_filename is not None, "dataset hash is None"
- return dataset_config_hash_filename
-
- def save_dataset_inputs_to_cache(self, split: str):
- """Saves the dataset inputs to cache.
-
- Args:
- split: Which dataset split to save.
- """
- dataset_inputs = self.get_dataset_inputs(split=split)
- dataset_config_hash_filename = self._get_cache_filename(split)
- write_to_pkl(dataset_config_hash_filename, dataset_inputs)
-
- def _get_dataset_inputs_from_cache(self, split: str) -> DatasetInputs:
- """Loads the dataset inputs from cache. If the cache does not exist, it will be created.
-
- Args:
- split: Which dataset split to load.
- """
- dataset_config_hash_filename = self._get_cache_filename(split)
- if dataset_config_hash_filename.exists():
- logging.info("Loading dataset from cache.")
- dataset_inputs = load_from_pkl(dataset_config_hash_filename)
- else:
- logging.info("Cache file not found. Generating and saving dataset to cache.")
- dataset_inputs = self._generate_dataset_inputs(split=split)
- self.save_dataset_inputs_to_cache(split)
- return dataset_inputs
-
-
-@dataclass
-class Nerfactory(DataParser):
- """Nerfactory Dataset
-
- Args:
- data_directory: Location of data
- scale_factor: How much to scale the camera origins by.
- downscale_factor: How much to downscale images.
- scene_scale: How much to scale the scene.
- orientation_method: The method to use for orientation. Either "pca" or "up".
- """
-
- config: cfg.NerfactoryDataParserConfig
-
- def _generate_dataset_inputs(self, split="train"):
-
- abs_dir = get_absolute_path(self.config.data_directory)
-
- meta = load_from_json(abs_dir / "transforms.json")
- image_filenames = []
- poses = []
- num_skipped_image_filenames = 0
- for frame in meta["frames"]:
- fname = abs_dir / Path(frame["file_path"])
- if self.config.downscale_factor > 1:
- fname = abs_dir / f"images_{self.config.downscale_factor}" / Path(frame["file_path"]).name
- else:
- fname = abs_dir / Path(frame["file_path"])
- if not fname:
- num_skipped_image_filenames += 1
- else:
- image_filenames.append(fname)
- poses.append(np.array(frame["transform_matrix"]))
- if num_skipped_image_filenames >= 0:
- logging.info("Skipping %s files in dataset split %s.", num_skipped_image_filenames, split)
- assert (
- len(image_filenames) != 0
- ), """
- No image files found.
- You should check the file_paths in the transforms.json file to make sure they are correct.
- """
- poses = torch.from_numpy(np.array(poses).astype(np.float32))
- poses = camera_utils.auto_orient_poses(poses, method=self.config.orientation_method)
-
- # Scale poses
- scale_factor = 1.0 / torch.max(torch.abs(poses[:, :3, 3]))
- poses[:, :3, 3] *= scale_factor
-
- distortion_params = camera_utils.get_distortion_params(
- k1=float(meta["k1"]), k2=float(meta["k2"]), p1=float(meta["p1"]), p2=float(meta["p2"])
- )
-
- # in x,y,z order
- # assumes that the scene is centered at the origin
- aabb_scale = self.config.scene_scale
- scene_bounds = SceneBounds(
- aabb=torch.tensor(
- [[-aabb_scale, -aabb_scale, -aabb_scale], [aabb_scale, aabb_scale, aabb_scale]], dtype=torch.float32
- )
- )
-
- cameras = Cameras(
- fx=float(meta["fl_x"]),
- fy=float(meta["fl_y"]),
- cx=float(meta["cx"]),
- cy=float(meta["cy"]),
- distortion_params=distortion_params,
- height=int(meta["h"]),
- width=int(meta["w"]),
- camera_to_worlds=poses[:, :3, :4],
- camera_type=CameraType.PERSPECTIVE,
- )
-
- cameras.rescale_output_resolution(scaling_factor=1.0 / self.config.downscale_factor)
-
- dataset_inputs = DatasetInputs(
- image_filenames=image_filenames,
- cameras=cameras,
- scene_bounds=scene_bounds,
- )
- return dataset_inputs
-
-
-@dataclass
-class Blender(DataParser):
- """Blender Dataset
- Some of this code comes from https://github.com/yenchenlin/nerf-pytorch/blob/master/load_blender.py#L37.
-
- Args:
- data_directory: Location of data
- alpha_color: Sets transparent regions to specified color, otherwise black.
- scale_factor: How much to scale the camera origins by.
- """
-
- def __init__(self, config: cfg.BlenderDataParserConfig):
- super().__init__(config=config)
- self.data_directory: Path = config.data_directory
- self.scale_factor: float = config.scale_factor
- self.alpha_color = config.alpha_color
-
- def _generate_dataset_inputs(self, split="train"):
- if self.alpha_color is not None:
- alpha_color_tensor = get_color(self.alpha_color)
- else:
- alpha_color_tensor = None
-
- abs_dir = get_absolute_path(self.data_directory)
- meta = load_from_json(abs_dir / f"transforms_{split}.json")
- image_filenames = []
- poses = []
- for frame in meta["frames"]:
- fname = abs_dir / Path(frame["file_path"].replace("./", "") + ".png")
- image_filenames.append(fname)
- poses.append(np.array(frame["transform_matrix"]))
- poses = np.array(poses).astype(np.float32)
-
- img_0 = imageio.imread(image_filenames[0])
- image_height, image_width = img_0.shape[:2]
- camera_angle_x = float(meta["camera_angle_x"])
- focal_length = 0.5 * image_width / np.tan(0.5 * camera_angle_x)
-
- cx = image_width / 2.0
- cy = image_height / 2.0
- camera_to_world = torch.from_numpy(poses[:, :3]) # camera to world transform
-
- # in x,y,z order
- camera_to_world[..., 3] *= self.scale_factor
- scene_bounds = SceneBounds(aabb=torch.tensor([[-1.5, -1.5, -1.5], [1.5, 1.5, 1.5]], dtype=torch.float32))
-
- cameras = Cameras(
- camera_to_worlds=camera_to_world,
- fx=focal_length,
- fy=focal_length,
- cx=cx,
- cy=cy,
- camera_type=CameraType.PERSPECTIVE,
- )
-
- dataset_inputs = DatasetInputs(
- image_filenames=image_filenames,
- cameras=cameras,
- alpha_color=alpha_color_tensor,
- scene_bounds=scene_bounds,
- )
-
- return dataset_inputs
-
-
-@dataclass
-class InstantNGP(DataParser):
- """Instant NGP Dataset
-
- Args:
- data_directory: Location of data
- scale_factor: How much to scale the camera origins by.
- scene_scale: How much to scale the scene. Defaults to 0.33
- """
-
- config: cfg.InstantNGPDataParserConfig
-
- def _generate_dataset_inputs(self, split="train"):
-
- abs_dir = get_absolute_path(self.config.data_directory)
-
- meta = load_from_json(abs_dir / "transforms.json")
- image_filenames = []
- poses = []
- num_skipped_image_filenames = 0
- for frame in meta["frames"]:
- fname = abs_dir / Path(frame["file_path"])
- if not fname:
- num_skipped_image_filenames += 1
- else:
- image_filenames.append(fname)
- poses.append(np.array(frame["transform_matrix"]))
- if num_skipped_image_filenames >= 0:
- logging.info("Skipping %s files in dataset split %s.", num_skipped_image_filenames, split)
- assert (
- len(image_filenames) != 0
- ), """
- No image files found.
- You should check the file_paths in the transforms.json file to make sure they are correct.
- """
- poses = np.array(poses).astype(np.float32)
- poses[:3, 3] *= self.config.scene_scale
-
- camera_to_world = torch.from_numpy(poses[:, :3]) # camera to world transform
-
- distortion_params = camera_utils.get_distortion_params(
- k1=float(meta["k1"]), k2=float(meta["k2"]), p1=float(meta["p1"]), p2=float(meta["p2"])
- )
-
- # in x,y,z order
- # assumes that the scene is centered at the origin
- aabb_scale = meta["aabb_scale"]
- scene_bounds = SceneBounds(
- aabb=torch.tensor(
- [[-aabb_scale, -aabb_scale, -aabb_scale], [aabb_scale, aabb_scale, aabb_scale]], dtype=torch.float32
- )
- )
-
- cameras = Cameras(
- fx=float(meta["fl_x"]),
- fy=float(meta["fl_y"]),
- cx=float(meta["cx"]),
- cy=float(meta["cy"]),
- distortion_params=distortion_params,
- height=int(meta["h"]),
- width=int(meta["w"]),
- camera_to_worlds=camera_to_world,
- camera_type=CameraType.PERSPECTIVE,
- )
-
- # TODO(ethan): add alpha background color
- dataset_inputs = DatasetInputs(
- image_filenames=image_filenames,
- cameras=cameras,
- scene_bounds=scene_bounds,
- )
-
- return dataset_inputs
-
-
-@dataclass
-class Mipnerf360(DataParser):
- """MipNeRF 360 Dataset
-
- Args:
- data_directory: Location of data
- downscale_factor: How much to downscale images. Defaults to 1.
- val_skip: 1/val_skip images to use for validation. Defaults to 8.
- auto_scale: Scale based on pose bounds. Defaults to True.
- aabb_scale: Scene scale, Defaults to 1.0.
- """
-
- config: cfg.MipNerf360DataParserConfig
-
- @classmethod
- def normalize_orientation(cls, poses: np.ndarray):
- """Set the _up_ direction to be in the positive Y direction.
-
- Args:
- poses: Numpy array of poses.
- """
- poses_orig = poses.copy()
- bottom = np.reshape([0, 0, 0, 1.0], [1, 4])
- center = poses[:, :3, 3].mean(0)
- vec2 = poses[:, :3, 2].sum(0) / np.linalg.norm(poses[:, :3, 2].sum(0))
- up = poses[:, :3, 1].sum(0)
- vec0 = np.cross(up, vec2) / np.linalg.norm(np.cross(up, vec2))
- vec1 = np.cross(vec2, vec0) / np.linalg.norm(np.cross(vec2, vec0))
- c2w = np.stack([vec0, vec1, vec2, center], -1) # [3, 4]
- c2w = np.concatenate([c2w[:3, :4], bottom], -2) # [4, 4]
- bottom = np.tile(np.reshape(bottom, [1, 1, 4]), [poses.shape[0], 1, 1]) # [BS, 1, 4]
- poses = np.concatenate([poses[:, :3, :4], bottom], -2) # [BS, 4, 4]
- poses = np.linalg.inv(c2w) @ poses
- poses_orig[:, :3, :4] = poses[:, :3, :4]
- return poses_orig
-
- def _generate_dataset_inputs(self, split="train"):
- abs_dir = get_absolute_path(self.config.data_directory)
- image_dir = "images"
- if self.config.downscale_factor > 1:
- image_dir += f"_{self.config.downscale_factor}"
- image_dir = abs_dir / image_dir
- if not image_dir.exists():
- raise ValueError(f"Image directory {image_dir} doesn't exist")
-
- valid_formats = [".jpg", ".png"]
- image_filenames = []
- for f in image_dir.iterdir():
- ext = f.suffix
- if ext.lower() not in valid_formats:
- continue
- image_filenames.append(image_dir / f)
- image_filenames = sorted(image_filenames)
- num_images = len(image_filenames)
-
- poses_data = np.load(abs_dir / "poses_bounds.npy")
- poses = poses_data[:, :-2].reshape([-1, 3, 5]).astype(np.float32)
- bounds = poses_data[:, -2:].transpose([1, 0])
-
- if num_images != poses.shape[0]:
- raise RuntimeError(f"Different number of images ({num_images}), and poses ({poses.shape[0]})")
-
- idx_test = np.arange(num_images)[:: self.config.val_skip]
- idx_train = np.array([i for i in np.arange(num_images) if i not in idx_test])
- idx = idx_train if split == "train" else idx_test
-
- image_filenames = np.array(image_filenames)[idx]
- poses = poses[idx]
-
- img_0 = imageio.imread(image_filenames[0])
- image_height, image_width = img_0.shape[:2]
-
- poses[:, :2, 4] = np.array([image_height, image_width])
- poses[:, 2, 4] = poses[:, 2, 4]
-
- # Reorder pose to match our convention
- poses = np.concatenate([poses[:, :, 1:2], -poses[:, :, 0:1], poses[:, :, 2:]], axis=-1)
-
- # Center poses and rotate. (Compute up from average of all poses)
- poses = self.normalize_orientation(poses)
-
- # Scale factor used in mipnerf
- if self.config.auto_scale:
- scale_factor = 1 / (np.min(bounds) * 0.75)
- poses[:, :3, 3] *= scale_factor
- bounds *= scale_factor
-
- # Center poses
- poses[:, :3, 3] = poses[:, :3, 3] - np.mean(poses[:, :3, :], axis=0)[:, 3]
-
- focal_length = poses[0, -1, -1]
-
- cx = image_width / 2.0
- cy = image_height / 2.0
- camera_to_world = torch.from_numpy(poses[:, :3, :4]) # camera to world transform
- num_cameras = len(image_filenames)
- num_intrinsics_params = 3
- intrinsics = torch.ones((num_cameras, num_intrinsics_params), dtype=torch.float32)
- intrinsics *= torch.tensor([cx, cy, focal_length])
-
- aabb = torch.tensor([[-4, -4, -4], [4, 4, 4]], dtype=torch.float32) * self.config.aabb_scale
- scene_bounds = SceneBounds(aabb=aabb)
-
- cameras = Cameras(
- fx=focal_length,
- fy=focal_length,
- cx=cx,
- cy=cy,
- camera_to_worlds=camera_to_world,
- camera_type=CameraType.PERSPECTIVE,
- )
-
- cameras.rescale_output_resolution(scaling_factor=1.0 / self.config.downscale_factor)
-
- dataset_inputs = DatasetInputs(
- image_filenames=image_filenames,
- cameras=cameras,
- scene_bounds=scene_bounds,
- )
-
- return dataset_inputs
-
-
-@dataclass
-class Record3D(DataParser):
- """Record3D Dataset
-
- Args:
- data_directory: Location of data
- val_skip: 1/val_skip images to use for validation. Defaults to 8.
- aabb_scale: Scene scale, Defaults to 4.0.
- max_dataset_size: Max number of images to train on. If the dataset has
- more, images will be sampled approximately evenly. Defaults to 150.
- """
-
- config: cfg.Record3DDataParserConfig
-
- def _generate_dataset_inputs(self, split: str = "train") -> DatasetInputs:
- abs_dir = get_absolute_path(self.config.data_directory)
-
- image_dir = abs_dir / "rgb"
-
- if not image_dir.exists():
- raise ValueError(f"Image directory {image_dir} doesn't exist")
-
- image_filenames = []
- for f in image_dir.iterdir():
- if f.stem.isdigit(): # removes possible duplicate images (for example, 123(3).jpg)
- image_filenames.append(image_dir / f)
-
- image_filenames = sorted(image_filenames, key=lambda fn: int(fn.stem))
- image_filenames = np.array(image_filenames)
- num_images = len(image_filenames)
-
- metadata_path = abs_dir / "metadata.json"
- metadata_dict = load_from_json(metadata_path)
-
- poses_data = np.array(metadata_dict["poses"])
- # (N, 3, 4)
- poses = np.concatenate(
- [Rotation.from_quat(poses_data[:, :4]).as_matrix(), poses_data[:, 4:, None]],
- axis=-1,
- ).astype(np.float32)
-
- if num_images > self.config.max_dataset_size:
- # Evenly select max_dataset_size images from dataset, including first
- # and last indices.
- idx = np.round(np.linspace(0, num_images - 1, self.config.max_dataset_size)).astype(int)
- poses = poses[idx]
- image_filenames = image_filenames[idx]
- num_images = len(image_filenames)
-
- # Normalization similar to Mipnerf360
- poses = Mipnerf360.normalize_orientation(poses)
-
- bottom = np.reshape([0, 0, 0, 1.0], [1, 4])
- bottom = np.tile(np.reshape(bottom, [1, 1, 4]), [poses.shape[0], 1, 1])
- poses = np.concatenate([poses[:, :3, :4], bottom], -2).astype(np.float32)
-
- rotation_matrix = np.array(
- [
- [1.0, 0.0, 0.0, 0.0],
- [0.0, 0.0, -1.0, 0.0],
- [0.0, 1.0, 0.0, 0.0],
- [0.0, 0.0, 0.0, 1.0],
- ],
- dtype=np.float32,
- )
- poses = rotation_matrix @ poses
-
- idx_test = np.arange(num_images)[:: self.config.val_skip]
- idx_train = np.array([i for i in np.arange(num_images) if i not in idx_test])
- idx = idx_train if split == "train" else idx_test
- if num_images != poses.shape[0]:
- raise RuntimeError(f"Different number of images ({num_images}), and poses ({poses.shape[0]})")
-
- image_filenames = image_filenames[idx]
- poses = poses[idx]
-
- # Centering poses
- poses[:, :3, 3] = poses[:, :3, 3] - np.mean(poses[:, :3, 3], axis=0)
-
- camera_to_world = torch.from_numpy(poses[:, :3, :4]) # camera to world transform
-
- # Camera intrinsics
- K = np.array(metadata_dict["K"]).reshape((3, 3)).T
- focal_length = K[0, 0]
-
- H = metadata_dict["h"]
- W = metadata_dict["w"]
-
- # TODO(akristoffersen): The metadata dict comes with principle points,
- # but caused errors in image coord indexing. Should update once that is fixed.
- cx, cy = W / 2, H / 2
-
- num_cameras = len(image_filenames)
- num_intrinsics_params = 3
- intrinsics = torch.ones((num_cameras, num_intrinsics_params), dtype=torch.float32)
- intrinsics *= torch.tensor([cx, cy, focal_length])
-
- # scene_bounds = SceneBounds.from_camera_poses(camera_to_world, self.config.aabb_scale)
- aabb = torch.tensor([[-1, -1, -1], [1, 1, 1]], dtype=torch.float32) * self.config.aabb_scale
- scene_bounds = SceneBounds(aabb=aabb)
-
- cameras = Cameras(
- fx=focal_length,
- fy=focal_length,
- cx=cx,
- cy=cy,
- camera_to_worlds=camera_to_world,
- camera_type=CameraType.PERSPECTIVE,
- )
-
- dataset_inputs = DatasetInputs(
- image_filenames=image_filenames,
- cameras=cameras,
- scene_bounds=scene_bounds,
- )
-
- return dataset_inputs
-
-
-@dataclass
-class Friends(DataParser):
- """Friends Dataset
-
- Args:
- data_directory: Location of data
- include_semantics: whether or not to include the semantics. Defaults to False.
- include_point_cloud: whether or not to include the point cloud. Defaults to False.
- """
-
- data_directory: Path
- include_semantics: bool = True
- include_point_cloud: bool = False
-
- @classmethod
- def _get_aabb_and_transform(cls, basedir):
- """Returns the aabb and pointcloud transform from the threejs.json file."""
- filename = basedir / "threejs.json"
- assert filename.exists()
- data = load_from_json(filename)
-
- # point cloud transformation
- transposed_point_cloud_transform = np.array(data["object"]["children"][0]["matrix"]).reshape(4, 4).T
- assert transposed_point_cloud_transform[3, 3] == 1.0
-
- # bbox transformation
- bbox_transform = np.array(data["object"]["children"][1]["matrix"]).reshape(4, 4).T
- w, h, d = data["geometries"][1]["width"], data["geometries"][1]["height"], data["geometries"][1]["depth"]
- temp = np.array([w, h, d]) / 2.0
- bbox = np.array([-temp, temp])
- bbox = np.concatenate([bbox, np.ones_like(bbox[:, 0:1])], axis=1)
- bbox = (bbox_transform @ bbox.T).T[:, 0:3]
-
- aabb = bbox # rename to aabb because it's an axis-aligned bounding box
- return torch.from_numpy(aabb).float(), torch.from_numpy(transposed_point_cloud_transform).float()
-
- def _generate_dataset_inputs(self, split="train"): # pylint: disable=too-many-statements
-
- abs_dir = get_absolute_path(self.data_directory)
-
- images_data = read_images_binary(abs_dir / "colmap" / "images.bin")
- # `image_path` is only the end of the filename, including the extension e.g., `.jpg`
- image_paths = sorted((abs_dir / "images").iterdir())
-
- image_path_to_image_id = {}
- image_id_to_image_path = {}
- for v in images_data.values():
- image_path_to_image_id[v.name] = v.id
- image_id_to_image_path[v.id] = v.name
- # TODO: handle the splits differently
- image_filenames = [abs_dir / "images" / image_path for image_path in image_paths]
-
- # -- set the bounding box ---
- aabb, transposed_point_cloud_transform = self._get_aabb_and_transform(abs_dir)
- scene_bounds_original = SceneBounds(aabb=aabb)
- # for shifting and rescale accoding to scene bounds
- box_center = scene_bounds_original.get_center()
- box_scale_factor = 5.0 / scene_bounds_original.get_diagonal_length() # the target diagonal length
- scene_bounds = scene_bounds_original.get_centered_and_scaled_scene_bounds(box_scale_factor)
-
- # --- intrinsics ---
- cameras_data = read_cameras_binary(abs_dir / "colmap" / "cameras.bin")
- focal_lengths = []
- for image_path in image_paths:
- cam = cameras_data[image_path_to_image_id[image_path]]
- assert len(cam.params) == 3
- focal_lengths.append(cam.params[0]) # f (fx and fy)
- focal_lengths = torch.stack(focal_lengths, dim=0)
-
- cam = cameras_data[image_path_to_image_id[image_paths[0]]]
- cx = cam.params[1] # cx
- cy = cam.params[2] # cy
-
- # --- camera_to_world (extrinsics) ---
- camera_to_world = []
- bottom_row = np.array([0, 0, 0, 1.0]).reshape(1, 4)
- for image_path in image_paths:
- image_data = images_data[image_path_to_image_id[image_path]]
- rot = image_data.qvec2rotmat()
- trans = image_data.tvec.reshape(3, 1)
- c2w = np.concatenate([np.concatenate([rot, trans], 1), bottom_row], 0)
- camera_to_world.append(c2w)
- camera_to_world = torch.tensor(np.array(camera_to_world)).float()
- camera_to_world = torch.inverse(camera_to_world)
- camera_to_world[..., 1:3] *= -1
- camera_to_world = transposed_point_cloud_transform @ camera_to_world
- camera_to_world = camera_to_world[:, :3]
- camera_to_world[..., 3] = (camera_to_world[..., 3] - box_center) * box_scale_factor # center and rescale
-
- # --- semantics ---
- semantics = None
- if self.include_semantics:
- thing_filenames = [
- Path(str(image_filename).replace("/images/", "/segmentations/thing/").replace(".jpg", ".png"))
- for image_filename in image_filenames
- ]
- stuff_filenames = [
- Path(str(image_filename).replace("/images/", "/segmentations/stuff/").replace(".jpg", ".png"))
- for image_filename in image_filenames
- ]
- panoptic_classes = load_from_json(abs_dir / "panoptic_classes.json")
- stuff_classes = panoptic_classes["stuff"]
- stuff_colors = torch.tensor(panoptic_classes["stuff_colors"], dtype=torch.float32) / 255.0
- thing_classes = panoptic_classes["thing"]
- thing_colors = torch.tensor(panoptic_classes["thing_colors"], dtype=torch.float32) / 255.0
- semantics = Semantics(
- stuff_classes=stuff_classes,
- stuff_colors=stuff_colors,
- stuff_filenames=stuff_filenames,
- thing_classes=thing_classes,
- thing_colors=thing_colors,
- thing_filenames=thing_filenames,
- )
-
- # Possibly include the sparse point cloud from COLMAP in the dataset inputs.
- # NOTE(ethan): this will be common across the different splits.
- point_cloud = PointCloud()
- if self.include_point_cloud:
- points_3d = read_points3d_binary(abs_dir / "colmap" / "points3D.bin")
- xyz = torch.tensor(np.array([p_value.xyz for p_id, p_value in points_3d.items()])).float()
- rgb = torch.tensor(np.array([p_value.rgb for p_id, p_value in points_3d.items()])).float()
- xyz_h = torch.cat([xyz, torch.ones_like(xyz[..., :1])], -1)
- xyz = (xyz_h @ transposed_point_cloud_transform.T)[..., :3]
- xyz = (xyz - box_center) * box_scale_factor # center and rescale
- point_cloud.xyz = xyz
- point_cloud.rgb = rgb
-
- cameras = Cameras(
- fx=focal_lengths,
- fy=focal_lengths,
- cx=cx,
- cy=cy,
- camera_to_worlds=camera_to_world,
- camera_type=CameraType.PERSPECTIVE,
- )
-
- dataset_inputs = DatasetInputs(
- image_filenames=image_filenames,
- cameras=cameras,
- semantics=semantics,
- point_cloud=point_cloud,
- scene_bounds=scene_bounds,
- )
- return dataset_inputs
diff --git a/nerfactory/dataloaders/__init__.py b/nerfactory/datamanagers/__init__.py
similarity index 100%
rename from nerfactory/dataloaders/__init__.py
rename to nerfactory/datamanagers/__init__.py
diff --git a/nerfactory/dataloaders/base.py b/nerfactory/datamanagers/base.py
similarity index 74%
rename from nerfactory/dataloaders/base.py
rename to nerfactory/datamanagers/base.py
index d4df44aa8..2d8bb0c4f 100644
--- a/nerfactory/dataloaders/base.py
+++ b/nerfactory/datamanagers/base.py
@@ -28,14 +28,12 @@ from torch.utils.data.distributed import DistributedSampler
from nerfactory.cameras.rays import RayBundle
from nerfactory.configs import base as cfg
-from nerfactory.dataloaders.image_dataloader import (
+from nerfactory.datamanagers.dataloaders import (
CacheImageDataloader,
- EvalDataloader,
FixedIndicesEvalDataloader,
)
-from nerfactory.dataloaders.image_dataset import ImageDataset, PanopticImageDataset
-from nerfactory.dataloaders.pixel_sampler import PixelSampler
-from nerfactory.dataloaders.structs import DatasetInputs
+from nerfactory.datamanagers.datasets import InputDataset
+from nerfactory.datamanagers.pixel_sampler import PixelSampler
from nerfactory.models.modules.ray_generator import RayGenerator
from nerfactory.utils.callbacks import TrainingCallback, TrainingCallbackAttributes
from nerfactory.utils.misc import IterableWrapper
@@ -75,47 +73,36 @@ class DataManager(nn.Module):
next_eval: will be called on __next__() for the eval iterator
get_eval_iterable: utility that gets a clean pythonic iterator for your eval data
- Args:
- use_train: whether this data manager is being used for training
- use_eval: whether this data manager is being used for evaluation
Attributes:
- use_train (bool): whether or not we are using train
- use_eval (bool): whether or not we are using eval
train_count (int): the step number of our train iteration, needs to be incremented manually
eval_count (int): the step number of our eval iteration, needs to be incremented manually
- train_datasetinputs (DatasetInputs): the inputs for the train dataset
- eval_datasetinputs (DatasetInputs): the inputs for the eval dataset
- train_image_dataset (ImageDataset): the image dataset for the train dataset
+ train_input_dataset (InputDataset): the input dataset for the train dataset
+ eval_input_dataset (InputDataset): the input dataset for the eval dataset
Additional attributes specific to each subclass are defined in the setup_train and setup_eval
functions.
"""
- train_datasetinputs: Optional[DatasetInputs] = None
- eval_datasetinputs: Optional[DatasetInputs] = None
- train_image_dataset: Optional[ImageDataset] = None
- eval_dataloader: Optional[EvalDataloader] = None
+ train_input_dataset: Optional[InputDataset] = None
+ eval_input_dataset: Optional[InputDataset] = None
- def __init__(self, use_train: bool, use_eval: bool):
+ def __init__(self):
"""Constructor for the DataManager class.
Subclassed DataManagers will likely need to override this constructor.
If you aren't manually calling the setup_train and setup_eval functions from an overriden
- constructor, that you call super().__init__(use_train, use_eval) BEFORE you initialize any
+ constructor, that you call super().__init__() BEFORE you initialize any
nn.Modules or nn.Parameters, but AFTER you've already set all the attributes you need
for the setup functions."""
super().__init__()
- self.use_train = use_train
- self.use_eval = use_eval
self.train_count = 0
self.eval_count = 0
- assert use_train or use_eval
- if use_train:
+ if self.train_input_dataset and self.train_input_dataset.inputs:
self.setup_train()
- if use_eval:
+ if self.eval_input_dataset and self.eval_input_dataset.inputs:
self.setup_eval()
def forward(self):
@@ -144,7 +131,7 @@ class DataManager(nn.Module):
as __iter__ and __next__ methods respectivley.
This basically is just a little utility if you want to do something like:
- | for ray_bundle, batch in data_manager.get_train_iterable():
+ | for ray_bundle, batch in datamanager.get_train_iterable():
|
since the returned IterableWrapper is just an iterator with the __iter__ and __next__
methods (methods bound to our DataManager instance in this case) specified in the constructor.
@@ -156,7 +143,7 @@ class DataManager(nn.Module):
as __iter__ and __next__ methods respectivley.
This basically is just a little utility if you want to do something like:
- | for ray_bundle, batch in data_manager.get_eval_iterable():
+ | for ray_bundle, batch in datamanager.get_eval_iterable():
|
since the returned IterableWrapper is just an iterator with the __iter__ and __next__
methods (methods bound to our DataManager instance in this case) specified in the constructor.
@@ -233,70 +220,43 @@ class VanillaDataManager(DataManager): # pylint: disable=abstract-method
self.local_rank = local_rank
self.sampler = None
- dataset_train = config.train_dataset.setup()
- self.train_datasetinputs = dataset_train.get_dataset_inputs(split="train")
- self.train_cameras = self.train_datasetinputs.cameras.to(device)
- if config.eval_dataset is not None:
- dataset_eval = config.eval_dataset.setup()
- else:
+ if config.eval_dataparser is None:
logging.info("No eval dataset specified so using train dataset for eval.")
- dataset_eval = dataset_train
- self.eval_datasetinputs = dataset_eval.get_dataset_inputs(split="val" if not test_mode else "test")
- self.eval_cameras = self.eval_datasetinputs.cameras.to(device)
- use_train = self.train_datasetinputs is not None
- use_eval = self.eval_datasetinputs is not None
- super().__init__(use_train, use_eval)
+ config.eval_dataparser = config.train_dataparser
+ self.train_input_dataset = InputDataset(config.train_dataparser, split="train")
+ self.eval_input_dataset = InputDataset(config.eval_dataparser, split="val" if not test_mode else "test")
+ super().__init__()
def setup_train(self):
- """Sets up the data manager for training"""
- assert self.train_datasetinputs is not None
- if self.config.image_dataset_type == "rgb":
- self.train_image_dataset = ImageDataset(
- image_filenames=self.train_datasetinputs.image_filenames,
- alpha_color=self.train_datasetinputs.alpha_color,
- )
- elif self.config.image_dataset_type == "panoptic":
- self.train_image_dataset = PanopticImageDataset(**self.train_datasetinputs.as_dict())
- else:
- raise ValueError(f"Unknown image dataset type {self.config.image_dataset_type}")
-
+ """Sets up the data loaders for training"""
+ assert self.train_input_dataset is not None
if self.world_size > 1:
self.sampler = DistributedSampler(
- self.train_image_dataset, num_replicas=self.world_size, rank=self.local_rank, shuffle=True, seed=42
+ self.train_input_dataset, num_replicas=self.world_size, rank=self.local_rank, shuffle=True, seed=42
)
self.train_image_dataloader = CacheImageDataloader(
- self.train_image_dataset,
+ self.train_input_dataset,
num_images_to_sample_from=self.config.train_num_images_to_sample_from,
device=self.device,
num_workers=self.world_size * 4,
pin_memory=True,
sampler=self.sampler,
) # TODO(ethan): pass this in
-
else:
self.train_image_dataloader = CacheImageDataloader(
- self.train_image_dataset,
+ self.train_input_dataset,
num_images_to_sample_from=self.config.train_num_images_to_sample_from,
device=self.device,
)
self.iter_train_image_dataloader = iter(self.train_image_dataloader)
self.train_pixel_sampler = PixelSampler(self.config.train_num_rays_per_batch)
- self.train_ray_generator = RayGenerator(self.train_cameras)
+ self.train_ray_generator = RayGenerator(self.train_input_dataset.inputs.cameras.to(self.device))
def setup_eval(self):
- """Sets up the data manager for evaluation"""
- assert self.eval_datasetinputs is not None
- if self.config.image_dataset_type == "rgb":
- self.eval_image_dataset = ImageDataset(
- image_filenames=self.eval_datasetinputs.image_filenames,
- alpha_color=self.eval_datasetinputs.alpha_color,
- )
- elif self.config.image_dataset_type == "panoptic":
- self.eval_image_dataset = PanopticImageDataset(**self.eval_datasetinputs.as_dict())
-
+ """Sets up the data loader for evaluation"""
+ assert self.eval_input_dataset is not None
self.eval_dataloader = FixedIndicesEvalDataloader(
- image_dataset=self.eval_image_dataset,
- cameras=self.eval_cameras,
+ input_dataset=self.eval_input_dataset,
num_rays_per_chunk=self.config.eval_num_rays_per_chunk,
image_indices=self.config.eval_image_indices,
device=self.device,
diff --git a/nerfactory/dataloaders/image_dataloader.py b/nerfactory/datamanagers/dataloaders.py
similarity index 89%
rename from nerfactory/dataloaders/image_dataloader.py
rename to nerfactory/datamanagers/dataloaders.py
index a90d26ad0..0614fb800 100644
--- a/nerfactory/dataloaders/image_dataloader.py
+++ b/nerfactory/datamanagers/dataloaders.py
@@ -26,13 +26,13 @@ from torch.utils.data.dataloader import DataLoader
from nerfactory.cameras.cameras import Cameras
from nerfactory.cameras.rays import RayBundle
-from nerfactory.dataloaders.image_dataset import ImageDataset
+from nerfactory.datamanagers.datasets import InputDataset
from nerfactory.utils.misc import get_dict_to_torch
class CacheImageDataloader(DataLoader):
"""Collated image dataset that implements caching of images.
- Creates batches of the ImageDataset return type.
+ Creates batches of the InputDataset return type.
Args:
dataset (torch.utils.data.Dataset): Dataset to sample from.
@@ -43,7 +43,7 @@ class CacheImageDataloader(DataLoader):
def __init__(
self,
- dataset: ImageDataset,
+ dataset: InputDataset,
num_images_to_sample_from: int = -1,
num_times_to_repeat_images: int = 0,
device: Union[torch.device, str] = "cpu",
@@ -103,7 +103,7 @@ class EvalDataloader(DataLoader):
"""Evaluation dataloader base class
Args:
- image_dataset: ImageDataset to load data from
+ input_dataset: InputDataset to load data from
cameras: Cameras to use for generating rays
num_rays_per_chunk: Number of camera rays to generate per chunk
device: Device to load data to
@@ -111,18 +111,17 @@ class EvalDataloader(DataLoader):
def __init__(
self,
- image_dataset: ImageDataset,
- cameras: Cameras,
+ input_dataset: InputDataset,
num_rays_per_chunk: int,
device: Union[torch.device, str] = "cpu",
**kwargs,
):
- self.image_dataset = image_dataset
- self.cameras = cameras
+ self.input_dataset = input_dataset
+ self.cameras = input_dataset.inputs.cameras.to(device)
self.num_rays_per_chunk = num_rays_per_chunk
self.device = device
self.kwargs = kwargs
- super().__init__(dataset=image_dataset)
+ super().__init__(dataset=input_dataset)
@abstractmethod
def __iter__(self):
@@ -159,7 +158,7 @@ class EvalDataloader(DataLoader):
ray_bundle = self.cameras.generate_rays(camera_indices=image_idx)
ray_bundle.num_rays_per_chunk = self.num_rays_per_chunk
ray_bundle.camera_indices = torch.Tensor([image_idx])[..., None].int()
- batch = self.image_dataset[image_idx]
+ batch = self.input_dataset[image_idx]
batch = get_dict_to_torch(batch, device=self.device)
return ray_bundle, batch
@@ -169,8 +168,7 @@ class FixedIndicesEvalDataloader(EvalDataloader):
def __init__(
self,
- image_dataset: ImageDataset,
- cameras: Cameras,
+ input_dataset: InputDataset,
num_rays_per_chunk: int,
image_indices: Optional[Tuple[int]] = None,
device: Union[torch.device, str] = "cpu",
@@ -178,15 +176,15 @@ class FixedIndicesEvalDataloader(EvalDataloader):
):
"""
Args:
- image_dataset: ImageDataset to load data from
+ input_dataset: InputDataset to load data from
cameras: Cameras to use for generating rays
num_rays_per_chunk: Number of camera rays to generate per chunk
image_indices: List of image indices to load data from. If None, then use all images.
device: Device to load data to
"""
- super().__init__(image_dataset, cameras, num_rays_per_chunk, device, **kwargs)
+ super().__init__(input_dataset, num_rays_per_chunk, device, **kwargs)
if image_indices is None:
- self.image_indices = list(range(len(image_dataset)))
+ self.image_indices = list(range(len(input_dataset)))
else:
self.image_indices = image_indices
self.num_rays_per_chunk = num_rays_per_chunk
@@ -210,7 +208,7 @@ class RandIndicesEvalDataloader(EvalDataloader):
"""Dataloader that returns random images.
Args:
- image_dataset: ImageDataset to load data from
+ input_dataset: InputDataset to load data from
cameras: Cameras to use for generating rays
num_rays_per_chunk: Number of camera rays to generate per chunk
device: Device to load data to
@@ -218,13 +216,12 @@ class RandIndicesEvalDataloader(EvalDataloader):
def __init__(
self,
- image_dataset: ImageDataset,
- cameras: Cameras,
+ input_dataset: InputDataset,
num_rays_per_chunk: int,
device: Union[torch.device, str] = "cpu",
**kwargs,
):
- super().__init__(image_dataset, cameras, num_rays_per_chunk, device, **kwargs)
+ super().__init__(input_dataset, num_rays_per_chunk, device, **kwargs)
self.count = 0
def __iter__(self):
diff --git a/nerfactory/datamanagers/dataparsers/base.py b/nerfactory/datamanagers/dataparsers/base.py
new file mode 100644
index 000000000..4f18b7679
--- /dev/null
+++ b/nerfactory/datamanagers/dataparsers/base.py
@@ -0,0 +1,106 @@
+# Copyright 2022 The Plenoptix Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""A set of standard datasets."""
+
+from __future__ import annotations
+
+import dataclasses
+import logging
+from abc import abstractmethod
+from dataclasses import dataclass
+from pathlib import Path
+
+from nerfactory.configs import base as cfg
+from nerfactory.datamanagers.structs import DatasetInputs
+from nerfactory.utils.io import get_absolute_path, load_from_pkl, write_to_pkl
+from nerfactory.utils.misc import get_hash_str_from_dict
+
+
+@dataclass
+class DataParser:
+ """A dataset."""
+
+ def __init__(self, config: cfg.DataParserConfig):
+ super().__init__()
+ self.config = config
+
+ @abstractmethod
+ def _generate_dataset_inputs(self, split: str = "train") -> DatasetInputs:
+ """Returns the dataset inputs for the given split.
+
+ Args:
+ split: Which dataset split to generate.
+
+ Returns:
+ DatasetInputs
+ """
+
+ def get_dataset_inputs(self, split: str = "train", use_preprocessing_cache: bool = False) -> DatasetInputs:
+ """Returns the dataset inputs for the given split.
+
+ Args:
+ split: Which dataset split to generate.
+ use_preprocessing_cache: Whether to use the cached dataset inputs. Defaults to False.
+
+ Returns:
+ DatasetInputs
+ """
+ if use_preprocessing_cache:
+ dataset_inputs = self._get_dataset_inputs_from_cache(split)
+ else:
+ dataset_inputs = self._generate_dataset_inputs(split)
+ return dataset_inputs
+
+ def _get_cache_filename(self, split: str) -> Path:
+ """Creates a cache filename from the dataset inputs arguments.
+
+ Args:
+ split: Which dataset split to generate filename for.
+
+ Returns:
+ filename for cache.
+ """
+ dataset_config_hash = get_hash_str_from_dict(dataclasses.asdict(self))
+ dataset_config_hash_filename = get_absolute_path(
+ f"cache/dataset_inputs/{dataset_config_hash}-{split}.pkl"
+ ).parent.mkdir()
+ assert dataset_config_hash_filename is not None, "dataset hash is None"
+ return dataset_config_hash_filename
+
+ def save_dataset_inputs_to_cache(self, split: str):
+ """Saves the dataset inputs to cache.
+
+ Args:
+ split: Which dataset split to save.
+ """
+ dataset_inputs = self.get_dataset_inputs(split=split)
+ dataset_config_hash_filename = self._get_cache_filename(split)
+ write_to_pkl(dataset_config_hash_filename, dataset_inputs)
+
+ def _get_dataset_inputs_from_cache(self, split: str) -> DatasetInputs:
+ """Loads the dataset inputs from cache. If the cache does not exist, it will be created.
+
+ Args:
+ split: Which dataset split to load.
+ """
+ dataset_config_hash_filename = self._get_cache_filename(split)
+ if dataset_config_hash_filename.exists():
+ logging.info("Loading dataset from cache.")
+ dataset_inputs = load_from_pkl(dataset_config_hash_filename)
+ else:
+ logging.info("Cache file not found. Generating and saving dataset to cache.")
+ dataset_inputs = self._generate_dataset_inputs(split=split)
+ self.save_dataset_inputs_to_cache(split)
+ return dataset_inputs
diff --git a/nerfactory/datamanagers/dataparsers/blender_parser.py b/nerfactory/datamanagers/dataparsers/blender_parser.py
new file mode 100644
index 000000000..69a6ee9f9
--- /dev/null
+++ b/nerfactory/datamanagers/dataparsers/blender_parser.py
@@ -0,0 +1,95 @@
+# Copyright 2022 The Plenoptix Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Data parser for blender dataset"""
+from __future__ import annotations
+
+from dataclasses import dataclass
+from pathlib import Path
+
+import imageio
+import numpy as np
+import torch
+
+from nerfactory.cameras.cameras import Cameras, CameraType
+from nerfactory.configs import base as cfg
+from nerfactory.datamanagers.dataparsers.base import DataParser
+from nerfactory.datamanagers.structs import DatasetInputs, SceneBounds
+from nerfactory.utils.colors import get_color
+from nerfactory.utils.io import get_absolute_path, load_from_json
+
+
+@dataclass
+class Blender(DataParser):
+ """Blender Dataset
+ Some of this code comes from https://github.com/yenchenlin/nerf-pytorch/blob/master/load_blender.py#L37.
+
+ Args:
+ data_directory: Location of data
+ alpha_color: Sets transparent regions to specified color, otherwise black.
+ scale_factor: How much to scale the camera origins by.
+ """
+
+ def __init__(self, config: cfg.BlenderDataParserConfig):
+ super().__init__(config=config)
+ self.data_directory: Path = config.data_directory
+ self.scale_factor: float = config.scale_factor
+ self.alpha_color = config.alpha_color
+
+ def _generate_dataset_inputs(self, split="train"):
+ if self.alpha_color is not None:
+ alpha_color_tensor = get_color(self.alpha_color)
+ else:
+ alpha_color_tensor = None
+
+ abs_dir = get_absolute_path(self.data_directory)
+ meta = load_from_json(abs_dir / f"transforms_{split}.json")
+ image_filenames = []
+ poses = []
+ for frame in meta["frames"]:
+ fname = abs_dir / Path(frame["file_path"].replace("./", "") + ".png")
+ image_filenames.append(fname)
+ poses.append(np.array(frame["transform_matrix"]))
+ poses = np.array(poses).astype(np.float32)
+
+ img_0 = imageio.imread(image_filenames[0])
+ image_height, image_width = img_0.shape[:2]
+ camera_angle_x = float(meta["camera_angle_x"])
+ focal_length = 0.5 * image_width / np.tan(0.5 * camera_angle_x)
+
+ cx = image_width / 2.0
+ cy = image_height / 2.0
+ camera_to_world = torch.from_numpy(poses[:, :3]) # camera to world transform
+
+ # in x,y,z order
+ camera_to_world[..., 3] *= self.scale_factor
+ scene_bounds = SceneBounds(aabb=torch.tensor([[-1.5, -1.5, -1.5], [1.5, 1.5, 1.5]], dtype=torch.float32))
+
+ cameras = Cameras(
+ camera_to_worlds=camera_to_world,
+ fx=focal_length,
+ fy=focal_length,
+ cx=cx,
+ cy=cy,
+ camera_type=CameraType.PERSPECTIVE,
+ )
+
+ dataset_inputs = DatasetInputs(
+ image_filenames=image_filenames,
+ cameras=cameras,
+ alpha_color=alpha_color_tensor,
+ scene_bounds=scene_bounds,
+ )
+
+ return dataset_inputs
diff --git a/nerfactory/datamanagers/dataparsers/friends_parser.py b/nerfactory/datamanagers/dataparsers/friends_parser.py
new file mode 100644
index 000000000..728c2c743
--- /dev/null
+++ b/nerfactory/datamanagers/dataparsers/friends_parser.py
@@ -0,0 +1,183 @@
+# Copyright 2022 The Plenoptix Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Data parser for friends dataset"""
+from __future__ import annotations
+
+from dataclasses import dataclass
+from pathlib import Path
+
+import numpy as np
+import torch
+
+from nerfactory.cameras.cameras import Cameras, CameraType
+from nerfactory.datamanagers.dataparsers.base import DataParser
+from nerfactory.datamanagers.structs import (
+ DatasetInputs,
+ PointCloud,
+ SceneBounds,
+ Semantics,
+)
+from nerfactory.utils.colmap_utils import (
+ read_cameras_binary,
+ read_images_binary,
+ read_points3d_binary,
+)
+from nerfactory.utils.io import get_absolute_path, load_from_json
+
+
+@dataclass
+class Friends(DataParser):
+ """Friends Dataset
+
+ Args:
+ data_directory: Location of data
+ include_semantics: whether or not to include the semantics. Defaults to False.
+ include_point_cloud: whether or not to include the point cloud. Defaults to False.
+ """
+
+ data_directory: Path
+ include_semantics: bool = True
+ include_point_cloud: bool = False
+
+ @classmethod
+ def _get_aabb_and_transform(cls, basedir):
+ """Returns the aabb and pointcloud transform from the threejs.json file."""
+ filename = basedir / "threejs.json"
+ assert filename.exists()
+ data = load_from_json(filename)
+
+ # point cloud transformation
+ transposed_point_cloud_transform = np.array(data["object"]["children"][0]["matrix"]).reshape(4, 4).T
+ assert transposed_point_cloud_transform[3, 3] == 1.0
+
+ # bbox transformation
+ bbox_transform = np.array(data["object"]["children"][1]["matrix"]).reshape(4, 4).T
+ w, h, d = data["geometries"][1]["width"], data["geometries"][1]["height"], data["geometries"][1]["depth"]
+ temp = np.array([w, h, d]) / 2.0
+ bbox = np.array([-temp, temp])
+ bbox = np.concatenate([bbox, np.ones_like(bbox[:, 0:1])], axis=1)
+ bbox = (bbox_transform @ bbox.T).T[:, 0:3]
+
+ aabb = bbox # rename to aabb because it's an axis-aligned bounding box
+ return torch.from_numpy(aabb).float(), torch.from_numpy(transposed_point_cloud_transform).float()
+
+ def _generate_dataset_inputs(self, split="train"): # pylint: disable=too-many-statements
+
+ abs_dir = get_absolute_path(self.data_directory)
+
+ images_data = read_images_binary(abs_dir / "colmap" / "images.bin")
+ # `image_path` is only the end of the filename, including the extension e.g., `.jpg`
+ image_paths = sorted((abs_dir / "images").iterdir())
+
+ image_path_to_image_id = {}
+ image_id_to_image_path = {}
+ for v in images_data.values():
+ image_path_to_image_id[v.name] = v.id
+ image_id_to_image_path[v.id] = v.name
+ # TODO: handle the splits differently
+ image_filenames = [abs_dir / "images" / image_path for image_path in image_paths]
+
+ # -- set the bounding box ---
+ aabb, transposed_point_cloud_transform = self._get_aabb_and_transform(abs_dir)
+ scene_bounds_original = SceneBounds(aabb=aabb)
+ # for shifting and rescale accoding to scene bounds
+ box_center = scene_bounds_original.get_center()
+ box_scale_factor = 5.0 / scene_bounds_original.get_diagonal_length() # the target diagonal length
+ scene_bounds = scene_bounds_original.get_centered_and_scaled_scene_bounds(box_scale_factor)
+
+ # --- intrinsics ---
+ cameras_data = read_cameras_binary(abs_dir / "colmap" / "cameras.bin")
+ focal_lengths = []
+ for image_path in image_paths:
+ cam = cameras_data[image_path_to_image_id[image_path]]
+ assert len(cam.params) == 3
+ focal_lengths.append(cam.params[0]) # f (fx and fy)
+ focal_lengths = torch.stack(focal_lengths, dim=0)
+
+ cam = cameras_data[image_path_to_image_id[image_paths[0]]]
+ cx = cam.params[1] # cx
+ cy = cam.params[2] # cy
+
+ # --- camera_to_world (extrinsics) ---
+ camera_to_world = []
+ bottom_row = np.array([0, 0, 0, 1.0]).reshape(1, 4)
+ for image_path in image_paths:
+ image_data = images_data[image_path_to_image_id[image_path]]
+ rot = image_data.qvec2rotmat()
+ trans = image_data.tvec.reshape(3, 1)
+ c2w = np.concatenate([np.concatenate([rot, trans], 1), bottom_row], 0)
+ camera_to_world.append(c2w)
+ camera_to_world = torch.tensor(np.array(camera_to_world)).float()
+ camera_to_world = torch.inverse(camera_to_world)
+ camera_to_world[..., 1:3] *= -1
+ camera_to_world = transposed_point_cloud_transform @ camera_to_world
+ camera_to_world = camera_to_world[:, :3]
+ camera_to_world[..., 3] = (camera_to_world[..., 3] - box_center) * box_scale_factor # center and rescale
+
+ # --- semantics ---
+ semantics = None
+ if self.include_semantics:
+ thing_filenames = [
+ Path(str(image_filename).replace("/images/", "/segmentations/thing/").replace(".jpg", ".png"))
+ for image_filename in image_filenames
+ ]
+ stuff_filenames = [
+ Path(str(image_filename).replace("/images/", "/segmentations/stuff/").replace(".jpg", ".png"))
+ for image_filename in image_filenames
+ ]
+ panoptic_classes = load_from_json(abs_dir / "panoptic_classes.json")
+ stuff_classes = panoptic_classes["stuff"]
+ stuff_colors = torch.tensor(panoptic_classes["stuff_colors"], dtype=torch.float32) / 255.0
+ thing_classes = panoptic_classes["thing"]
+ thing_colors = torch.tensor(panoptic_classes["thing_colors"], dtype=torch.float32) / 255.0
+ semantics = Semantics(
+ stuff_classes=stuff_classes,
+ stuff_colors=stuff_colors,
+ stuff_filenames=stuff_filenames,
+ thing_classes=thing_classes,
+ thing_colors=thing_colors,
+ thing_filenames=thing_filenames,
+ )
+
+ # Possibly include the sparse point cloud from COLMAP in the dataset inputs.
+ # NOTE(ethan): this will be common across the different splits.
+ point_cloud = PointCloud()
+ if self.include_point_cloud:
+ points_3d = read_points3d_binary(abs_dir / "colmap" / "points3D.bin")
+ xyz = torch.tensor(np.array([p_value.xyz for p_id, p_value in points_3d.items()])).float()
+ rgb = torch.tensor(np.array([p_value.rgb for p_id, p_value in points_3d.items()])).float()
+ xyz_h = torch.cat([xyz, torch.ones_like(xyz[..., :1])], -1)
+ xyz = (xyz_h @ transposed_point_cloud_transform.T)[..., :3]
+ xyz = (xyz - box_center) * box_scale_factor # center and rescale
+ point_cloud.xyz = xyz
+ point_cloud.rgb = rgb
+
+ cameras = Cameras(
+ fx=focal_lengths,
+ fy=focal_lengths,
+ cx=cx,
+ cy=cy,
+ camera_to_worlds=camera_to_world,
+ camera_type=CameraType.PERSPECTIVE,
+ )
+
+ dataset_inputs = DatasetInputs(
+ image_filenames=image_filenames,
+ cameras=cameras,
+ semantics=semantics,
+ point_cloud=point_cloud,
+ scene_bounds=scene_bounds,
+ )
+ return dataset_inputs
diff --git a/nerfactory/datamanagers/dataparsers/instant_ngp_parser.py b/nerfactory/datamanagers/dataparsers/instant_ngp_parser.py
new file mode 100644
index 000000000..9e22d007e
--- /dev/null
+++ b/nerfactory/datamanagers/dataparsers/instant_ngp_parser.py
@@ -0,0 +1,106 @@
+# Copyright 2022 The Plenoptix Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Data parser for instant ngp data"""
+
+from __future__ import annotations
+
+import logging
+from dataclasses import dataclass
+from pathlib import Path
+
+import numpy as np
+import torch
+
+from nerfactory.cameras import utils as camera_utils
+from nerfactory.cameras.cameras import Cameras, CameraType
+from nerfactory.configs import base as cfg
+from nerfactory.datamanagers.dataparsers.base import DataParser
+from nerfactory.datamanagers.structs import DatasetInputs, SceneBounds
+from nerfactory.utils.io import get_absolute_path, load_from_json
+
+
+@dataclass
+class InstantNGP(DataParser):
+ """Instant NGP Dataset
+
+ Args:
+ data_directory: Location of data
+ scale_factor: How much to scale the camera origins by.
+ scene_scale: How much to scale the scene. Defaults to 0.33
+ """
+
+ config: cfg.InstantNGPDataParserConfig
+
+ def _generate_dataset_inputs(self, split="train"):
+
+ abs_dir = get_absolute_path(self.config.data_directory)
+
+ meta = load_from_json(abs_dir / "transforms.json")
+ image_filenames = []
+ poses = []
+ num_skipped_image_filenames = 0
+ for frame in meta["frames"]:
+ fname = abs_dir / Path(frame["file_path"])
+ if not fname:
+ num_skipped_image_filenames += 1
+ else:
+ image_filenames.append(fname)
+ poses.append(np.array(frame["transform_matrix"]))
+ if num_skipped_image_filenames >= 0:
+ logging.info("Skipping %s files in dataset split %s.", num_skipped_image_filenames, split)
+ assert (
+ len(image_filenames) != 0
+ ), """
+ No image files found.
+ You should check the file_paths in the transforms.json file to make sure they are correct.
+ """
+ poses = np.array(poses).astype(np.float32)
+ poses[:3, 3] *= self.config.scene_scale
+
+ camera_to_world = torch.from_numpy(poses[:, :3]) # camera to world transform
+
+ distortion_params = camera_utils.get_distortion_params(
+ k1=float(meta["k1"]), k2=float(meta["k2"]), p1=float(meta["p1"]), p2=float(meta["p2"])
+ )
+
+ # in x,y,z order
+ # assumes that the scene is centered at the origin
+ aabb_scale = meta["aabb_scale"]
+ scene_bounds = SceneBounds(
+ aabb=torch.tensor(
+ [[-aabb_scale, -aabb_scale, -aabb_scale], [aabb_scale, aabb_scale, aabb_scale]], dtype=torch.float32
+ )
+ )
+
+ cameras = Cameras(
+ fx=float(meta["fl_x"]),
+ fy=float(meta["fl_y"]),
+ cx=float(meta["cx"]),
+ cy=float(meta["cy"]),
+ distortion_params=distortion_params,
+ height=int(meta["h"]),
+ width=int(meta["w"]),
+ camera_to_worlds=camera_to_world,
+ camera_type=CameraType.PERSPECTIVE,
+ )
+
+ # TODO(ethan): add alpha background color
+ dataset_inputs = DatasetInputs(
+ image_filenames=image_filenames,
+ cameras=cameras,
+ scene_bounds=scene_bounds,
+ )
+
+ return dataset_inputs
diff --git a/nerfactory/datamanagers/dataparsers/mipnerf_parser.py b/nerfactory/datamanagers/dataparsers/mipnerf_parser.py
new file mode 100644
index 000000000..ecfe9172f
--- /dev/null
+++ b/nerfactory/datamanagers/dataparsers/mipnerf_parser.py
@@ -0,0 +1,151 @@
+# Copyright 2022 The Plenoptix Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Data parser for mipnerf dataset"""
+from __future__ import annotations
+
+from dataclasses import dataclass
+
+import imageio
+import numpy as np
+import torch
+
+from nerfactory.cameras.cameras import Cameras, CameraType
+from nerfactory.configs import base as cfg
+from nerfactory.datamanagers.dataparsers.base import DataParser
+from nerfactory.datamanagers.structs import DatasetInputs, SceneBounds
+from nerfactory.utils.io import get_absolute_path
+
+
+@dataclass
+class Mipnerf360(DataParser):
+ """MipNeRF 360 Dataset
+
+ Args:
+ data_directory: Location of data
+ downscale_factor: How much to downscale images. Defaults to 1.
+ val_skip: 1/val_skip images to use for validation. Defaults to 8.
+ auto_scale: Scale based on pose bounds. Defaults to True.
+ aabb_scale: Scene scale, Defaults to 1.0.
+ """
+
+ config: cfg.MipNerf360DataParserConfig
+
+ @classmethod
+ def normalize_orientation(cls, poses: np.ndarray):
+ """Set the _up_ direction to be in the positive Y direction.
+
+ Args:
+ poses: Numpy array of poses.
+ """
+ poses_orig = poses.copy()
+ bottom = np.reshape([0, 0, 0, 1.0], [1, 4])
+ center = poses[:, :3, 3].mean(0)
+ vec2 = poses[:, :3, 2].sum(0) / np.linalg.norm(poses[:, :3, 2].sum(0))
+ up = poses[:, :3, 1].sum(0)
+ vec0 = np.cross(up, vec2) / np.linalg.norm(np.cross(up, vec2))
+ vec1 = np.cross(vec2, vec0) / np.linalg.norm(np.cross(vec2, vec0))
+ c2w = np.stack([vec0, vec1, vec2, center], -1) # [3, 4]
+ c2w = np.concatenate([c2w[:3, :4], bottom], -2) # [4, 4]
+ bottom = np.tile(np.reshape(bottom, [1, 1, 4]), [poses.shape[0], 1, 1]) # [BS, 1, 4]
+ poses = np.concatenate([poses[:, :3, :4], bottom], -2) # [BS, 4, 4]
+ poses = np.linalg.inv(c2w) @ poses
+ poses_orig[:, :3, :4] = poses[:, :3, :4]
+ return poses_orig
+
+ def _generate_dataset_inputs(self, split="train"):
+ abs_dir = get_absolute_path(self.config.data_directory)
+ image_dir = "images"
+ if self.config.downscale_factor > 1:
+ image_dir += f"_{self.config.downscale_factor}"
+ image_dir = abs_dir / image_dir
+ if not image_dir.exists():
+ raise ValueError(f"Image directory {image_dir} doesn't exist")
+
+ valid_formats = [".jpg", ".png"]
+ image_filenames = []
+ for f in image_dir.iterdir():
+ ext = f.suffix
+ if ext.lower() not in valid_formats:
+ continue
+ image_filenames.append(image_dir / f)
+ image_filenames = sorted(image_filenames)
+ num_images = len(image_filenames)
+
+ poses_data = np.load(abs_dir / "poses_bounds.npy")
+ poses = poses_data[:, :-2].reshape([-1, 3, 5]).astype(np.float32)
+ bounds = poses_data[:, -2:].transpose([1, 0])
+
+ if num_images != poses.shape[0]:
+ raise RuntimeError(f"Different number of images ({num_images}), and poses ({poses.shape[0]})")
+
+ idx_test = np.arange(num_images)[:: self.config.val_skip]
+ idx_train = np.array([i for i in np.arange(num_images) if i not in idx_test])
+ idx = idx_train if split == "train" else idx_test
+
+ image_filenames = np.array(image_filenames)[idx]
+ poses = poses[idx]
+
+ img_0 = imageio.imread(image_filenames[0])
+ image_height, image_width = img_0.shape[:2]
+
+ poses[:, :2, 4] = np.array([image_height, image_width])
+ poses[:, 2, 4] = poses[:, 2, 4]
+
+ # Reorder pose to match our convention
+ poses = np.concatenate([poses[:, :, 1:2], -poses[:, :, 0:1], poses[:, :, 2:]], axis=-1)
+
+ # Center poses and rotate. (Compute up from average of all poses)
+ poses = self.normalize_orientation(poses)
+
+ # Scale factor used in mipnerf
+ if self.config.auto_scale:
+ scale_factor = 1 / (np.min(bounds) * 0.75)
+ poses[:, :3, 3] *= scale_factor
+ bounds *= scale_factor
+
+ # Center poses
+ poses[:, :3, 3] = poses[:, :3, 3] - np.mean(poses[:, :3, :], axis=0)[:, 3]
+
+ focal_length = poses[0, -1, -1]
+
+ cx = image_width / 2.0
+ cy = image_height / 2.0
+ camera_to_world = torch.from_numpy(poses[:, :3, :4]) # camera to world transform
+ num_cameras = len(image_filenames)
+ num_intrinsics_params = 3
+ intrinsics = torch.ones((num_cameras, num_intrinsics_params), dtype=torch.float32)
+ intrinsics *= torch.tensor([cx, cy, focal_length])
+
+ aabb = torch.tensor([[-4, -4, -4], [4, 4, 4]], dtype=torch.float32) * self.config.aabb_scale
+ scene_bounds = SceneBounds(aabb=aabb)
+
+ cameras = Cameras(
+ fx=focal_length,
+ fy=focal_length,
+ cx=cx,
+ cy=cy,
+ camera_to_worlds=camera_to_world,
+ camera_type=CameraType.PERSPECTIVE,
+ )
+
+ cameras.rescale_output_resolution(scaling_factor=1.0 / self.config.downscale_factor)
+
+ dataset_inputs = DatasetInputs(
+ image_filenames=image_filenames,
+ cameras=cameras,
+ scene_bounds=scene_bounds,
+ )
+
+ return dataset_inputs
diff --git a/nerfactory/datamanagers/dataparsers/nerfactory_parser.py b/nerfactory/datamanagers/dataparsers/nerfactory_parser.py
new file mode 100644
index 000000000..68538fc05
--- /dev/null
+++ b/nerfactory/datamanagers/dataparsers/nerfactory_parser.py
@@ -0,0 +1,112 @@
+# Copyright 2022 The Plenoptix Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+
+import logging
+from dataclasses import dataclass
+from pathlib import Path
+
+import numpy as np
+import torch
+
+from nerfactory.cameras import utils as camera_utils
+from nerfactory.cameras.cameras import Cameras, CameraType
+from nerfactory.configs import base as cfg
+from nerfactory.datamanagers.dataparsers.base import DataParser
+from nerfactory.datamanagers.structs import DatasetInputs, SceneBounds
+from nerfactory.utils.io import get_absolute_path, load_from_json
+
+
+@dataclass
+class Nerfactory(DataParser):
+ """Nerfactory Dataset
+
+ Args:
+ data_directory: Location of data
+ scale_factor: How much to scale the camera origins by.
+ downscale_factor: How much to downscale images.
+ scene_scale: How much to scale the scene.
+ orientation_method: The method to use for orientation. Either "pca" or "up".
+ """
+
+ config: cfg.NerfactoryDataParserConfig
+
+ def _generate_dataset_inputs(self, split="train"):
+
+ abs_dir = get_absolute_path(self.config.data_directory)
+
+ meta = load_from_json(abs_dir / "transforms.json")
+ image_filenames = []
+ poses = []
+ num_skipped_image_filenames = 0
+ for frame in meta["frames"]:
+ fname = abs_dir / Path(frame["file_path"])
+ if self.config.downscale_factor > 1:
+ fname = abs_dir / f"images_{self.config.downscale_factor}" / Path(frame["file_path"]).name
+ else:
+ fname = abs_dir / Path(frame["file_path"])
+ if not fname:
+ num_skipped_image_filenames += 1
+ else:
+ image_filenames.append(fname)
+ poses.append(np.array(frame["transform_matrix"]))
+ if num_skipped_image_filenames >= 0:
+ logging.info("Skipping %s files in dataset split %s.", num_skipped_image_filenames, split)
+ assert (
+ len(image_filenames) != 0
+ ), """
+ No image files found.
+ You should check the file_paths in the transforms.json file to make sure they are correct.
+ """
+ poses = torch.from_numpy(np.array(poses).astype(np.float32))
+ poses = camera_utils.auto_orient_poses(poses, method=self.config.orientation_method)
+
+ # Scale poses
+ scale_factor = 1.0 / torch.max(torch.abs(poses[:, :3, 3]))
+ poses[:, :3, 3] *= scale_factor
+
+ distortion_params = camera_utils.get_distortion_params(
+ k1=float(meta["k1"]), k2=float(meta["k2"]), p1=float(meta["p1"]), p2=float(meta["p2"])
+ )
+
+ # in x,y,z order
+ # assumes that the scene is centered at the origin
+ aabb_scale = self.config.scene_scale
+ scene_bounds = SceneBounds(
+ aabb=torch.tensor(
+ [[-aabb_scale, -aabb_scale, -aabb_scale], [aabb_scale, aabb_scale, aabb_scale]], dtype=torch.float32
+ )
+ )
+
+ cameras = Cameras(
+ fx=float(meta["fl_x"]),
+ fy=float(meta["fl_y"]),
+ cx=float(meta["cx"]),
+ cy=float(meta["cy"]),
+ distortion_params=distortion_params,
+ height=int(meta["h"]),
+ width=int(meta["w"]),
+ camera_to_worlds=poses[:, :3, :4],
+ camera_type=CameraType.PERSPECTIVE,
+ )
+
+ cameras.rescale_output_resolution(scaling_factor=1.0 / self.config.downscale_factor)
+
+ dataset_inputs = DatasetInputs(
+ image_filenames=image_filenames,
+ cameras=cameras,
+ scene_bounds=scene_bounds,
+ )
+ return dataset_inputs
diff --git a/nerfactory/datamanagers/dataparsers/record3d_parser.py b/nerfactory/datamanagers/dataparsers/record3d_parser.py
new file mode 100644
index 000000000..f966142e5
--- /dev/null
+++ b/nerfactory/datamanagers/dataparsers/record3d_parser.py
@@ -0,0 +1,148 @@
+# Copyright 2022 The Plenoptix Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Data parser for record3d dataset"""
+from __future__ import annotations
+
+from dataclasses import dataclass
+
+import numpy as np
+import torch
+from scipy.spatial.transform import Rotation
+
+from nerfactory.cameras.cameras import Cameras, CameraType
+from nerfactory.configs import base as cfg
+from nerfactory.datamanagers.dataparsers.base import DataParser
+from nerfactory.datamanagers.dataparsers.mipnerf_parser import Mipnerf360
+from nerfactory.datamanagers.structs import DatasetInputs, SceneBounds
+from nerfactory.utils.io import get_absolute_path, load_from_json
+
+
+@dataclass
+class Record3D(DataParser):
+ """Record3D Dataset
+
+ Args:
+ data_directory: Location of data
+ val_skip: 1/val_skip images to use for validation. Defaults to 8.
+ aabb_scale: Scene scale, Defaults to 4.0.
+ max_dataset_size: Max number of images to train on. If the dataset has
+ more, images will be sampled approximately evenly. Defaults to 150.
+ """
+
+ config: cfg.Record3DDataParserConfig
+
+ def _generate_dataset_inputs(self, split: str = "train") -> DatasetInputs:
+ abs_dir = get_absolute_path(self.config.data_directory)
+
+ image_dir = abs_dir / "rgb"
+
+ if not image_dir.exists():
+ raise ValueError(f"Image directory {image_dir} doesn't exist")
+
+ image_filenames = []
+ for f in image_dir.iterdir():
+ if f.stem.isdigit(): # removes possible duplicate images (for example, 123(3).jpg)
+ image_filenames.append(image_dir / f)
+
+ image_filenames = sorted(image_filenames, key=lambda fn: int(fn.stem))
+ image_filenames = np.array(image_filenames)
+ num_images = len(image_filenames)
+
+ metadata_path = abs_dir / "metadata.json"
+ metadata_dict = load_from_json(metadata_path)
+
+ poses_data = np.array(metadata_dict["poses"])
+ # (N, 3, 4)
+ poses = np.concatenate(
+ [Rotation.from_quat(poses_data[:, :4]).as_matrix(), poses_data[:, 4:, None]],
+ axis=-1,
+ ).astype(np.float32)
+
+ if num_images > self.config.max_dataset_size:
+ # Evenly select max_dataset_size images from dataset, including first
+ # and last indices.
+ idx = np.round(np.linspace(0, num_images - 1, self.config.max_dataset_size)).astype(int)
+ poses = poses[idx]
+ image_filenames = image_filenames[idx]
+ num_images = len(image_filenames)
+
+ # Normalization similar to Mipnerf360
+ poses = Mipnerf360.normalize_orientation(poses)
+
+ bottom = np.reshape([0, 0, 0, 1.0], [1, 4])
+ bottom = np.tile(np.reshape(bottom, [1, 1, 4]), [poses.shape[0], 1, 1])
+ poses = np.concatenate([poses[:, :3, :4], bottom], -2).astype(np.float32)
+
+ rotation_matrix = np.array(
+ [
+ [1.0, 0.0, 0.0, 0.0],
+ [0.0, 0.0, -1.0, 0.0],
+ [0.0, 1.0, 0.0, 0.0],
+ [0.0, 0.0, 0.0, 1.0],
+ ],
+ dtype=np.float32,
+ )
+ poses = rotation_matrix @ poses
+
+ idx_test = np.arange(num_images)[:: self.config.val_skip]
+ idx_train = np.array([i for i in np.arange(num_images) if i not in idx_test])
+ idx = idx_train if split == "train" else idx_test
+ if num_images != poses.shape[0]:
+ raise RuntimeError(f"Different number of images ({num_images}), and poses ({poses.shape[0]})")
+
+ image_filenames = image_filenames[idx]
+ poses = poses[idx]
+
+ # Centering poses
+ poses[:, :3, 3] = poses[:, :3, 3] - np.mean(poses[:, :3, 3], axis=0)
+
+ camera_to_world = torch.from_numpy(poses[:, :3, :4]) # camera to world transform
+
+ # Camera intrinsics
+ K = np.array(metadata_dict["K"]).reshape((3, 3)).T
+ focal_length = K[0, 0]
+
+ H = metadata_dict["h"]
+ W = metadata_dict["w"]
+
+ # TODO(akristoffersen): The metadata dict comes with principle points,
+ # but caused errors in image coord indexing. Should update once that is fixed.
+ cx, cy = W / 2, H / 2
+
+ num_cameras = len(image_filenames)
+ num_intrinsics_params = 3
+ intrinsics = torch.ones((num_cameras, num_intrinsics_params), dtype=torch.float32)
+ intrinsics *= torch.tensor([cx, cy, focal_length])
+
+ # scene_bounds = SceneBounds.from_camera_poses(camera_to_world, self.config.aabb_scale)
+ aabb = torch.tensor([[-1, -1, -1], [1, 1, 1]], dtype=torch.float32) * self.config.aabb_scale
+ scene_bounds = SceneBounds(aabb=aabb)
+
+ cameras = Cameras(
+ fx=focal_length,
+ fy=focal_length,
+ cx=cx,
+ cy=cy,
+ camera_to_worlds=camera_to_world,
+ camera_type=CameraType.PERSPECTIVE,
+ )
+
+ dataset_inputs = DatasetInputs(
+ image_filenames=image_filenames,
+ cameras=cameras,
+ scene_bounds=scene_bounds,
+ )
+
+ return dataset_inputs
diff --git a/nerfactory/dataloaders/image_dataset.py b/nerfactory/datamanagers/datasets.py
similarity index 64%
rename from nerfactory/dataloaders/image_dataset.py
rename to nerfactory/datamanagers/datasets.py
index c266ffa0f..6f752b078 100644
--- a/nerfactory/dataloaders/image_dataset.py
+++ b/nerfactory/datamanagers/datasets.py
@@ -15,10 +15,10 @@
"""
Some dataset code.
"""
+from __future__ import annotations
from abc import abstractmethod
-from pathlib import Path
-from typing import Dict, List, Optional, Union
+from typing import Dict, Union
import numpy as np
import numpy.typing as npt
@@ -27,18 +27,15 @@ from PIL import Image
from torch.utils.data import Dataset
from torchtyping import TensorType
-from nerfactory.dataloaders.structs import Semantics
+from nerfactory.configs import base as cfg
+from nerfactory.datamanagers.structs import DatasetInputs
from nerfactory.utils.misc import is_not_none
-class ImageDataset(Dataset):
+class InputDataset(Dataset):
"""Dataset that returns images."""
- def __init__(
- self,
- image_filenames: List[Path],
- alpha_color: Optional[TensorType[3]] = None,
- ):
+ def __init__(self, config: Union[cfg.DataParserConfig, cfg.InstantiateConfig], split: str):
"""_summary_
Args:
@@ -46,11 +43,10 @@ class ImageDataset(Dataset):
alpha_color (TensorType[3], optional): Sets transparent regions to specified color, otherwise black.
"""
super().__init__()
- self.image_filenames = image_filenames
- self.alpha_color = alpha_color
+ self.inputs: DatasetInputs = config.setup().get_dataset_inputs(split=split)
def __len__(self):
- return len(self.image_filenames)
+ return len(self.inputs.image_filenames)
def get_numpy_image(self, image_idx: int) -> npt.NDArray[np.uint8]:
"""Returns the image.
@@ -61,7 +57,7 @@ class ImageDataset(Dataset):
Returns:
np.uint8: an image of shape (H, W, 3 or 4)
"""
- image_filename = self.image_filenames[image_idx]
+ image_filename = self.inputs.image_filenames[image_idx]
pil_image = Image.open(image_filename)
image = np.array(pil_image, dtype="uint8") # shape is (h, w, 3 or 4)
assert len(image.shape) == 3
@@ -72,9 +68,9 @@ class ImageDataset(Dataset):
def get_image(self, image_idx: int) -> TensorType["image_height", "image_width", "num_channels"]:
"""Returns a 3 channel image."""
image = torch.from_numpy(self.get_numpy_image(image_idx).astype("float32") / 255.0)
- if self.alpha_color is not None and image.shape[-1] == 4:
+ if self.inputs.alpha_color is not None and image.shape[-1] == 4:
assert image.shape[-1] == 4
- image = image[:, :, :3] * image[:, :, -1:] + self.alpha_color * (1.0 - image[:, :, -1:])
+ image = image[:, :, :3] * image[:, :, -1:] + self.inputs.alpha_color * (1.0 - image[:, :, -1:])
else:
image = image[:, :, :3]
return image
@@ -82,11 +78,23 @@ class ImageDataset(Dataset):
@abstractmethod
def get_mask(self, image_idx: int) -> Union[TensorType["image_height", "image_width", 1], None]:
"""Returns a mask, which indicates which pixels are valid to use with nerf."""
+ if self.inputs.semantics:
+ person_index = self.inputs.semantics.thing_classes.index("person")
+ thing_image_filename = self.inputs.semantics.thing_filenames[image_idx]
+ pil_image = Image.open(thing_image_filename)
+ thing_semantics = torch.from_numpy(np.array(pil_image, dtype="int32"))[..., None]
+ mask = (thing_semantics != person_index).to(torch.float32) # 1 where valid
+ return mask
return None
@abstractmethod
def get_semantics(self, image_idx: int) -> Union[TensorType["image_height", "image_width", "num_classes"], None]:
"""Returns an image with semantic class values."""
+ if self.inputs.semantics:
+ stuff_image_filename = self.inputs.semantics.stuff_filenames[image_idx]
+ pil_image = Image.open(stuff_image_filename)
+ stuff_semantics = torch.from_numpy(np.array(pil_image, dtype="int32"))[..., None]
+ return stuff_semantics
return None
def get_data(self, image_idx) -> Dict:
@@ -108,32 +116,3 @@ class ImageDataset(Dataset):
def __getitem__(self, image_idx):
data = self.get_data(image_idx)
return data
-
-
-class PanopticImageDataset(ImageDataset):
- """Panoptic image dataset that masks out people."""
-
- def __init__(
- self,
- semantics: Semantics,
- image_filenames: List[Path],
- alpha_color: Optional[TensorType[3]] = None,
- **kwargs,
- ):
- self.semantics = semantics
- self.person_index = self.semantics.thing_classes.index("person")
- super().__init__(image_filenames, alpha_color, **kwargs)
-
- def get_mask(self, image_idx):
- """Mask out the people. Valid only where there aren't people."""
- thing_image_filename = self.semantics.thing_filenames[image_idx]
- pil_image = Image.open(thing_image_filename)
- thing_semantics = torch.from_numpy(np.array(pil_image, dtype="int32"))[..., None]
- mask = (thing_semantics != self.person_index).to(torch.float32) # 1 where valid
- return mask
-
- def get_semantics(self, image_idx):
- stuff_image_filename = self.semantics.stuff_filenames[image_idx]
- pil_image = Image.open(stuff_image_filename)
- stuff_semantics = torch.from_numpy(np.array(pil_image, dtype="int32"))[..., None]
- return stuff_semantics
diff --git a/nerfactory/dataloaders/pixel_sampler.py b/nerfactory/datamanagers/pixel_sampler.py
similarity index 100%
rename from nerfactory/dataloaders/pixel_sampler.py
rename to nerfactory/datamanagers/pixel_sampler.py
diff --git a/nerfactory/dataloaders/structs.py b/nerfactory/datamanagers/structs.py
similarity index 100%
rename from nerfactory/dataloaders/structs.py
rename to nerfactory/datamanagers/structs.py
diff --git a/nerfactory/engine/trainer.py b/nerfactory/engine/trainer.py
index 458ae51b8..822948b06 100644
--- a/nerfactory/engine/trainer.py
+++ b/nerfactory/engine/trainer.py
@@ -114,13 +114,9 @@ class Trainer:
def train(self) -> None:
"""Train the model."""
- assert self.pipeline.data_manager.train_datasetinputs is not None, "Missing DatasetInputs"
- assert self.pipeline.data_manager.train_image_dataset is not None, "Missing ImageDataset"
+ assert self.pipeline.datamanager.train_input_dataset is not None, "Missing DatsetInputs"
- self.visualizer_state.init_scene(
- image_dataset=self.pipeline.data_manager.train_image_dataset,
- dataset_inputs=self.pipeline.data_manager.train_datasetinputs,
- )
+ self.visualizer_state.init_scene(dataset=self.pipeline.datamanager.train_input_dataset)
with TimeWriter(writer, EventName.TOTAL_TRAIN_TIME):
num_iterations = self.config.trainer.max_num_iterations
for step in range(self.start_step, self.start_step + num_iterations):
@@ -143,7 +139,7 @@ class Trainer:
with TimeWriter(writer, EventName.ITER_VIS_TIME, step=step) as _:
self.visualizer_state.update_scene(step, self.pipeline.model)
- train_num_rays_per_batch = self.config.pipeline.data_manager.train_num_rays_per_batch
+ train_num_rays_per_batch = self.config.pipeline.datamanager.train_num_rays_per_batch
writer.put_scalar(name=EventName.RAYS_PER_SEC, scalar=train_num_rays_per_batch / t.duration, step=step)
if step != 0 and step % self.config.logging.steps_per_log == 0:
diff --git a/nerfactory/fields/instant_ngp_field.py b/nerfactory/fields/instant_ngp_field.py
index 553cb1584..808edbad0 100644
--- a/nerfactory/fields/instant_ngp_field.py
+++ b/nerfactory/fields/instant_ngp_field.py
@@ -24,7 +24,7 @@ from torch.nn.parameter import Parameter
from torchtyping import TensorType
from nerfactory.cameras.rays import RaySamples
-from nerfactory.dataloaders.structs import SceneBounds
+from nerfactory.datamanagers.structs import SceneBounds
from nerfactory.fields.base import Field
from nerfactory.fields.modules.encoding import Encoding, HashEncoding, SHEncoding
from nerfactory.fields.modules.field_heads import FieldHeadNames
diff --git a/nerfactory/models/base.py b/nerfactory/models/base.py
index 402c896e7..e5207d88a 100644
--- a/nerfactory/models/base.py
+++ b/nerfactory/models/base.py
@@ -28,7 +28,7 @@ from torch.nn import Parameter
from nerfactory.cameras.rays import RayBundle
from nerfactory.configs import base as cfg
-from nerfactory.dataloaders.structs import SceneBounds
+from nerfactory.datamanagers.structs import SceneBounds
from nerfactory.fields.density_fields.density_grid import DensityGrid
from nerfactory.utils.callbacks import TrainingCallback, TrainingCallbackAttributes
from nerfactory.utils.misc import get_masked_dict, is_not_none
diff --git a/nerfactory/models/modules/scene_colliders.py b/nerfactory/models/modules/scene_colliders.py
index df3f550fe..8c316f470 100644
--- a/nerfactory/models/modules/scene_colliders.py
+++ b/nerfactory/models/modules/scene_colliders.py
@@ -23,7 +23,7 @@ from torch import nn
from torchtyping import TensorType
from nerfactory.cameras.rays import RayBundle
-from nerfactory.dataloaders.structs import SceneBounds
+from nerfactory.datamanagers.structs import SceneBounds
class SceneBoundsCollider(nn.Module):
diff --git a/nerfactory/models/semantic_nerf.py b/nerfactory/models/semantic_nerf.py
index 8a84363ab..09a1d0eed 100644
--- a/nerfactory/models/semantic_nerf.py
+++ b/nerfactory/models/semantic_nerf.py
@@ -22,7 +22,7 @@ from torch import nn
from nerfactory.cameras.rays import RayBundle
from nerfactory.configs import base as cfg
-from nerfactory.dataloaders.structs import Semantics
+from nerfactory.datamanagers.structs import Semantics
from nerfactory.fields.modules.encoding import NeRFEncoding
from nerfactory.fields.modules.field_heads import FieldHeadNames
from nerfactory.fields.nerf_field import NeRFField
diff --git a/nerfactory/pipelines/base.py b/nerfactory/pipelines/base.py
index 038866049..1334195f7 100644
--- a/nerfactory/pipelines/base.py
+++ b/nerfactory/pipelines/base.py
@@ -27,7 +27,7 @@ from torch.nn import Parameter
from torch.nn.parallel import DistributedDataParallel as DDP
from nerfactory.configs import base as cfg
-from nerfactory.dataloaders.base import DataManager
+from nerfactory.datamanagers.base import DataManager
from nerfactory.models.base import Model
from nerfactory.utils import profiler
from nerfactory.utils.callbacks import TrainingCallback, TrainingCallbackAttributes
@@ -66,11 +66,11 @@ class Pipeline(nn.Module):
Args:
model: The model to be used in the pipeline.
- data_manager: The data_manager to be used in the pipeline.
+ datamanager: The datamanager to be used in the pipeline.
loss_coefficients: A dictionary of loss coefficients that will be used
Attributes:
- self.data_manager (DataManager): The data manager that will be used
+ self.datamanager (DataManager): The data manager that will be used
self.model (Model): The model that will be used
"""
@@ -78,13 +78,13 @@ class Pipeline(nn.Module):
self, config: cfg.PipelineConfig, device: str, test_mode: bool = False, world_size: int = 1, local_rank: int = 0
):
super().__init__()
- self.data_manager: DataManager = config.data_manager.setup(
+ self.datamanager: DataManager = config.datamanager.setup(
device=device, test_mode=test_mode, world_size=world_size, local_rank=local_rank
)
- self.data_manager.to(device)
+ self.datamanager.to(device)
# TODO(ethan): get rid of scene_bounds from the model
- assert self.data_manager.train_datasetinputs is not None, "Missing DatasetInputs"
- self.model: Model = config.model.setup(scene_bounds=self.data_manager.train_datasetinputs.scene_bounds)
+ assert self.datamanager.train_input_dataset is not None, "Missing input dataset"
+ self.model: Model = config.model.setup(scene_bounds=self.datamanager.train_input_dataset.inputs.scene_bounds)
self.model.to(device)
self.world_size = world_size
@@ -106,8 +106,8 @@ class Pipeline(nn.Module):
getting the next batch of data from the DataManager and interfacing with the
Model class, feeding the data to the model's forward function."""
if self.world_size > 1:
- self.data_manager.sampler.set_epoch(step)
- ray_bundle, batch = self.data_manager.next_train()
+ self.datamanager.sampler.set_epoch(step)
+ ray_bundle, batch = self.datamanager.next_train()
model_outputs, loss_dict, metrics_dict = self.model(ray_bundle, batch)
return model_outputs, loss_dict, metrics_dict
@@ -118,8 +118,8 @@ class Pipeline(nn.Module):
from the DataManager and feed it to the model's forward function"""
self.eval()
# NOTE(ethan): next_eval() is not being used right now
- assert self.data_manager.eval_dataloader is not None
- for camera_ray_bundle, batch in self.data_manager.eval_dataloader:
+ assert self.datamanager.eval_dataloader is not None
+ for camera_ray_bundle, batch in self.datamanager.eval_dataloader:
assert camera_ray_bundle.camera_indices is not None
image_idx = int(camera_ray_bundle.camera_indices[0, 0])
if self.world_size > 1:
@@ -144,12 +144,12 @@ class Pipeline(nn.Module):
self, training_callback_attributes: TrainingCallbackAttributes
) -> List[TrainingCallback]:
"""Returns the training callbacks from both the Dataloader and the Model."""
- data_manager_callbacks = self.data_manager.get_training_callbacks(training_callback_attributes)
+ datamanager_callbacks = self.datamanager.get_training_callbacks(training_callback_attributes)
if self.world_size > 1:
model_callbacks = self.model.module.get_training_callbacks(training_callback_attributes)
else:
model_callbacks = self.model.get_training_callbacks(training_callback_attributes)
- callbacks = data_manager_callbacks + model_callbacks
+ callbacks = datamanager_callbacks + model_callbacks
return callbacks
def get_param_groups(self) -> Dict[str, List[Parameter]]:
@@ -158,10 +158,10 @@ class Pipeline(nn.Module):
Returns:
A list of dictionaries containing the pipeline's param groups.
"""
- data_manager_params = self.data_manager.get_param_groups()
+ datamanager_params = self.datamanager.get_param_groups()
if self.world_size > 1:
model_params = self.model.module.get_param_groups()
else:
model_params = self.model.get_param_groups()
# TODO(ethan): assert that key names don't overlap
- return {**data_manager_params, **model_params}
+ return {**datamanager_params, **model_params}
diff --git a/nerfactory/viewer/server/viewer_utils.py b/nerfactory/viewer/server/viewer_utils.py
index 47ee70445..f0d2f0ab0 100644
--- a/nerfactory/viewer/server/viewer_utils.py
+++ b/nerfactory/viewer/server/viewer_utils.py
@@ -28,8 +28,7 @@ import torch
from nerfactory.cameras.cameras import Cameras
from nerfactory.cameras.rays import RayBundle
from nerfactory.configs import base as cfg
-from nerfactory.dataloaders.image_dataset import ImageDataset
-from nerfactory.dataloaders.structs import DatasetInputs
+from nerfactory.datamanagers.datasets import InputDataset
from nerfactory.models.base import Model
from nerfactory.utils import profiler, visualization
from nerfactory.utils.decorators import check_visualizer_enabled, decorate_all
@@ -230,12 +229,11 @@ class VisualizerState:
self.outputs_set = False
- def init_scene(self, image_dataset: ImageDataset, dataset_inputs: DatasetInputs) -> None:
+ def init_scene(self, dataset: InputDataset) -> None:
"""Draw some images and the scene aabb in the viewer.
Args:
- image_dataset: dataset to render in the scene
- dataset_inputs: inputs to the image dataset and ray generator
+ dataset: dataset to render in the scene
"""
# clear the current scene
@@ -243,15 +241,15 @@ class VisualizerState:
self.vis["sceneState/cameras"].delete()
# draw the training cameras and images
- image_indices = range(len(image_dataset))
+ image_indices = range(len(dataset))
for idx in image_indices:
- image = image_dataset[idx]["image"]
+ image = dataset[idx]["image"]
bgr = image[..., [2, 1, 0]]
- camera_json = dataset_inputs.cameras.to_json(camera_idx=idx, image=bgr, resize_shape=(100, 100))
+ camera_json = dataset.inputs.cameras.to_json(camera_idx=idx, image=bgr, resize_shape=(100, 100))
self.vis[f"sceneState/cameras/{idx:06d}"].write(camera_json)
# draw the scene bounds (i.e., the bounding box)
- json_ = dataset_inputs.scene_bounds.to_json()
+ json_ = dataset.inputs.scene_bounds.to_json()
self.vis["sceneState/sceneBounds"].write(json_)
# set the properties of the camera
diff --git a/scripts/benchmarking/launch_train_blender.sh b/scripts/benchmarking/launch_train_blender.sh
index a28373d79..d77a8c013 100755
--- a/scripts/benchmarking/launch_train_blender.sh
+++ b/scripts/benchmarking/launch_train_blender.sh
@@ -55,7 +55,7 @@ for dataset in ${DATASETS[@]}; do
python scripts/run_train.py \
${config_name} \
--logging.writer.2.no-enable \
- --pipeline.data_manager.train-dataset.data-directory=data/blender/${dataset} \
+ --pipeline.datamanager.train-dataset.data-directory=data/blender/${dataset} \
--experiment-name=blender_${dataset}_${tag} \
--trainer.model-dir=nerfactory_models/ \
--trainer.steps-per-save=16000 \
diff --git a/scripts/run_eval.py b/scripts/run_eval.py
index bb59b4fbc..498b47d20 100644
--- a/scripts/run_eval.py
+++ b/scripts/run_eval.py
@@ -79,7 +79,7 @@ def _render_stats_dict(pipeline: Pipeline) -> Dict[str, float]:
avg_psnr = 0
avg_rays_per_sec = 0
avg_fps = 0
- for step, (camera_ray_bundle, batch) in tqdm(enumerate(pipeline.data_manager.eval_dataloader)):
+ for step, (camera_ray_bundle, batch) in tqdm(enumerate(pipeline.datamanager.eval_dataloader)):
with TimeWriter(writer=None, name=None, write=False) as t:
with torch.no_grad():
image_idx = int(camera_ray_bundle.camera_indices[0, 0])
@@ -142,7 +142,7 @@ def _eval_setup(config_path: Path) -> Tuple[cfg.Config, Pipeline, Path]:
# load checkpoints from wherever they were saved
# TODO: expose the ability to choose an arbitrary checkpoint
config.trainer.load_dir = config.trainer.model_dir
- config.pipeline.data_manager.eval_image_indices = None
+ config.pipeline.datamanager.eval_image_indices = None
# setup pipeline (which includes the DataManager)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
@@ -210,11 +210,11 @@ class RenderTrajectory:
# TODO(ethan): use camera information from parsing args
if self.traj == "spiral":
- camera_start = pipeline.data_manager.eval_dataloader.get_camera(image_idx=0)
+ camera_start = pipeline.datamanager.eval_dataloader.get_camera(image_idx=0)
# TODO(ethan): pass in the up direction of the camera
camera_path = get_spiral_path(camera_start, steps=30, radius=0.1)
elif self.traj == "interp":
- cameras = pipeline.data_manager.eval_dataloader.get_camera(image_idx=[0, 10])
+ cameras = pipeline.datamanager.eval_dataloader.get_camera(image_idx=[0, 10])
camera_path = get_interpolated_camera_path(cameras, steps=30)
else:
assert_never(self.traj)
@@ -225,7 +225,7 @@ class RenderTrajectory:
output_filename=self.output_path,
rendered_output_name=self.rendered_output_name,
rendered_resolution_scaling_factor=self.rendered_resolution_scaling_factor,
- num_rays_per_chunk=config.pipeline.data_manager.eval_num_rays_per_chunk,
+ num_rays_per_chunk=config.pipeline.datamanager.eval_num_rays_per_chunk,
)
diff --git a/tests/test_run_train.py b/tests/test_run_train.py
index 0e2a5774d..637c14313 100644
--- a/tests/test_run_train.py
+++ b/tests/test_run_train.py
@@ -21,15 +21,14 @@ def set_reduced_config(config: cfg.Config):
config.machine.num_gpus = 0
config.trainer.max_num_iterations = 2
# reduce dataset factors; set dataset to test
- # switch to using the vanilla ImageDataset class
- config.pipeline.data_manager.image_dataset_type = "rgb"
-
- config.pipeline.data_manager.train_dataset = cfg.BlenderDataParserConfig(
+ config.pipeline.datamanager.train_dataparser = cfg.BlenderDataParserConfig(
+ data_directory=Path("tests/data/lego_test")
+ )
+ config.pipeline.datamanager.train_num_images_to_sample_from = 1
+ config.pipeline.datamanager.train_num_rays_per_batch = 4
+ config.pipeline.datamanager.eval_dataparser = cfg.BlenderDataParserConfig(
data_directory=Path("tests/data/lego_test")
)
- config.pipeline.data_manager.train_num_images_to_sample_from = 1
- config.pipeline.data_manager.train_num_rays_per_batch = 4
- config.pipeline.data_manager.eval_dataset = cfg.BlenderDataParserConfig(data_directory=Path("tests/data/lego_test"))
# reduce model factors
config.pipeline.model.num_coarse_samples = 4