4
0
mirror of https://github.com/nerfstudio-project/nerfstudio.git synced 2026-11-11 02:24:38 +08:00
nerfstudio/scripts/run_eval.py
Ethan Weber 4ddd146633
Improving eval script with camera trajectory videos (#124)
* spiral traj working, specify version for hydra, better documentation

* cleaned up spiral trajectory a bit
2022-07-13 10:58:01 -07:00

161 lines
6.1 KiB
Python

"""
run_eval.py
"""
import argparse
import os
from typing import Tuple
import hydra
import mediapy as media
import torch
from hydra import compose, initialize
from omegaconf import DictConfig
from tqdm import tqdm
from pyrad.cameras.camera_paths import get_spiral_path
from pyrad.cameras.cameras import SimplePinholeCamera
from pyrad.data.dataloader import setup_dataset_eval, setup_dataset_train
from pyrad.graphs.base import Graph, setup_graph
from pyrad.utils.writer import TimeWriter
def _update_avg(prev_avg: float, new_val: float, step: int) -> float:
"""helper to calculate the running average
Args:
prev_avg (float): previous average value
new_val (float): new value to update the average with
step (int): current step number
Returns:
float: new updated average
"""
return (step * prev_avg + new_val) / (step + 1)
def _load_checkpoint(config: DictConfig, graph: Graph) -> None:
"""Helper function to load checkpointed graph
Args:
config (DictConfig): Configuration of graph to load
graph (Graph): Graph instance of which to load weights
"""
assert config.load_dir is not None
if config.load_step is None:
print("Loading latest checkpoint from load_dir")
# NOTE: this is specific to the checkpoint name format
load_step = sorted([int(x[x.find("-") + 1 : x.find(".")]) for x in os.listdir(config.load_dir)])[-1]
else:
load_step = config.load_step
load_path = os.path.join(config.load_dir, f"step-{load_step:09d}.ckpt")
assert os.path.exists(load_path), f"Checkpoint {load_path} does not exist"
loaded_state = torch.load(load_path, map_location="cpu")
graph.load_graph(loaded_state)
print(f"done loading checkpoint from {load_path}")
def run_inference(config: DictConfig, local_rank: int = 0, world_size: int = 1) -> Tuple[float, float]:
"""helper function to run inference given config specifications (also used in benchmarking)
Args:
config (DictConfig): Configuration for loading the evaluation.
Returns:
Tuple[float, float]: returns both the avg psnr and avg rays per second
"""
device = "cpu" if world_size == 0 else f"cuda:{local_rank}"
# setup graph and dataset
dataset_inputs_train, _ = setup_dataset_train(config.data, device=device)
_, dataloader_eval = setup_dataset_eval(config.data, test_mode=True, device=device)
graph = setup_graph(config.graph, dataset_inputs_train, device=device)
# load checkpointed information
_load_checkpoint(config.trainer.resume_train, graph)
# calculate average psnr across test dataset
# TODO(ethan): trajector specification
avg_psnr = 0
avg_rays_per_sec = 0
for step, (camera_ray_bundle, batch) in tqdm(enumerate(dataloader_eval)):
with TimeWriter(writer=None, name=None, write=False) as t:
with torch.no_grad():
image_idx = int(camera_ray_bundle.camera_indices[0, 0])
outputs = graph.get_outputs_for_camera_ray_bundle(camera_ray_bundle)
psnr = graph.log_test_image_outputs(image_idx, step, batch, outputs)
avg_rays_per_sec = _update_avg(avg_rays_per_sec, camera_ray_bundle.origins.shape[0] / t.duration, step)
avg_psnr = _update_avg(avg_psnr, psnr, step)
return avg_psnr, avg_rays_per_sec
def create_spiral_video(
config: DictConfig, local_rank: int = 0, world_size: int = 1, output_filename: str = None
) -> None:
print("Creating spiral video")
device = "cpu" if world_size == 0 else f"cuda:{local_rank}"
# setup graph and dataset
dataset_inputs_train, _ = setup_dataset_train(config.data, device=device)
_, dataloader_eval = setup_dataset_eval(config.data, test_mode=True, device=device)
graph = setup_graph(config.graph, dataset_inputs_train, device=device)
# load checkpointed information
_load_checkpoint(config.trainer.resume_train, graph)
# get a trajecory
start_camera = dataloader_eval.get_camera(image_idx=0)
# TODO(ethan): replace with radius with radiuses, based on camera pose percentiles
# see original nerf paper code for details
camera_path = get_spiral_path(start_camera, steps=60, radius=0.5)
images = []
for camera in tqdm(camera_path.cameras):
camera.cx /= 4
camera.cy /= 4
camera.fx /= 4
camera.fy /= 4
camera_ray_bundle = camera.get_camera_ray_bundle().to(device)
camera_ray_bundle.num_rays_per_chunk = 4096
outputs = graph.get_outputs_for_camera_ray_bundle(camera_ray_bundle)
# TODO: don't hardcode the key! this will break for some nerf Graphs
image = outputs["rgb"].cpu().numpy()
images.append(image)
seconds = 5.0
fps = len(images) / seconds
media.write_video(output_filename, images, fps=fps)
def main():
"""Main function."""
parser = argparse.ArgumentParser(description="Run the evaluation of a model.")
parser.add_argument(
"--method",
type=str,
default="psnr",
choices=["psnr", "traj"],
help="Specify which type of evaluation method to run.",
)
parser.add_argument("--traj", type=str, default="spiral", choices=["spiral", "interp"])
parser.add_argument("--output-filename", type=str, default="output.mp4")
parser.add_argument("--config-name", type=str, default="graph_default.yaml")
parser.add_argument("overrides", nargs="*", default=[])
args = parser.parse_args()
config_path = "../configs"
initialize(version_base="1.2", config_path=config_path)
config = compose(args.config_name, overrides=args.overrides)
assert config.trainer.resume_train.load_dir, "Please specify checkpoint load path"
assert args.traj != "interp", "Camera pose interpolation trajectory isn't yet implemented."
if args.method == "psnr":
avg_psnr, avg_rays_per_sec = run_inference(config)
print(f"Avg. PSNR: {avg_psnr:0.4f}")
print(f"Avg. Rays per sec: {avg_rays_per_sec:0.4f}")
elif args.method == "traj":
create_spiral_video(config, output_filename=args.output_filename)
if __name__ == "__main__":
main()