-
Notifications
You must be signed in to change notification settings - Fork 226
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Articulation #209
base: main
Are you sure you want to change the base?
Articulation #209
Changes from all commits
8cd5ce2
8b642f1
4c64383
87bde1f
6e9d298
1aec48f
fea6bae
ed8a8d3
1692e49
9e48a6b
ff719d0
6f3c3eb
44275b8
bdd544e
658c817
04df5ab
a5af1a4
126eeee
744cd47
66d055b
5cef8d7
9cb85bc
93d9e51
dd8e9de
22278c5
bc5ce4e
5d62cd8
fe1664c
9ed785d
6b6d9bb
f8dc68e
5084098
2655c5f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# Dynamic NeRF Challenge | ||
An example of rendering animation from blender is given in examples/articulation.py | ||
To render the following example, run: | ||
``` | ||
mkdir examples/KuBasic/rain_v22 | ||
gcloud cp gs://research-brain-kubric-xgcp/articulation/* examples/KuBasic/rain_v22/ | ||
docker build -f docker/KubruntuDev.Dockerfile -t kubricdockerhub/kubruntudev:latest . | ||
docker run --rm --interactive --user `id -u`:`id -g` \ | ||
--volume `pwd`:/workspace \ | ||
kubricdockerhub/kubruntudev \ | ||
python3 challenges/dynamic_nerf/worker.py | ||
``` | ||
|
||
![](/docs/images/articulation.gif) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
# Copyright 2021 The Kubric Authors | ||
# | ||
# 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 | ||
# | ||
# https://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. | ||
import os | ||
import imageio | ||
import logging | ||
import numpy as np | ||
import kubric as kb | ||
from kubric.renderer.blender import Blender as KubricRenderer | ||
|
||
logging.basicConfig(level="INFO") | ||
|
||
|
||
# --- CLI arguments | ||
parser = kb.ArgumentParser() | ||
FLAGS = parser.parse_args() | ||
|
||
# --- create scene and attach a renderer | ||
image_size = (512, 512) | ||
scene = kb.Scene(resolution=image_size) | ||
scene.frame_start = 1 | ||
scene.frame_end = 180 # < numbers of frames to render | ||
scene.frame_rate = 24 # < rendering framerate | ||
camera_radius = 0.7 | ||
camera_height = 1.5 | ||
camera_look_at = (0, 0, camera_height) | ||
use_static_scene = False | ||
|
||
if use_static_scene: | ||
blend_file = "examples/KuBasic/rain_v22/rain_v2.1.blend" | ||
scratch_dir = "output/static" | ||
else: | ||
blend_file = "examples/KuBasic/rain_v22/rain_v2.1_face_animated.blend" | ||
scratch_dir = "output/dynamic" | ||
|
||
scene += kb.DirectionalLight(name="sun", position=(10, -10, 1.5), look_at=(0, 0, 1), intensity=3.0) | ||
scene += kb.DirectionalLight(name="sun", position=(-10, -10, 1.5), look_at=(0, 0, 1), intensity=3.0) | ||
scene += kb.PerspectiveCamera(name="camera", position=(0, -camera_radius, camera_height), look_at=camera_look_at) | ||
renderer = KubricRenderer(scene, scratch_dir=scratch_dir, | ||
custom_scene=blend_file) | ||
|
||
frames = [] | ||
|
||
for frame_nr in range(scene.frame_start - 1, scene.frame_end + 2): | ||
interp = float(frame_nr - scene.frame_start + 1) / float(scene.frame_end - scene.frame_start + 3) | ||
scene.camera.position = (-camera_radius*np.sin(interp*2*np.pi), | ||
-camera_radius*np.abs(np.cos(interp*2*np.pi)), | ||
camera_height) | ||
scene.camera.look_at(camera_look_at) | ||
scene.camera.keyframe_insert("position", frame_nr ) | ||
scene.camera.keyframe_insert("quaternion", frame_nr ) | ||
|
||
kb.write_json(filename=f"{scratch_dir}/camera/frame_{frame_nr:04d}.json", data= | ||
kb.get_camera_info(scene.camera)) | ||
|
||
frames.append(frame_nr) | ||
|
||
frames_dict = renderer.render(frames=frames, ignore_missing_textures=True) | ||
kb.write_image_dict(frames_dict, f"{scratch_dir}/output/") | ||
|
||
# --- render (and save the blender file) | ||
renderer.save_state(f"{scratch_dir}/articulation.blend") | ||
|
||
# -- generate a gif | ||
gif_writer = imageio.get_writer(f"{scratch_dir}/summary.gif", mode="I") | ||
mp4_writer = imageio.get_writer(f"{scratch_dir}/video.mp4", fps=scene.frame_rate) | ||
all_files = os.listdir(f"{scratch_dir}/output/") | ||
for frame in range(scene.frame_start - 1, scene.frame_end + 2): | ||
image_files = [os.path.join(f"{scratch_dir}/output", f) for f in all_files if str(frame).zfill(4) in f] | ||
image_files = [f for f in image_files if f.endswith(".png") and "coordinates" not in f] | ||
image_files = sorted(image_files) | ||
assert len(image_files) > 0 | ||
images = [imageio.imread(f, "PNG") for f in image_files] | ||
images = [np.dstack(3*[im[..., None]]) if len(im.shape) == 2 else im for im in images] | ||
images = [im[..., :3] if im.shape[2]>3 else im for im in images] | ||
if len(images) % 2 == 1: | ||
images += [np.zeros_like(images[0])] | ||
images = [np.hstack(images[:len(images)//2]), np.hstack(images[len(images)//2:])] | ||
images = np.vstack(images) | ||
gif_writer.append_data(images) | ||
rgb_image_file = os.path.join(f"{scratch_dir}/images", f"frame_{frame:04d}.png") | ||
image = imageio.imread(rgb_image_file) | ||
mp4_writer.append_data(image) | ||
gif_writer.close() | ||
mp4_writer.close() |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -233,6 +233,9 @@ def save_state(self, path: PathLike, pack_textures: bool = True): | |
path.parent.mkdir(parents=True, exist_ok=True) # ensure directory exists | ||
logger.info("Saving '%s'", path) | ||
tf.io.gfile.copy(tmp_path, path, overwrite=True) | ||
|
||
def load_state(self, path: PathLike): | ||
bpy.ops.wm.open_mainfile(filepath=path) | ||
|
||
def render(self, | ||
frames: Optional[Sequence[int]] = None, | ||
|
@@ -263,8 +266,10 @@ def render(self, | |
- "normal": shape = (nr_frames, height, width, 3) (uint16) | ||
""" | ||
logger.info("Using scratch rendering folder: '%s'", self.scratch_dir) | ||
if not ignore_missing_textures: | ||
self._check_missing_textures() | ||
missing_textures = sorted({img.filepath for img in bpy.data.images | ||
if tuple(img.size) == (0, 0) or len(img.filepath)>1}) | ||
if missing_textures and not ignore_missing_textures: | ||
raise RuntimeError(f"Missing textures: {missing_textures}") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any particular reason for inlining |
||
self.set_exr_output_path(self.scratch_dir / "exr" / "frame_") | ||
# --- starts rendering | ||
if frames is None: | ||
|
@@ -357,6 +362,9 @@ def clear_and_reset_blender_scene(verbose: bool = False, custom_scene: str = Non | |
else: | ||
logger.info("Loading scene from '%s'", custom_scene) | ||
bpy.ops.wm.open_mainfile(filepath=custom_scene) | ||
dirname = os.path.dirname(custom_scene) | ||
for img in bpy.data.images: | ||
img.filepath = img.filepath.replace("//", dirname + "/") | ||
|
||
@singledispatchmethod | ||
def add_asset(self, asset: core.Asset) -> Any: | ||
|
@@ -716,7 +724,6 @@ def __call__(self, change): | |
if self.converter: | ||
# use converter if given | ||
new_value = self.converter(new_value) | ||
|
||
setattr(self.blender_obj, self.attribute, new_value) | ||
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ etils[epath_no_tf] | |
cloudml-hypertune | ||
google-cloud-storage | ||
imageio | ||
imageio-ffmpeg | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this should be a mandatory kubric dependency just because the dynamic nerf worker uses it. I'd prefer adding specific instructions for additional dependencies to that worker. |
||
munch | ||
numpy>=1.17 | ||
pandas | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please do not change from png to tiff here. That is a breaking change.
If you need coordinates as a tiff file, then add a new
write_coordinates_batch_tif
method and in your worker setkb.file_io.DEFAULT_WRITERS["object_coordinates"] = write_coordinates_batch_tif