Merge HDR stuff to main

This commit is contained in:
mokad6 committed 2023-09-12 21:09:54 -04:00
commit 8ba0e9bf76
10 files changed
+572 -75

No files matched your search

+29 -52
View File
@@ -19,38 +19,44 @@ Put all the method implementations in one location.
from __future__ import annotations
from collections import OrderedDict
from typing import Dict
from dataclasses import dataclass, field
from typing import Dict
import tyro
from lantern.config import lantern_config
from nerfstudio.cameras.camera_optimizers import CameraOptimizerConfig
from nerfstudio.configs.base_config import ViewerConfig
from nerfstudio.configs.external_methods import get_external_methods
from nerfstudio.data.datamanagers.random_cameras_datamanager import RandomCamerasDataManagerConfig
from nerfstudio.data.datamanagers.base_datamanager import VanillaDataManager, VanillaDataManagerConfig, HDRVanillaDataManagerConfig
from nerfstudio.data.dataparsers.blender_dataparser import BlenderDataParserConfig
from nerfstudio.data.datamanagers.base_datamanager import (
HDRVanillaDataManagerConfig, VanillaDataManager, VanillaDataManagerConfig)
from nerfstudio.data.datamanagers.random_cameras_datamanager import \
RandomCamerasDataManagerConfig
from nerfstudio.data.dataparsers.blender_dataparser import \
BlenderDataParserConfig
from nerfstudio.data.dataparsers.dnerf_dataparser import DNeRFDataParserConfig
from nerfstudio.data.dataparsers.instant_ngp_dataparser import InstantNGPDataParserConfig
from nerfstudio.data.dataparsers.nerfstudio_dataparser import NerfstudioDataParserConfig
from nerfstudio.data.dataparsers.phototourism_dataparser import PhototourismDataParserConfig
from nerfstudio.data.dataparsers.sdfstudio_dataparser import SDFStudioDataParserConfig
from nerfstudio.data.dataparsers.sitcoms3d_dataparser import Sitcoms3DDataParserConfig
from nerfstudio.data.dataparsers.instant_ngp_dataparser import \
InstantNGPDataParserConfig
from nerfstudio.data.dataparsers.nerfstudio_dataparser import \
NerfstudioDataParserConfig
from nerfstudio.data.dataparsers.phototourism_dataparser import \
PhototourismDataParserConfig
from nerfstudio.data.dataparsers.sdfstudio_dataparser import \
SDFStudioDataParserConfig
from nerfstudio.data.dataparsers.sitcoms3d_dataparser import \
Sitcoms3DDataParserConfig
from nerfstudio.data.datasets.depth_dataset import DepthDataset
from nerfstudio.data.datasets.hdr_dataset import HDRInputDataset
from nerfstudio.data.datasets.sdf_dataset import SDFDataset
from nerfstudio.data.datasets.semantic_dataset import SemanticDataset
from nerfstudio.data.datasets.hdr_dataset import HDRInputDataset
from nerfstudio.engine.optimizers import AdamOptimizerConfig, RAdamOptimizerConfig
from nerfstudio.engine.schedulers import (
CosineDecaySchedulerConfig,
ExponentialDecaySchedulerConfig,
MultiStepSchedulerConfig,
)
from nerfstudio.engine.optimizers import (AdamOptimizerConfig,
RAdamOptimizerConfig)
from nerfstudio.engine.schedulers import (CosineDecaySchedulerConfig,
ExponentialDecaySchedulerConfig,
MultiStepSchedulerConfig)
from nerfstudio.engine.trainer import TrainerConfig
from nerfstudio.field_components.temporal_distortions import TemporalDistortionKind
from nerfstudio.field_components.temporal_distortions import \
TemporalDistortionKind
from nerfstudio.fields.sdf_field import SDFFieldConfig
from nerfstudio.models.depth_nerfacto import DepthNerfactoModelConfig
from nerfstudio.models.generfacto import GenerfactoModelConfig
@@ -70,6 +76,7 @@ method_configs: Dict[str, TrainerConfig] = {}
descriptions = {
"nerfacto": "Recommended real-time model tuned for real captures. This model will be continually updated.",
"nerfacto-HDR": "Nerfacto that take HDR as input, and output HDR field.",
"lantern-nerfacto": "Nerfacto that take HDR as input, and output HDR field.",
"depth-nerfacto": "Nerfacto with depth supervision.",
"volinga": "Real-time rendering model from Volinga. Directly exportable to NVOL format at https://volinga.ai/",
"instant-ngp": "Implementation of Instant-NGP. Recommended real-time model for unbounded scenes.",
@@ -118,38 +125,8 @@ method_configs["nerfacto"] = TrainerConfig(
vis="viewer",
)
method_configs["nerfacto-HDR"] = TrainerConfig(
method_name="nerfacto-HDR",
steps_per_eval_batch=500,
steps_per_save=2000,
max_num_iterations=30000,
mixed_precision=True,
pipeline=VanillaPipelineConfig(
datamanager=HDRVanillaDataManagerConfig(
dataparser=NerfstudioDataParserConfig(),
train_num_rays_per_batch=4096,
eval_num_rays_per_batch=4096,
camera_optimizer=CameraOptimizerConfig(
mode="SO3xR3",
optimizer=AdamOptimizerConfig(lr=6e-4, eps=1e-8, weight_decay=1e-2),
scheduler=ExponentialDecaySchedulerConfig(lr_final=6e-6, max_steps=200000),
),
),
model=NerfactoModelConfig(eval_num_rays_per_chunk=1 << 15),
),
optimizers={
"proposal_networks": {
"optimizer": AdamOptimizerConfig(lr=1e-2, eps=1e-15),
"scheduler": ExponentialDecaySchedulerConfig(lr_final=0.0001, max_steps=200000),
},
"fields": {
"optimizer": AdamOptimizerConfig(lr=1e-2, eps=1e-15),
"scheduler": ExponentialDecaySchedulerConfig(lr_final=0.0001, max_steps=200000),
},
},
viewer=ViewerConfig(num_rays_per_chunk=1 << 15),
vis="viewer",
)
method_configs["lantern-nerfacto"] = lantern_config
method_configs["nerfacto-big"] = TrainerConfig(
method_name="nerfacto",
steps_per_eval_batch=500,
View File
Whitespace-only changes.
+55
View File
@@ -0,0 +1,55 @@
"""
Nerfstudio Config
Define custom method here that registers with Nerfstudio CLI.
"""
from __future__ import annotations
from nerfstudio.cameras.camera_optimizers import CameraOptimizerConfig
from nerfstudio.configs.base_config import ViewerConfig
from nerfstudio.data.dataparsers.nerfstudio_dataparser import \
NerfstudioDataParserConfig
from nerfstudio.engine.optimizers import (AdamOptimizerConfig,
RAdamOptimizerConfig)
from nerfstudio.engine.schedulers import ExponentialDecaySchedulerConfig
from nerfstudio.engine.trainer import TrainerConfig
from nerfstudio.lantern.datamanager import HDRVanillaDataManagerConfig
from nerfstudio.lantern.model import LanternModelConfig
from nerfstudio.pipelines.base_pipeline import VanillaPipelineConfig
from nerfstudio.plugins.types import MethodSpecification
def Get_lantern_config():
return TrainerConfig(
method_name="Lantern-nerfacto",
steps_per_eval_batch=500,
steps_per_save=2000,
max_num_iterations=30000,
mixed_precision=True,
pipeline=VanillaPipelineConfig(
datamanager=HDRVanillaDataManagerConfig(
dataparser=NerfstudioDataParserConfig(),
train_num_rays_per_batch=4096,
eval_num_rays_per_batch=4096,
camera_optimizer=CameraOptimizerConfig(
mode="SO3xR3",
optimizer=AdamOptimizerConfig(lr=6e-4, eps=1e-8, weight_decay=1e-2),
scheduler=ExponentialDecaySchedulerConfig(lr_final=6e-6, max_steps=200000),
),
),
model=LanternModelConfig(eval_num_rays_per_chunk=1 << 15),
),
optimizers={
"proposal_networks": {
"optimizer": AdamOptimizerConfig(lr=1e-2, eps=1e-15),
"scheduler": ExponentialDecaySchedulerConfig(lr_final=0.0001, max_steps=200000),
},
"fields": {
"optimizer": AdamOptimizerConfig(lr=1e-2, eps=1e-15),
"scheduler": ExponentialDecaySchedulerConfig(lr_final=0.0001, max_steps=200000),
},
},
viewer=ViewerConfig(num_rays_per_chunk=1 << 15),
vis="viewer",
)
+22
View File
@@ -0,0 +1,22 @@
"""
Lantern HDR DataManager
"""
from dataclasses import dataclass, field
from typing import Dict, Literal, Tuple, Type, Union
import torch
from nerfstudio.cameras.rays import RayBundle
from nerfstudio.data.datamanagers.base_datamanager import (
VanillaDataManager, VanillaDataManagerConfig)
from nerfstudio.data.datasets.hdr_dataset import HDRInputDataset
@dataclass
class HDRVanillaDataManagerConfig(VanillaDataManagerConfig):
"""A HDR data manager, based on VanillaDataManager"""
_target: Type = field(default_factory=lambda: VanillaDataManager[HDRInputDataset])
"""Target class to instantiate."""
+140
View File
@@ -0,0 +1,140 @@
# Copyright 2022 the Regents of the University of California, Nerfstudio Team and contributors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
HDR Dataset of .exr format
"""
from __future__ import annotations
import os
from copy import deepcopy
from pathlib import Path
from typing import Dict, List
os.environ["OPENCV_IO_ENABLE_OPENEXR"]="1"
import cv2
import numpy as np
import numpy.typing as npt
import torch
from jaxtyping import Float
from PIL import Image
from torch import Tensor
from torch.utils.data import Dataset
from nerfstudio.cameras.cameras import Cameras
from nerfstudio.data.dataparsers.base_dataparser import DataparserOutputs
from nerfstudio.data.datasets.base_dataset import InputDataset
from nerfstudio.data.utils.data_utils import get_image_mask_tensor_from_path
class HDRInputDataset(InputDataset):
"""Dataset that returns HDR images, OpenEXR.
Args:
dataparser_outputs: description of where and how to read input images.
scale_factor: The scaling factor for the dataparser outputs
"""
# exclude_batch_keys_from_device: List[str] = ["image", "mask"]
# cameras: Cameras
def __init__(self, dataparser_outputs: DataparserOutputs, scale_factor: float = 1.0):
super().__init__(dataparser_outputs, scale_factor)
def __len__(self):
return len(self._dataparser_outputs.image_filenames)
def get_numpy_image(self, image_idx: int) -> npt.NDArray[np.uint8]:
"""Returns the image of shape (H, W, 3 or 4), with np.float32 values.
Args:
image_idx: The image index in the dataset.
"""
image_filename = self._dataparser_outputs.image_filenames[image_idx]
hdr_image = cv2.imread(str(image_filename), cv2.IMREAD_UNCHANGED)
if self.scale_factor != 1.0:
# Here cv2 img shape is (height, width, BGR)
height, width = hdr_image.shape[:2]
newsize = (int(width * self.scale_factor), int(height * self.scale_factor))
hdr_image = cv2.resize(hdr_image, newsize, interpolation = cv2.INTER_LINEAR)
hdr_image = cv2.cvtColor(hdr_image, cv2.COLOR_BGR2RGB)
hdr_image = hdr_image.astype("float32") # shape is (h, w) or (h, w, 3 or 4)
if len(hdr_image.shape) == 2:
hdr_image = hdr_image[:, :, None].repeat(3, axis=2)
assert len(hdr_image.shape) == 3
assert hdr_image.dtype == np.float32
assert hdr_image.shape[2] in [3, 4], f"Image shape of {hdr_image.shape} is in correct."
return hdr_image
def get_image(self, image_idx: int) -> Float[Tensor, "image_height image_width num_channels"]:
"""Returns a 3 channel image.
Args:
image_idx: The image index in the dataset.
"""
# Compress HDR using: pixel = log(pixel + 1.)
image = torch.from_numpy(self.get_numpy_image(image_idx).astype("float32"))
# image = torch.log(1. + image)
# image = torch.log(1. + image)
# image = torch.log(1. + image)
# Compress HDR using u-law: pixel = log(u * pixel + 1.) / log(u + 1.)
u = 5000.
image = torch.log(1. + u * image) / torch.log(torch.tensor(1.+u))
if self._dataparser_outputs.alpha_color is not None and image.shape[-1] == 4:
image = image[:, :, :3] * image[:, :, -1:] + self._dataparser_outputs.alpha_color * (1.0 - image[:, :, -1:])
return image
def get_data(self, image_idx: int) -> Dict:
"""Returns the ImageDataset data as a dictionary.
Args:
image_idx: The image index in the dataset.
"""
image = self.get_image(image_idx)
data = {"image_idx": image_idx, "image": image}
if self._dataparser_outputs.mask_filenames is not None:
mask_filepath = self._dataparser_outputs.mask_filenames[image_idx]
data["mask"] = get_image_mask_tensor_from_path(filepath=mask_filepath, scale_factor=self.scale_factor)
assert (
data["mask"].shape[:2] == data["image"].shape[:2]
), f"Mask and image have different shapes. Got {data['mask'].shape[:2]} and {data['image'].shape[:2]}"
metadata = self.get_metadata(data)
data.update(metadata)
return data
def get_metadata(self, data: Dict) -> Dict:
"""Method that can be used to process any additional metadata that may be part of the model inputs.
Args:
image_idx: The image index in the dataset.
"""
del data
return {}
def __getitem__(self, image_idx: int) -> Dict:
data = self.get_data(image_idx)
return data
@property
def image_filenames(self) -> List[Path]:
"""
Returns image filenames for this dataset.
The order of filenames is the same as in the Cameras object for easy mapping.
"""
return self._dataparser_outputs.image_filenames
+71
View File
@@ -0,0 +1,71 @@
"""
HDR Nerfstudio Field
Currently this subclasses the NerfactoField. Consider subclassing the base Field.
"""
from typing import Literal, Optional
from torch import Tensor, nn
from nerfstudio.field_components.mlp import MLP
from nerfstudio.field_components.spatial_distortions import SpatialDistortion
from nerfstudio.fields.nerfacto_field import \
NerfactoField # for subclassing NerfactoField
class LanternNerfactoField(NerfactoField):
"""Template Field
Args:
aabb: parameters of scene aabb bounds
num_images: number of images in the dataset
"""
aabb: Tensor
def __init__(
self,
aabb: Tensor,
num_images: int,
num_layers: int = 2,
hidden_dim: int = 64,
geo_feat_dim: int = 15,
num_levels: int = 16,
base_res: int = 16,
max_res: int = 2048,
log2_hashmap_size: int = 19,
num_layers_color: int = 3,
num_layers_transient: int = 2,
features_per_level: int = 2,
hidden_dim_color: int = 64,
hidden_dim_transient: int = 64,
appearance_embedding_dim: int = 32,
transient_embedding_dim: int = 16,
use_transient_embedding: bool = False,
use_semantics: bool = False,
num_semantic_classes: int = 100,
pass_semantic_gradients: bool = False,
use_pred_normals: bool = False,
use_average_appearance_embedding: bool = False,
spatial_distortion: Optional[SpatialDistortion] = None,
implementation: Literal["tcnn", "torch"] = "tcnn",
) -> None:
super().__init__(aabb, num_images, num_layers, hidden_dim,
geo_feat_dim, num_levels, base_res,
max_res, log2_hashmap_size, num_layers_color,
num_layers_transient, features_per_level, hidden_dim_color,
hidden_dim_transient, appearance_embedding_dim, transient_embedding_dim,
use_transient_embedding, use_semantics, num_semantic_classes,
pass_semantic_gradients, use_pred_normals, use_average_appearance_embedding,
spatial_distortion, implementation)
# Define the last part MLP for RGB outputs
self.mlp_head = MLP(
in_dim=self.direction_encoding.get_out_dim() + self.geo_feat_dim + self.appearance_embedding_dim,
num_layers=num_layers_color,
layer_width=hidden_dim_color,
out_dim=3,
activation=nn.ReLU(),
out_activation=nn.ReLU(),
implementation=implementation,
)
+190
View File
@@ -0,0 +1,190 @@
"""
Lantern Model File
Currently this subclasses the Nerfacto model. Consider subclassing from the base Model.
"""
from dataclasses import dataclass, field
from typing import Dict, List, Literal, Tuple, Type
import numpy as np
import torch
from torchmetrics.functional import structural_similarity_index_measure
from torchmetrics.image import PeakSignalNoiseRatio
from torchmetrics.image.lpip import LearnedPerceptualImagePatchSimilarity
from nerfstudio.field_components.spatial_distortions import SceneContraction
from nerfstudio.fields.density_fields import HashMLPDensityField
from nerfstudio.lantern.field import LanternNerfactoField
from nerfstudio.model_components.losses import (
MSELoss, distortion_loss, interlevel_loss, orientation_loss,
pred_normal_loss, scale_gradients_by_distance_squared)
from nerfstudio.model_components.ray_samplers import (ProposalNetworkSampler,
UniformSampler)
from nerfstudio.model_components.renderers import (AccumulationRenderer,
DepthRenderer,
NormalsRenderer,
RGBRenderer)
from nerfstudio.model_components.scene_colliders import NearFarCollider
from nerfstudio.model_components.shaders import NormalsShader
from nerfstudio.models.base_model import Model, ModelConfig # for custom Model
from nerfstudio.models.nerfacto import ( # for subclassing Nerfacto model
NerfactoModel, NerfactoModelConfig)
from nerfstudio.utils import colormaps
@dataclass
class LanternModelConfig(NerfactoModelConfig):
"""Template Model Configuration.
Add your custom model config parameters here.
"""
_target: Type = field(default_factory=lambda: LanternModel)
class LanternModel(NerfactoModel):
"""Template Model."""
config: LanternModelConfig
def populate_modules(self):
super().populate_modules()
if self.config.disable_scene_contraction:
scene_contraction = None
else:
scene_contraction = SceneContraction(order=float("inf"))
# Fields
self.field = LanternNerfactoField(
self.scene_box.aabb,
hidden_dim=self.config.hidden_dim,
num_levels=self.config.num_levels,
max_res=self.config.max_res,
base_res=self.config.base_res,
features_per_level=self.config.features_per_level,
log2_hashmap_size=self.config.log2_hashmap_size,
hidden_dim_color=self.config.hidden_dim_color,
hidden_dim_transient=self.config.hidden_dim_transient,
spatial_distortion=scene_contraction,
num_images=self.num_train_data,
use_pred_normals=self.config.predict_normals,
use_average_appearance_embedding=self.config.use_average_appearance_embedding,
appearance_embedding_dim=self.config.appearance_embed_dim,
implementation=self.config.implementation,
)
self.density_fns = []
num_prop_nets = self.config.num_proposal_iterations
# Build the proposal network(s)
self.proposal_networks = torch.nn.ModuleList()
if self.config.use_same_proposal_network:
assert len(self.config.proposal_net_args_list) == 1, "Only one proposal network is allowed."
prop_net_args = self.config.proposal_net_args_list[0]
network = HashMLPDensityField(
self.scene_box.aabb,
spatial_distortion=scene_contraction,
**prop_net_args,
implementation=self.config.implementation,
)
self.proposal_networks.append(network)
self.density_fns.extend([network.density_fn for _ in range(num_prop_nets)])
else:
for i in range(num_prop_nets):
prop_net_args = self.config.proposal_net_args_list[min(i, len(self.config.proposal_net_args_list) - 1)]
network = HashMLPDensityField(
self.scene_box.aabb,
spatial_distortion=scene_contraction,
**prop_net_args,
implementation=self.config.implementation,
)
self.proposal_networks.append(network)
self.density_fns.extend([network.density_fn for network in self.proposal_networks])
# Samplers
def update_schedule(step):
return np.clip(
np.interp(step, [0, self.config.proposal_warmup], [0, self.config.proposal_update_every]),
1,
self.config.proposal_update_every,
)
# Change proposal network initial sampler if uniform
initial_sampler = None # None is for piecewise as default (see ProposalNetworkSampler)
if self.config.proposal_initial_sampler == "uniform":
initial_sampler = UniformSampler(single_jitter=self.config.use_single_jitter)
self.proposal_sampler = ProposalNetworkSampler(
num_nerf_samples_per_ray=self.config.num_nerf_samples_per_ray,
num_proposal_samples_per_ray=self.config.num_proposal_samples_per_ray,
num_proposal_network_iterations=self.config.num_proposal_iterations,
single_jitter=self.config.use_single_jitter,
update_sched=update_schedule,
initial_sampler=initial_sampler,
)
# Collider
self.collider = NearFarCollider(near_plane=self.config.near_plane, far_plane=self.config.far_plane)
# renderers
self.renderer_rgb = RGBRenderer(background_color=self.config.background_color)
self.renderer_accumulation = AccumulationRenderer()
self.renderer_depth = DepthRenderer()
self.renderer_normals = NormalsRenderer()
# shaders
self.normals_shader = NormalsShader()
# TODO losses
self.rgb_loss = MSELoss()
# metrics
self.psnr = PeakSignalNoiseRatio(data_range=1.0)
self.ssim = structural_similarity_index_measure
self.lpips = LearnedPerceptualImagePatchSimilarity(normalize=True)
def get_image_metrics_and_images(
self, outputs: Dict[str, torch.Tensor], batch: Dict[str, torch.Tensor]
) -> Tuple[Dict[str, float], Dict[str, torch.Tensor]]:
gt_rgb = batch["image"].to(self.device)
predicted_rgb = outputs["rgb"] # Blended with background (black if random background)
gt_rgb = self.renderer_rgb.blend_background(gt_rgb)
acc = colormaps.apply_colormap(outputs["accumulation"])
depth = colormaps.apply_depth_colormap(
outputs["depth"],
accumulation=outputs["accumulation"],
)
combined_rgb = torch.cat([gt_rgb, predicted_rgb], dim=1)
combined_acc = torch.cat([acc], dim=1)
combined_depth = torch.cat([depth], dim=1)
# Switch images from [H, W, C] to [1, C, H, W] for metrics computations
gt_rgb_tonemapped = torch.clamp(colormaps.apply_colormap(gt_rgb), 0, 1)
predicted_rgb_tonemapped = torch.clamp(colormaps.apply_colormap(predicted_rgb), 0, 1)
gt_rgb_tonemapped = torch.moveaxis(gt_rgb_tonemapped, -1, 0)[None, ...]
predicted_rgb_tonemapped = torch.moveaxis(predicted_rgb_tonemapped, -1, 0)[None, ...]
gt_rgb = torch.moveaxis(gt_rgb, -1, 0)[None, ...]
predicted_rgb = torch.moveaxis(predicted_rgb, -1, 0)[None, ...]
psnr = self.psnr(gt_rgb, predicted_rgb)
ssim = self.ssim(gt_rgb, predicted_rgb)
# tonemapping before computing lpips
lpips = self.lpips(gt_rgb_tonemapped, predicted_rgb_tonemapped)
# all of these metrics will be logged as scalars
metrics_dict = {"psnr": float(psnr.item()), "ssim": float(ssim)} # type: ignore
metrics_dict["lpips"] = float(lpips)
images_dict = {"img": combined_rgb, "accumulation": combined_acc, "depth": combined_depth}
for i in range(self.config.num_proposal_iterations):
key = f"prop_depth_{i}"
prop_depth_i = colormaps.apply_depth_colormap(
outputs[key],
accumulation=outputs["accumulation"],
)
images_dict[key] = prop_depth_i
return metrics_dict, images_dict
@@ -17,16 +17,16 @@
import json
from dataclasses import dataclass
from pathlib import Path
from typing import Optional, Literal, Tuple
from typing import Literal, Optional, Tuple
import numpy as np
from nerfstudio.process_data import equirect_utils, process_data_utils
from nerfstudio.process_data.base_converter_to_nerfstudio_dataset import (
BaseConverterToNerfstudioDataset,
)
from nerfstudio.utils.rich_utils import CONSOLE
from nerfstudio.process_data.base_converter_to_nerfstudio_dataset import \
BaseConverterToNerfstudioDataset
from nerfstudio.utils import io
from nerfstudio.utils.rich_utils import CONSOLE
@dataclass
class ProcessAlignedPano(BaseConverterToNerfstudioDataset):
@@ -69,11 +69,10 @@ class ProcessAlignedPano(BaseConverterToNerfstudioDataset):
summary_log = []
pers_size = equirect_utils.compute_resolution_from_equirect(self.data, self.images_per_equirect)
CONSOLE.log(f"Generating {self.images_per_equirect} {pers_size} sized images per equirectangular image")
# self.data = equirect_utils.generate_planar_projections_from_equirectangular(
# self.metadata, self.data, pers_size, self.images_per_equirect, crop_factor=self.crop_factor
# )
self.data = equirect_utils.generate_planar_projections_from_equirectangular_GT(
self.metadata, self.data, pers_size, self.images_per_equirect, crop_factor=self.crop_factor, clip_output = False
)
self.camera_type = "perspective"
self.data = self.data / "planar_projections"
metadata_dict = io.load_from_json(self.data / "transforms.json")
# Copy images to output directory
cropped_images_filename = []
+35 -14
View File
@@ -15,32 +15,27 @@
"""Helper utils for processing equirectangular data."""
import os
os.environ["OPENCV_IO_ENABLE_OPENEXR"]="1"
import json
import math
import sys
from pathlib import Path
from typing import List, Tuple
import math
import cv2
import numpy as np
from numpy.linalg import inv
from scipy.spatial.transform import Rotation
import torch
from equilib import Equi2Pers
from rich.progress import (
BarColumn,
Progress,
TaskProgressColumn,
TextColumn,
TimeRemainingColumn,
)
from numpy.linalg import inv
from rich.progress import (BarColumn, Progress, TaskProgressColumn, TextColumn,
TimeRemainingColumn)
from scipy.spatial.transform import Rotation
from nerfstudio.utils.rich_utils import CONSOLE, ItersPerSecColumn
from equilib import Equi2Pers
from nerfstudio.process_data.process_data_utils import CAMERA_MODELS
from nerfstudio.utils import io
from nerfstudio.utils.rich_utils import CONSOLE, ItersPerSecColumn
def _crop_bottom(bound_arr: list, fov: int, crop_factor: float) -> List[float]:
@@ -119,8 +114,11 @@ def generate_planar_projections_from_equirectangular(
image_dir: Path,
planar_image_size: Tuple[int, int],
samples_per_im: int,
<<<<<<< HEAD
mask_dir: Path,
hdr_dir: Path,
=======
>>>>>>> origin/jmchen
crop_factor: Tuple[float, float, float, float] = (0.0, 0.0, 0.0, 0.0),
is_HDR: bool = False,
HDR_planar_image_size: Tuple[int, int] = (None, None),
@@ -184,6 +182,7 @@ def generate_planar_projections_from_equirectangular(
frame_dir = image_dir
output_dir = image_dir / "planar_projections"
output_dir.mkdir(exist_ok=True)
<<<<<<< HEAD
output_mask_dir = None
if mask_dir is not None:
@@ -197,6 +196,8 @@ def generate_planar_projections_from_equirectangular(
else:
equi2pers_hdr = equi2pers
=======
>>>>>>> origin/jmchen
num_ims = len(os.listdir(frame_dir))
progress = Progress(
TextColumn("[bold blue]Generating Planar Images", justify="right"),
@@ -208,6 +209,7 @@ def generate_planar_projections_from_equirectangular(
with progress:
for i in progress.track(os.listdir(frame_dir), description="", total=num_ims):
<<<<<<< HEAD
if i.lower().endswith((".jpg", ".png", ".jpeg", ".exr")):
if i.lower().endswith((".exr")):
im = np.array(cv2.imread(os.path.join(frame_dir, i), cv2.IMREAD_UNCHANGED)).astype("float32")
@@ -241,10 +243,17 @@ def generate_planar_projections_from_equirectangular(
hdr = torch.tensor(hdr, dtype=torch.float32, device=device)
hdr = torch.permute(hdr, (2, 0, 1))
=======
if i.lower().endswith((".jpg", ".png", ".jpeg")):
im = np.array(cv2.imread(os.path.join(frame_dir, i)))
im = torch.tensor(im, dtype=torch.float32, device=device)
im = torch.permute(im, (2, 0, 1)) / 255.0
>>>>>>> origin/jmchen
count = 0
for u_deg, v_deg in yaw_pitch_pairs:
v_rad = torch.pi * v_deg / 180.0
u_rad = torch.pi * u_deg / 180.0
<<<<<<< HEAD
pers_image = equi2pers(im, rots={"roll": 0, "pitch": v_rad, "yaw": u_rad})
if mask_dir is not None:
pers_mask = equi2pers_hdr(mask, rots={"roll": 0, "pitch": v_rad, "yaw": u_rad}) * 255.0
@@ -278,12 +287,24 @@ def generate_planar_projections_from_equirectangular(
return output_dir, output_mask_dir, output_hdr_dir
def generate_planar_projections_from_equirectangular_2(
=======
pers_image = equi2pers(im, rots={"roll": 0, "pitch": v_rad, "yaw": u_rad}) * 255.0
assert isinstance(pers_image, torch.Tensor)
pers_image = (pers_image.permute(1, 2, 0)).type(torch.uint8).to("cpu").numpy()
cv2.imwrite(f"{output_dir}/{i[:-4]}_{count}.png", pers_image)
count += 1
return output_dir
def generate_planar_projections_from_equirectangular_GT(
>>>>>>> origin/jmchen
metadata_path: Path,
image_dir: Path,
planar_image_size: Tuple[int, int],
samples_per_im: int,
mask_dir: Path = None,
crop_factor: Tuple[float, float, float, float] = (0.0, 0.0, 0.0, 0.0),
clip_output: bool = False,
) -> Path:
"""Given camera pose, generate planar projections from an equirectangular image.
And output corresponding camera pose.
@@ -381,7 +402,7 @@ def generate_planar_projections_from_equirectangular_2(
for u_deg, v_deg in yaw_pitch_pairs:
v_rad = torch.pi * v_deg / 180.0
u_rad = torch.pi * u_deg / 180.0
pers_image = equi2pers(im, rots={"roll": 0, "pitch": v_rad, "yaw": u_rad})
pers_image = equi2pers(im, rots={"roll": 0, "pitch": v_rad, "yaw": u_rad}, clip_output=clip_output)
if mask_dir is not None:
pers_mask = equi2pers(mask, rots={"roll": 0, "pitch": v_rad, "yaw": u_rad}) * 255.0
# transform matrix for blender: object.matrix_world
+22
View File
@@ -43,8 +43,11 @@ class ColormapOptions:
""" Whether to invert the output colormap """
exposure_scale: float = 0
"""Scale the brightness prior to tonemapping by 2 ^ Exposure"""
<<<<<<< HEAD
=======
>>>>>>> origin/jmchen
def apply_colormap(
image: Float[Tensor, "*bs channels"],
@@ -67,6 +70,7 @@ def apply_colormap(
# default for rgb images
if image.shape[-1] == 3:
<<<<<<< HEAD
# print('colormap_options.exposure_scale', colormap_options.exposure_scale)
# if(colormap_options.exposure_scale != 0):
# img_gamma_22 = image ** (2.2) - 1.
@@ -76,6 +80,24 @@ def apply_colormap(
# else:
# return image
return image
=======
if(colormap_options.exposure_scale != 0 or image.max() > 1.):
# Log
img_gamma_22 = torch.exp(image) - 1.
# u-law
u = 5000.
img_gamma_22 = torch.exp(image * torch.log(torch.tensor(u+1.))) - 1.
img_gamma_22 /= u
# img_gamma_22 = torch.exp(img_gamma_22) - 1.
# img_gamma_22 = torch.exp(img_gamma_22) - 1.
img_gamma_22 *= 2.**(colormap_options.exposure_scale)
img_gamma_22 = torch.pow(img_gamma_22, 1./2.2)
return torch.clamp(img_gamma_22, 0., 1.)
else:
return image
>>>>>>> origin/jmchen
# rendering depth outputs
if image.shape[-1] == 1 and torch.is_floating_point(image):