diff --git a/.vscode/launch.json b/.vscode/launch.json index 9f626f025..9d48fe768 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,6 +11,13 @@ "url": "http://localhost:4000", "webRoot": "${workspaceFolder}/nerfactory/viewer/app" }, + { + "name": "Python: Attach using Process ID", + "type": "python", + "request": "attach", + "processId": "${command:pickProcess}", + "justMyCode": true + }, { "name": "Python: Current File", "type": "python", @@ -26,7 +33,9 @@ "program": "scripts/run_train.py", "console": "integratedTerminal", "args": [ - "vanilla_nerf" + "instant_ngp", + "--viewer.no-enable", + "--logging.event-writer=wandb" ] }, { @@ -47,15 +56,16 @@ "console": "integratedTerminal", "args": [ "instant_ngp", - "--experiment_name=nerfactory_experiment", - "--logging.writer.2.max-log-size=0", + // "--experiment_name=nerfactory_experiment", + "--logging.local-writer.max-log-size=0", + "--viewer.no-enable" // viewer // "--viewer.enable", // "--viewer.zmq-url=tcp://127.0.0.1:6001", // "--viewer.websocket-port=8014", // "--pipeline.model.randomize-background", // "--pipeline.model.cone-angle=0.00390625", - "nerfactory-data" + // "nerfactory-data" ] }, { diff --git a/nerfactory/configs/base.py b/nerfactory/configs/base.py index 7b073deab..08913ce53 100644 --- a/nerfactory/configs/base.py +++ b/nerfactory/configs/base.py @@ -22,22 +22,14 @@ from datetime import datetime from pathlib import Path from typing import Any, Dict, List, Literal, Optional, Tuple, Type -import dcargs import torch import yaml from rich.console import Console from nerfactory.configs.utils import to_immutable_dict -# data instances -from nerfactory.datamanagers.base import VanillaDataManager - # model instances -from nerfactory.models.base import Model -from nerfactory.models.nerfw import NerfWModel -from nerfactory.models.tensorf import TensoRFModel from nerfactory.optimizers.schedulers import ExponentialDecaySchedule -from nerfactory.pipelines.base import VanillaPipeline from nerfactory.utils import writer @@ -163,153 +155,6 @@ class TrainerConfig(PrintableConfig): """optionally specify a pre-defined config to load from""" -# pylint: disable=wrong-import-position -from nerfactory.datamanagers.dataparsers.blender_parser import BlenderDataParserConfig -from nerfactory.datamanagers.dataparsers.friends_parser import FriendsDataParserConfig -from nerfactory.datamanagers.dataparsers.instant_ngp_parser import ( - InstantNGPDataParserConfig, -) -from nerfactory.datamanagers.dataparsers.mipnerf_parser import ( - MipNerf360DataParserConfig, -) -from nerfactory.datamanagers.dataparsers.nerfactory_parser import ( - NerfactoryDataParserConfig, -) -from nerfactory.datamanagers.dataparsers.record3d_parser import Record3DDataParserConfig - -AnnotatedDataParserUnion = dcargs.extras.subcommand_type_from_defaults( - { - "nerfactory-data": NerfactoryDataParserConfig(), - "blender-data": BlenderDataParserConfig(), - "friends-data": FriendsDataParserConfig(), - "mipnerf-360-data": MipNerf360DataParserConfig(), - "instant-ngp-data": InstantNGPDataParserConfig(), - "record3d-data": Record3DDataParserConfig(), - }, - prefix_names=False, -) -"""Union over possible dataparser types, annotated with metadata for dcargs. This is the -same as the vanilla union, but results in shorter subcommand names.""" - - -@dataclass -class VanillaDataManagerConfig(InstantiateConfig): - """Configuration for data manager instantiation; DataManager is in charge of keeping the train/eval dataparsers; - After instantiation, data manager holds both train/eval datasets and is in charge of returning unpacked - train/eval data at each iteration - """ - - _target: Type = VanillaDataManager - """target class to instantiate""" - dataparser: AnnotatedDataParserUnion = BlenderDataParserConfig() - """specifies the dataparser used to unpack the data""" - train_num_rays_per_batch: int = 1024 - """number of rays per batch to use per training iteration""" - train_num_images_to_sample_from: int = -1 - """number of images to sample during training iteration""" - eval_num_rays_per_batch: int = 1024 - """number of rays per batch to use per eval iteration""" - eval_num_images_to_sample_from: int = -1 - """number of images to sample during eval iteration""" - eval_image_indices: Optional[Tuple[int, ...]] = (0,) - """specifies the image indices to use during eval; if None, uses all""" - - -# Model related configs -@dataclass -class ModelConfig(InstantiateConfig): - """Configuration for model instantiation""" - - _target: Type = Model - """target class to instantiate""" - enable_collider: bool = True - """Whether to create a scene collider to filter rays.""" - collider_params: Optional[Dict[str, float]] = to_immutable_dict({"near_plane": 2.0, "far_plane": 6.0}) - """parameters to instantiate scene collider with""" - loss_coefficients: Dict[str, float] = to_immutable_dict({"rgb_loss_coarse": 1.0, "rgb_loss_fine": 1.0}) - """Loss specific weights.""" - enable_density_field: bool = False - """Whether to create a density field to filter samples.""" - density_field_params: Dict[str, Any] = to_immutable_dict( - { - "center": 0.0, # simply set it as the center of the scene bbox - "base_scale": 3.0, # simply set it as the scale of the scene bbox - "num_cascades": 1, # if using more than 1 cascade, the `base_scale` can be smaller than scene scale. - "resolution": 128, - "update_every_num_iters": 16, - } - ) - """parameters to instantiate density field with""" - eval_num_rays_per_chunk: int = 4096 - """specifies number of rays per chunk during eval""" - - -@dataclass -class VanillaModelConfig(ModelConfig): - """Vanilla Model Config""" - - num_coarse_samples: int = 64 - """Number of samples in coarse field evaluation""" - num_importance_samples: int = 128 - """Number of samples in fine field evaluation""" - - -@dataclass -class NerfWModelConfig(VanillaModelConfig): - """NerfW model config""" - - _target: Type = NerfWModel - """target class to instantiate""" - loss_coefficients: Dict[str, float] = to_immutable_dict( - {"rgb_loss_coarse": 1.0, "rgb_loss_fine": 1.0, "uncertainty_loss": 1.0, "density_loss": 0.01} - ) - """Loss specific weights.""" - num_coarse_samples: int = 64 - """Number of samples in coarse field evaluation.""" - num_importance_samples: int = 64 - """Number of samples in fine field evaluation.""" - uncertainty_min: float = 0.03 - """This is added to the end of the uncertainty - rendering operation. It's called 'beta_min' in other repos. - This avoids calling torch.log() on a zero value, which would be undefined. - """ - num_images: int = 10000 # TODO: don't hardcode this - """How many images exist in the dataset.""" - appearance_embedding_dim: int = 48 - """Dimension of appearance embedding.""" - transient_embedding_dim: int = 16 - """Dimension of transient embedding.""" - - -@dataclass -class TensoRFModelConfig(VanillaModelConfig): - """TensoRF model config""" - - _target: Type = TensoRFModel - """target class to instantiate""" - init_resolution: int = 128 - """initial render resolution""" - final_resolution: int = 200 - """final render resolution""" - upsampling_iters: Tuple[int, ...] = (5000, 5500, 7000) - """specifies a list of iteration step numbers to perform upsampling""" - loss_coefficients: Dict[str, float] = to_immutable_dict({"rgb_loss_coarse": 1.0, "feature_loss": 8e-5}) - """Loss specific weights.""" - - -# Pipeline related configs -@dataclass -class VanillaPipelineConfig(InstantiateConfig): - """Configuration for pipeline instantiation""" - - _target: Type = VanillaPipeline - """target class to instantiate""" - datamanager: VanillaDataManagerConfig = VanillaDataManagerConfig() - """specifies the datamanager config""" - model: ModelConfig = ModelConfig() - """specifies the model config""" - - # Viewer related configs @dataclass class ViewerConfig(PrintableConfig): @@ -379,6 +224,10 @@ class SchedulerConfig(InstantiateConfig): return self._target(optimizer, lr_init, self.lr_final, self.max_steps) +# pylint: disable=wrong-import-position +from nerfactory.pipelines.base import VanillaPipelineConfig + + @dataclass class Config(PrintableConfig): """Full config contents""" diff --git a/nerfactory/configs/base_configs.py b/nerfactory/configs/base_configs.py index 6bc0dff48..ab154dce4 100644 --- a/nerfactory/configs/base_configs.py +++ b/nerfactory/configs/base_configs.py @@ -16,6 +16,8 @@ Put all the method implementations in one location. """ +from __future__ import annotations + import copy from typing import Dict, Type @@ -24,27 +26,30 @@ from typeguard import typeguard_ignore from nerfactory.configs.base import ( AdamOptimizerConfig, - BlenderDataParserConfig, Config, LoggingConfig, - MipNerf360DataParserConfig, - NerfWModelConfig, RAdamOptimizerConfig, SchedulerConfig, - TensoRFModelConfig, TrainerConfig, - VanillaDataManagerConfig, - VanillaModelConfig, - VanillaPipelineConfig, ViewerConfig, ) +from nerfactory.datamanagers.base import VanillaDataManagerConfig +from nerfactory.datamanagers.dataparsers.blender_parser import BlenderDataParserConfig from nerfactory.datamanagers.dataparsers.friends_parser import FriendsDataParserConfig +from nerfactory.datamanagers.dataparsers.mipnerf_parser import ( + MipNerf360DataParserConfig, +) +from nerfactory.models.base import VanillaModelConfig from nerfactory.models.compound import CompoundModelConfig from nerfactory.models.instant_ngp import InstantNGPModelConfig from nerfactory.models.mipnerf import MipNerfModel from nerfactory.models.mipnerf_360 import MipNerf360Model +from nerfactory.models.nerfw import NerfWModelConfig from nerfactory.models.semantic_nerf import SemanticNerfModel +from nerfactory.models.tensorf import TensoRFModelConfig from nerfactory.models.vanilla_nerf import NeRFModel +from nerfactory.pipelines.base import VanillaPipelineConfig +from nerfactory.pipelines.dynamic_batch import DynamicBatchPipelineConfig base_configs: Dict[str, Config] = {} base_configs["compound"] = Config( @@ -67,7 +72,7 @@ base_configs["compound"] = Config( base_configs["instant-ngp"] = Config( method_name="instant-ngp", trainer=TrainerConfig(steps_per_eval_batch=500, steps_per_save=2000, mixed_precision=True), - pipeline=VanillaPipelineConfig( + pipeline=DynamicBatchPipelineConfig( datamanager=VanillaDataManagerConfig(dataparser=BlenderDataParserConfig(), train_num_rays_per_batch=8192), model=InstantNGPModelConfig(eval_num_rays_per_chunk=8192), ), diff --git a/nerfactory/datamanagers/base.py b/nerfactory/datamanagers/base.py index c67aef23e..29e853263 100644 --- a/nerfactory/datamanagers/base.py +++ b/nerfactory/datamanagers/base.py @@ -18,26 +18,54 @@ Data loader. from __future__ import annotations from abc import abstractmethod -from typing import Dict, List, Optional, Tuple, Union +from dataclasses import dataclass, field +from typing import Dict, List, Optional, Tuple, Type, Union +import dcargs import torch from torch import nn from torch.nn import Parameter from torch.utils.data.distributed import DistributedSampler from nerfactory.cameras.rays import RayBundle -from nerfactory.configs import base as cfg +from nerfactory.configs.base import InstantiateConfig from nerfactory.datamanagers.dataloaders import ( CacheDataloader, FixedIndicesEvalDataloader, RandIndicesEvalDataloader, ) +from nerfactory.datamanagers.dataparsers.blender_parser import BlenderDataParserConfig +from nerfactory.datamanagers.dataparsers.friends_parser import FriendsDataParserConfig +from nerfactory.datamanagers.dataparsers.instant_ngp_parser import ( + InstantNGPDataParserConfig, +) +from nerfactory.datamanagers.dataparsers.mipnerf_parser import ( + MipNerf360DataParserConfig, +) +from nerfactory.datamanagers.dataparsers.nerfactory_parser import ( + NerfactoryDataParserConfig, +) +from nerfactory.datamanagers.dataparsers.record3d_parser import Record3DDataParserConfig 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 +AnnotatedDataParserUnion = dcargs.extras.subcommand_type_from_defaults( + { + "nerfactory-data": NerfactoryDataParserConfig(), + "blender-data": BlenderDataParserConfig(), + "friends-data": FriendsDataParserConfig(), + "mipnerf-360-data": MipNerf360DataParserConfig(), + "instant-ngp-data": InstantNGPDataParserConfig(), + "record3d-data": Record3DDataParserConfig(), + }, + prefix_names=False, +) +"""Union over possible dataparser types, annotated with metadata for dcargs. This is the +same as the vanilla union, but results in shorter subcommand names.""" + class DataManager(nn.Module): """Generic data manager's abstract class @@ -201,6 +229,29 @@ class DataManager(nn.Module): return {} +@dataclass +class VanillaDataManagerConfig(InstantiateConfig): + """Configuration for data manager instantiation; DataManager is in charge of keeping the train/eval dataparsers; + After instantiation, data manager holds both train/eval datasets and is in charge of returning unpacked + train/eval data at each iteration + """ + + _target: Type = field(default_factory=lambda: VanillaDataManager) + """target class to instantiate""" + dataparser: AnnotatedDataParserUnion = BlenderDataParserConfig() + """specifies the dataparser used to unpack the data""" + train_num_rays_per_batch: int = 1024 + """number of rays per batch to use per training iteration""" + train_num_images_to_sample_from: int = -1 + """number of images to sample during training iteration""" + eval_num_rays_per_batch: int = 1024 + """number of rays per batch to use per eval iteration""" + eval_num_images_to_sample_from: int = -1 + """number of images to sample during eval iteration""" + eval_image_indices: Optional[Tuple[int, ...]] = (0,) + """specifies the image indices to use during eval; if None, uses all""" + + class VanillaDataManager(DataManager): # pylint: disable=abstract-method """Basic stored data manager implementation. @@ -214,11 +265,11 @@ class VanillaDataManager(DataManager): # pylint: disable=abstract-method config: the DataManagerConfig used to instantiate class """ - config: cfg.VanillaDataManagerConfig + config: VanillaDataManagerConfig def __init__( self, - config: cfg.VanillaDataManagerConfig, + config: VanillaDataManagerConfig, device: Union[torch.device, str] = "cpu", test_mode: bool = False, world_size: int = 1, diff --git a/nerfactory/datamanagers/pixel_sampler.py b/nerfactory/datamanagers/pixel_sampler.py index 4d1beaa89..c0934140c 100644 --- a/nerfactory/datamanagers/pixel_sampler.py +++ b/nerfactory/datamanagers/pixel_sampler.py @@ -87,6 +87,14 @@ class PixelSampler: # pylint: disable=too-few-public-methods self.num_rays_per_batch = num_rays_per_batch self.keep_full_image = keep_full_image + def set_num_rays_per_batch(self, num_rays_per_batch: int): + """Set the number of rays to sample per batch. + + Args: + num_rays_per_batch: number of rays to sample per batch + """ + self.num_rays_per_batch = num_rays_per_batch + def sample(self, image_batch: Dict): """Sample an image batch and return a pixel batch. diff --git a/nerfactory/engine/trainer.py b/nerfactory/engine/trainer.py index 3f3ba7923..7f65642b5 100644 --- a/nerfactory/engine/trainer.py +++ b/nerfactory/engine/trainer.py @@ -322,7 +322,7 @@ class Trainer: step: Current training step. """ # a batch of eval rays - if step_check(step, self.config.trainer.steps_per_eval_batch, run_at_zero=True): + if step_check(step, self.config.trainer.steps_per_eval_batch): _, eval_loss_dict, eval_metrics_dict = self.pipeline.get_eval_loss_dict(step=step) eval_loss = functools.reduce(torch.add, eval_loss_dict.values()) writer.put_scalar(name="Eval Loss", scalar=eval_loss, step=step) diff --git a/nerfactory/models/base.py b/nerfactory/models/base.py index d13ce3a3a..82ea54359 100644 --- a/nerfactory/models/base.py +++ b/nerfactory/models/base.py @@ -20,14 +20,16 @@ from __future__ import annotations from abc import abstractmethod from collections import defaultdict -from typing import Any, Dict, List, Optional, Tuple +from dataclasses import dataclass, field +from typing import Any, Dict, List, Optional, Tuple, Type import torch from torch import nn from torch.nn import Parameter from nerfactory.cameras.rays import RayBundle -from nerfactory.configs import base as cfg +from nerfactory.configs.base import InstantiateConfig +from nerfactory.configs.utils import to_immutable_dict from nerfactory.datamanagers.structs import SceneBounds from nerfactory.fields.density_fields.density_grid import DensityGrid from nerfactory.models.modules.scene_colliders import NearFarCollider @@ -35,6 +37,35 @@ from nerfactory.utils.callbacks import TrainingCallback, TrainingCallbackAttribu from nerfactory.utils.misc import get_masked_dict +# Model related configs +@dataclass +class ModelConfig(InstantiateConfig): + """Configuration for model instantiation""" + + _target: Type = field(default_factory=lambda: Model) + """target class to instantiate""" + enable_collider: bool = True + """Whether to create a scene collider to filter rays.""" + collider_params: Optional[Dict[str, float]] = to_immutable_dict({"near_plane": 2.0, "far_plane": 6.0}) + """parameters to instantiate scene collider with""" + loss_coefficients: Dict[str, float] = to_immutable_dict({"rgb_loss_coarse": 1.0, "rgb_loss_fine": 1.0}) + """Loss specific weights.""" + enable_density_field: bool = False + """Whether to create a density field to filter samples.""" + density_field_params: Dict[str, Any] = to_immutable_dict( + { + "center": 0.0, # simply set it as the center of the scene bbox + "base_scale": 3.0, # simply set it as the scale of the scene bbox + "num_cascades": 1, # if using more than 1 cascade, the `base_scale` can be smaller than scene scale. + "resolution": 128, + "update_every_num_iters": 16, + } + ) + """parameters to instantiate density field with""" + eval_num_rays_per_chunk: int = 4096 + """specifies number of rays per chunk during eval""" + + class Model(nn.Module): """Model class Where everything (Fields, Optimizers, Samplers, Visualization, etc) is linked together. This should be @@ -45,11 +76,11 @@ class Model(nn.Module): scene_bounds: dataset scene bounds """ - config: cfg.ModelConfig + config: ModelConfig def __init__( self, - config: cfg.ModelConfig, + config: ModelConfig, scene_bounds: SceneBounds, num_train_data: int, **kwargs, @@ -216,3 +247,13 @@ class Model(nn.Module): """ state = {key.replace("module.", ""): value for key, value in loaded_state["model"].items()} self.load_state_dict(state) # type: ignore + + +@dataclass +class VanillaModelConfig(ModelConfig): + """Vanilla Model Config""" + + num_coarse_samples: int = 64 + """Number of samples in coarse field evaluation""" + num_importance_samples: int = 128 + """Number of samples in fine field evaluation""" diff --git a/nerfactory/models/compound.py b/nerfactory/models/compound.py index 8110ad02c..3f9106ac4 100644 --- a/nerfactory/models/compound.py +++ b/nerfactory/models/compound.py @@ -39,10 +39,9 @@ from torchmetrics.functional import structural_similarity_index_measure from torchmetrics.image.lpip import LearnedPerceptualImagePatchSimilarity from nerfactory.cameras.rays import RayBundle -from nerfactory.configs import base as cfg from nerfactory.fields.compound_field import field_implementation_to_class from nerfactory.fields.modules.field_heads import FieldHeadNames -from nerfactory.models.base import Model +from nerfactory.models.base import Model, ModelConfig from nerfactory.models.modules.ray_sampler import VolumetricSampler from nerfactory.optimizers.loss import MSELoss from nerfactory.renderers.renderers import ( @@ -59,7 +58,7 @@ from nerfactory.utils.callbacks import ( @dataclass -class CompoundModelConfig(cfg.ModelConfig): +class CompoundModelConfig(ModelConfig): """Compound Model Config""" _target: Type = field( diff --git a/nerfactory/models/instant_ngp.py b/nerfactory/models/instant_ngp.py index 9f2cb7df6..2cd92283b 100644 --- a/nerfactory/models/instant_ngp.py +++ b/nerfactory/models/instant_ngp.py @@ -29,10 +29,9 @@ from torchmetrics.functional import structural_similarity_index_measure from torchmetrics.image.lpip import LearnedPerceptualImagePatchSimilarity from nerfactory.cameras.rays import RayBundle -from nerfactory.configs import base as cfg from nerfactory.fields.instant_ngp_field import field_implementation_to_class from nerfactory.fields.modules.field_heads import FieldHeadNames -from nerfactory.models.base import Model +from nerfactory.models.base import Model, ModelConfig from nerfactory.models.modules.ray_sampler import VolumetricSampler from nerfactory.optimizers.loss import MSELoss from nerfactory.renderers.renderers import ( @@ -49,7 +48,7 @@ from nerfactory.utils.callbacks import ( @dataclass -class InstantNGPModelConfig(cfg.ModelConfig): +class InstantNGPModelConfig(ModelConfig): """Instant NGP Model Config""" _target: Type = field( @@ -62,7 +61,7 @@ class InstantNGPModelConfig(cfg.ModelConfig): """Instant NGP doesn't use a collider.""" field_implementation: Literal["torch", "tcnn"] = "tcnn" # torch, tcnn, ... """one of "torch" or "tcnn", or other fields in 'field_implementation_to_class'""" - num_samples: int = 1024 + max_num_samples_per_ray: int = 1024 """Number of samples in field evaluation.""" grid_resolution: int = 128 """Resolution of the grid used for the field.""" @@ -102,7 +101,7 @@ class NGPModel(Model): aabb=self.scene_aabb, density_fn=self.field.density_fn, grid_resolution=self.config.grid_resolution, - num_samples=self.config.num_samples, + num_samples=self.config.max_num_samples_per_ray, ) # renderers @@ -174,6 +173,7 @@ class NGPModel(Model): "accumulation": accumulation, "depth": depth, "alive_ray_mask": alive_ray_mask, # the rays we kept from sampler + "num_samples_per_ray": packed_info[:, 1], } return outputs @@ -181,6 +181,7 @@ class NGPModel(Model): image = batch["image"].to(self.device) metrics_dict = {} metrics_dict["psnr"] = self.psnr(outputs["rgb"], image) + metrics_dict["num_samples_per_batch"] = outputs["num_samples_per_ray"].sum() return metrics_dict def get_loss_dict(self, outputs, batch, metrics_dict=None): diff --git a/nerfactory/models/mipnerf.py b/nerfactory/models/mipnerf.py index 876145564..5a08addfe 100644 --- a/nerfactory/models/mipnerf.py +++ b/nerfactory/models/mipnerf.py @@ -26,11 +26,10 @@ from torchmetrics.functional import structural_similarity_index_measure from torchmetrics.image.lpip import LearnedPerceptualImagePatchSimilarity from nerfactory.cameras.rays import RayBundle -from nerfactory.configs import base as cfg from nerfactory.fields.modules.encoding import NeRFEncoding from nerfactory.fields.modules.field_heads import FieldHeadNames from nerfactory.fields.nerf_field import NeRFField -from nerfactory.models.base import Model +from nerfactory.models.base import Model, ModelConfig from nerfactory.models.modules.ray_sampler import PDFSampler, UniformSampler from nerfactory.optimizers.loss import MSELoss from nerfactory.renderers.renderers import ( @@ -50,7 +49,7 @@ class MipNerfModel(Model): def __init__( self, - config: cfg.ModelConfig, + config: ModelConfig, **kwargs, ) -> None: self.field = None diff --git a/nerfactory/models/mipnerf_360.py b/nerfactory/models/mipnerf_360.py index e49b548db..9390b1e82 100644 --- a/nerfactory/models/mipnerf_360.py +++ b/nerfactory/models/mipnerf_360.py @@ -27,12 +27,11 @@ from torchmetrics.functional import structural_similarity_index_measure from torchmetrics.image.lpip import LearnedPerceptualImagePatchSimilarity from nerfactory.cameras.rays import RayBundle -from nerfactory.configs import base as cfg from nerfactory.fields.modules.encoding import NeRFEncoding from nerfactory.fields.modules.field_heads import FieldHeadNames from nerfactory.fields.modules.spatial_distortions import SceneContraction from nerfactory.fields.nerf_field import NeRFField -from nerfactory.models.base import Model +from nerfactory.models.base import Model, ModelConfig from nerfactory.models.modules.ray_losses import distortion_loss from nerfactory.models.modules.ray_sampler import PDFSampler, UniformSampler from nerfactory.optimizers.loss import MSELoss @@ -53,7 +52,7 @@ class MipNerf360Model(Model): def __init__( self, - config: cfg.ModelConfig, + config: ModelConfig, **kwargs, ) -> None: self.field = None diff --git a/nerfactory/models/nerfw.py b/nerfactory/models/nerfw.py index 73c555c17..bbbae4586 100644 --- a/nerfactory/models/nerfw.py +++ b/nerfactory/models/nerfw.py @@ -18,7 +18,8 @@ NeRF-W (NeRF in the wild) implementation. from __future__ import annotations -from typing import Dict, Tuple +from dataclasses import dataclass, field +from typing import Dict, Tuple, Type import torch from torchmetrics import PeakSignalNoiseRatio @@ -26,12 +27,12 @@ from torchmetrics.functional import structural_similarity_index_measure from torchmetrics.image.lpip import LearnedPerceptualImagePatchSimilarity from nerfactory.cameras.rays import RayBundle -from nerfactory.configs import base as cfg +from nerfactory.configs.utils import to_immutable_dict from nerfactory.fields.modules.encoding import NeRFEncoding from nerfactory.fields.modules.field_heads import FieldHeadNames from nerfactory.fields.nerf_field import NeRFField from nerfactory.fields.nerfw_field import VanillaNerfWField -from nerfactory.models.base import Model +from nerfactory.models.base import Model, VanillaModelConfig from nerfactory.models.modules.ray_sampler import PDFSampler, UniformSampler from nerfactory.models.modules.scene_colliders import AABBBoxCollider from nerfactory.optimizers.loss import MSELoss @@ -44,6 +45,33 @@ from nerfactory.renderers.renderers import ( from nerfactory.utils import colors, misc, visualization +@dataclass +class NerfWModelConfig(VanillaModelConfig): + """NerfW model config""" + + _target: Type = field(default_factory=lambda: NerfWModel) + """target class to instantiate""" + loss_coefficients: Dict[str, float] = to_immutable_dict( + {"rgb_loss_coarse": 1.0, "rgb_loss_fine": 1.0, "uncertainty_loss": 1.0, "density_loss": 0.01} + ) + """Loss specific weights.""" + num_coarse_samples: int = 64 + """Number of samples in coarse field evaluation.""" + num_importance_samples: int = 64 + """Number of samples in fine field evaluation.""" + uncertainty_min: float = 0.03 + """This is added to the end of the uncertainty + rendering operation. It's called 'beta_min' in other repos. + This avoids calling torch.log() on a zero value, which would be undefined. + """ + num_images: int = 10000 # TODO: don't hardcode this + """How many images exist in the dataset.""" + appearance_embedding_dim: int = 48 + """Dimension of appearance embedding.""" + transient_embedding_dim: int = 16 + """Dimension of transient embedding.""" + + class NerfWModel(Model): """NeRF-W model @@ -51,11 +79,11 @@ class NerfWModel(Model): config: NerfW configuration to instantiate model """ - config: cfg.NerfWModelConfig + config: NerfWModelConfig def __init__( self, - config: cfg.NerfWModelConfig, + config: NerfWModelConfig, **kwargs, ) -> None: """A NeRF-W model. diff --git a/nerfactory/models/semantic_nerf.py b/nerfactory/models/semantic_nerf.py index 786d750a0..c8cb2527e 100644 --- a/nerfactory/models/semantic_nerf.py +++ b/nerfactory/models/semantic_nerf.py @@ -23,12 +23,12 @@ import torch from torch import nn from nerfactory.cameras.rays import RayBundle -from nerfactory.configs import base as cfg 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 from nerfactory.fields.semantic_nerf_field import SemanticNerfField +from nerfactory.models.base import ModelConfig from nerfactory.models.modules.scene_colliders import AABBBoxCollider from nerfactory.models.vanilla_nerf import NeRFModel from nerfactory.renderers.renderers import SemanticRenderer @@ -43,7 +43,7 @@ class SemanticNerfModel(NeRFModel): semantics: additional semantics data info """ - def __init__(self, config: cfg.ModelConfig, semantics: Semantics, **kwargs) -> None: + def __init__(self, config: ModelConfig, semantics: Semantics, **kwargs) -> None: self.stuff_classes = semantics.stuff_classes self.stuff_colors = semantics.stuff_colors super().__init__(config=config, **kwargs) diff --git a/nerfactory/models/tensorf.py b/nerfactory/models/tensorf.py index e4025fd5e..fccfdcb40 100644 --- a/nerfactory/models/tensorf.py +++ b/nerfactory/models/tensorf.py @@ -18,7 +18,8 @@ TensorRF implementation. from __future__ import annotations -from typing import Dict, List, Tuple +from dataclasses import dataclass, field +from typing import Dict, List, Tuple, Type import numpy as np import torch @@ -28,11 +29,11 @@ from torchmetrics.functional import structural_similarity_index_measure from torchmetrics.image.lpip import LearnedPerceptualImagePatchSimilarity from nerfactory.cameras.rays import RayBundle -from nerfactory.configs import base as cfg +from nerfactory.configs.utils import to_immutable_dict from nerfactory.fields.modules.encoding import TensorVMEncoding from nerfactory.fields.modules.field_heads import FieldHeadNames from nerfactory.fields.nerf_field import NeRFField -from nerfactory.models.base import Model +from nerfactory.models.base import Model, VanillaModelConfig from nerfactory.models.modules.ray_sampler import PDFSampler, UniformSampler from nerfactory.optimizers.loss import L1Loss, MSELoss from nerfactory.optimizers.optimizers import Optimizers @@ -49,6 +50,22 @@ from nerfactory.utils.callbacks import ( ) +@dataclass +class TensoRFModelConfig(VanillaModelConfig): + """TensoRF model config""" + + _target: Type = field(default_factory=lambda: TensoRFModel) + """target class to instantiate""" + init_resolution: int = 128 + """initial render resolution""" + final_resolution: int = 200 + """final render resolution""" + upsampling_iters: Tuple[int, ...] = (5000, 5500, 7000) + """specifies a list of iteration step numbers to perform upsampling""" + loss_coefficients: Dict[str, float] = to_immutable_dict({"rgb_loss_coarse": 1.0, "feature_loss": 8e-5}) + """Loss specific weights.""" + + class TensoRFModel(Model): """TensoRF Model @@ -58,7 +75,7 @@ class TensoRFModel(Model): def __init__( self, - config: cfg.TensoRFModelConfig, + config: TensoRFModelConfig, **kwargs, ) -> None: self.init_resolution = config.init_resolution diff --git a/nerfactory/models/vanilla_nerf.py b/nerfactory/models/vanilla_nerf.py index c5c0cfb72..85ffbdfd6 100644 --- a/nerfactory/models/vanilla_nerf.py +++ b/nerfactory/models/vanilla_nerf.py @@ -27,11 +27,10 @@ from torchmetrics.functional import structural_similarity_index_measure from torchmetrics.image.lpip import LearnedPerceptualImagePatchSimilarity from nerfactory.cameras.rays import RayBundle -from nerfactory.configs import base as cfg from nerfactory.fields.modules.encoding import NeRFEncoding from nerfactory.fields.modules.field_heads import FieldHeadNames from nerfactory.fields.nerf_field import NeRFField -from nerfactory.models.base import Model +from nerfactory.models.base import Model, ModelConfig from nerfactory.models.modules.ray_sampler import PDFSampler, UniformSampler from nerfactory.optimizers.loss import MSELoss from nerfactory.renderers.renderers import ( @@ -56,7 +55,7 @@ class NeRFModel(Model): def __init__( self, - config: cfg.ModelConfig, + config: ModelConfig, **kwargs, ) -> None: self.field_coarse = None diff --git a/nerfactory/pipelines/base.py b/nerfactory/pipelines/base.py index 3d71f57b7..f2bf86c55 100644 --- a/nerfactory/pipelines/base.py +++ b/nerfactory/pipelines/base.py @@ -19,8 +19,9 @@ from __future__ import annotations import typing from abc import abstractmethod +from dataclasses import dataclass, field from time import time -from typing import Any, Dict, List, Optional, Union, cast +from typing import Any, Dict, List, Optional, Type, Union, cast import torch import torch.distributed as dist @@ -35,9 +36,14 @@ from torch import nn from torch.nn import Parameter from torch.nn.parallel import DistributedDataParallel as DDP +# from nerfactory.configs.base import InstantiateConfig from nerfactory.configs import base as cfg -from nerfactory.datamanagers.base import DataManager, VanillaDataManager -from nerfactory.models.base import Model +from nerfactory.datamanagers.base import ( + DataManager, + VanillaDataManager, + VanillaDataManagerConfig, +) +from nerfactory.models.base import Model, ModelConfig from nerfactory.utils import profiler from nerfactory.utils.callbacks import TrainingCallback, TrainingCallbackAttributes @@ -81,7 +87,9 @@ class Pipeline(nn.Module): self.model: The model that will be used """ - datamanager: DataManager + # pylint: disable=abstract-method + + datamanger: DataManager model: Model @property @@ -142,10 +150,6 @@ class Pipeline(nn.Module): def get_average_eval_image_metrics(self, step: Optional[int] = None): """Iterate over all the images in the eval dataset and get the average.""" - @abstractmethod - def log_test_image_outputs(self) -> None: - """Log the test image outputs""" - def load_pipeline(self, loaded_state: Dict[str, Any]) -> None: """Load the checkpoint from the given path @@ -166,6 +170,18 @@ class Pipeline(nn.Module): """ +@dataclass +class VanillaPipelineConfig(cfg.InstantiateConfig): + """Configuration for pipeline instantiation""" + + _target: Type = field(default_factory=lambda: VanillaPipeline) + """target class to instantiate""" + datamanager: VanillaDataManagerConfig = VanillaDataManagerConfig() + """specifies the datamanager config""" + model: ModelConfig = ModelConfig() + """specifies the model config""" + + class VanillaPipeline(Pipeline): """The pipeline class for the vanilla nerf setup of multiple cameras for one or a few scenes. @@ -182,20 +198,21 @@ class VanillaPipeline(Pipeline): def __init__( self, - config: cfg.VanillaPipelineConfig, + config: VanillaPipelineConfig, device: str, test_mode: bool = False, world_size: int = 1, local_rank: int = 0, ): super().__init__() + self.config = config self.datamanager: VanillaDataManager = config.datamanager.setup( device=device, test_mode=test_mode, world_size=world_size, local_rank=local_rank ) self.datamanager.to(device) # TODO(ethan): get rid of scene_bounds from the model assert self.datamanager.train_input_dataset is not None, "Missing input dataset" - self.model: Model = config.model.setup( + self.model = config.model.setup( scene_bounds=self.datamanager.train_input_dataset.dataset_inputs.scene_bounds, num_train_data=len(self.datamanager.train_input_dataset), ) @@ -213,7 +230,6 @@ class VanillaPipeline(Pipeline): """Returns the device that the model is on.""" return module_wrapper(self.model).device - @abstractmethod @profiler.time_function def get_train_loss_dict(self, step: int): """This function gets your training loss dict. This will be responsible for @@ -230,7 +246,13 @@ class VanillaPipeline(Pipeline): return model_outputs, loss_dict, metrics_dict - @abstractmethod + def forward(self): + """Blank forward method + + This is an nn.Module, and so requires a forward() method normally, although in our case + we do not need a forward() method""" + raise NotImplementedError + @profiler.time_function def get_eval_loss_dict(self, step: int): """This function gets your evaluation loss dict. It needs to get the data @@ -247,7 +269,6 @@ class VanillaPipeline(Pipeline): self.train() return model_outputs, loss_dict, metrics_dict - @abstractmethod @profiler.time_function def get_eval_image_metrics_and_images(self, step: int): """This function gets your evaluation loss dict. It needs to get the data @@ -267,7 +288,6 @@ class VanillaPipeline(Pipeline): self.train() return metrics_dict, images_dict - @abstractmethod @profiler.time_function def get_average_eval_image_metrics(self, step: Optional[int] = None): """Iterate over all the images in the eval dataset and get the average. diff --git a/nerfactory/pipelines/dynamic_batch.py b/nerfactory/pipelines/dynamic_batch.py new file mode 100644 index 000000000..33efe5026 --- /dev/null +++ b/nerfactory/pipelines/dynamic_batch.py @@ -0,0 +1,101 @@ +# 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 pipeline that dynamically chooses the number of rays to sample. +""" + +from dataclasses import dataclass, field +from typing import Type + +import torch + +from nerfactory.datamanagers.base import VanillaDataManager +from nerfactory.pipelines.base import VanillaPipeline, VanillaPipelineConfig + + +@dataclass +class DynamicBatchPipelineConfig(VanillaPipelineConfig): + """Dynamic Batch Pipeline Config""" + + _target: Type = field(default_factory=lambda: DynamicBatchPipeline) + target_num_samples: int = 262144 # 1 << 18 + """The target number of samples to use for an entire batch of rays.""" + max_num_samples_per_ray: int = 1024 # 1 << 10 + """The maximum number of samples to be placed along a ray.""" + + +class DynamicBatchPipeline(VanillaPipeline): + """Pipeline with logic for changing the number of rays per batch.""" + + # pylint: disable=abstract-method + + config: DynamicBatchPipelineConfig + datamanger: VanillaDataManager + dynamic_num_rays_per_batch: int + + def __init__( + self, + config: DynamicBatchPipelineConfig, + device: str, + test_mode: bool = False, + world_size: int = 1, + local_rank: int = 0, + ): + super().__init__(config, device, test_mode, world_size, local_rank) + assert isinstance( + self.datamanager, VanillaDataManager + ), "DynamicBatchPipeline only works with VanillaDataManager." + + self.dynamic_num_rays_per_batch = self.config.target_num_samples // self.config.max_num_samples_per_ray + self._update_pixel_samplers() + + def _update_pixel_samplers(self): + """Update the pixel samplers for train and eval with the dynamic number of rays per batch.""" + self.datamanager.train_pixel_sampler.set_num_rays_per_batch(self.dynamic_num_rays_per_batch) + self.datamanager.eval_pixel_sampler.set_num_rays_per_batch(self.dynamic_num_rays_per_batch) + + def _update_dynamic_num_rays_per_batch(self, num_samples_per_batch: int): + """Updates the dynamic number of rays per batch variable, + based on the total number of samples in the last batch of rays.""" + self.dynamic_num_rays_per_batch = int( + self.dynamic_num_rays_per_batch * (self.config.target_num_samples / num_samples_per_batch) + ) + + def get_train_loss_dict(self, step: int): + model_outputs, loss_dict, metrics_dict = super().get_train_loss_dict(step) + + # update the number of rays for the next step + if "num_samples_per_batch" not in metrics_dict: + raise ValueError( + "'num_samples_per_batch' is not in metrics_dict." + "Please return 'num_samples_per_batch' in the models get_metrics_dict function to use this method." + ) + self._update_dynamic_num_rays_per_batch(metrics_dict["num_samples_per_batch"]) + self._update_pixel_samplers() + + # add the number of rays + assert "num_rays_per_batch" not in metrics_dict + metrics_dict["num_rays_per_batch"] = torch.tensor(self.datamanager.train_pixel_sampler.num_rays_per_batch) + + return model_outputs, loss_dict, metrics_dict + + def get_eval_loss_dict(self, step: int): + model_outputs, loss_dict, metrics_dict = super().get_eval_loss_dict(step) + + # add the number of rays + assert "num_rays_per_batch" not in metrics_dict + metrics_dict["num_rays_per_batch"] = torch.tensor(self.datamanager.eval_pixel_sampler.num_rays_per_batch) + + return model_outputs, loss_dict, metrics_dict diff --git a/scripts/run_train.py b/scripts/run_train.py index 472b0b67c..73fe58ac0 100755 --- a/scripts/run_train.py +++ b/scripts/run_train.py @@ -3,6 +3,8 @@ run_train_nerf.py """ +from __future__ import annotations + import logging import random import socket diff --git a/tests/test_run_train.py b/tests/test_run_train.py index f76fa3f09..58038aed5 100644 --- a/tests/test_run_train.py +++ b/tests/test_run_train.py @@ -9,19 +9,20 @@ from pathlib import Path import pytest -import nerfactory.configs.base as cfg +from nerfactory.configs.base import Config from nerfactory.configs.base_configs import base_configs +from nerfactory.datamanagers.dataparsers.blender_parser import BlenderDataParserConfig from nerfactory.engine.trainer import train_loop BLACKLIST = ["base", "semantic-nerf", "mipnerf-360", "instant-ngp", "compound"] -def set_reduced_config(config: cfg.Config): +def set_reduced_config(config: Config): """Reducing the config settings to speedup test""" config.machine.num_gpus = 0 config.trainer.max_num_iterations = 2 # reduce dataset factors; set dataset to test - config.pipeline.datamanager.dataparser = cfg.BlenderDataParserConfig(data_directory=Path("tests/data/lego_test")) + config.pipeline.datamanager.dataparser = 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