Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import shutil
import bpy
from bpy.app.handlers import persistent
from . import blender_nerf_ui, sof_ui, ttc_ui, sof_operator, ttc_operator
from . import blender_nerf_ui, sof_ui, ttc_ui, cos_ui, sof_operator, ttc_operator, cos_operator, dataset_from_cameras_operator, dataset_from_cameras_ui


bl_info = {
Expand Down Expand Up @@ -47,15 +47,28 @@ def poll_is_camera(self, obj):
('ttc_dataset_name', bpy.props.StringProperty(name='Name', description='Name of the TTC dataset : the data will be stored under <save path>/<name>', default='dataset') ),
('camera_train_target', bpy.props.PointerProperty(type=bpy.types.Object, name=TRAIN_CAM, description='Pointer to training camera', poll=poll_is_camera) ),
('camera_test_target', bpy.props.PointerProperty(type=bpy.types.Object, name=TEST_CAM, description='Pointer to testing camera', poll=poll_is_camera) ),

('cos_radius', bpy.props.FloatProperty(name='Sampling Radius', description='Radius of the COS sampling sphere', default=5.0, soft_min=0.0) ),
('cos_train_num_samples', bpy.props.IntProperty(name='Num Samples', description='Number of samples to take from the COS sampling sphere', default=64, soft_min=1) ),
('cos_test_num_samples', bpy.props.IntProperty(name='Num Samples', description='Number of samples to take from the COS sampling sphere', default=5, soft_min=1) ),
('cos_above_horizon_only', bpy.props.BoolProperty(name='Above Horizon Only', description='Whether to only sample points above the horizon', default=True) ),

('train_camera_collection', bpy.props.PointerProperty(type=bpy.types.Collection, name='Train Camera Collection', description='Collection containing the cameras to use for the Train dataset') ),
('test_camera_collection', bpy.props.PointerProperty(type=bpy.types.Collection, name='Test Camera Collection', description='Collection containing the cameras to use for the Test dataset') ),
('dfc_dataset_name', bpy.props.StringProperty(name='Name', description='Name of the DFC dataset : the data will be stored under <save path>/<name>', default='dataset') ),
]

# classes to register / unregister
CLASSES = [
blender_nerf_ui.BlenderNeRF_UI,
sof_ui.SOF_UI,
ttc_ui.TTC_UI,
cos_ui.COS_UI,
sof_operator.SubsetOfFrames,
ttc_operator.TrainTestCameras
ttc_operator.TrainTestCameras,
cos_operator.RandomCamerasAroundOrigin,
dataset_from_cameras_operator.DatasetFromCameras,
dataset_from_cameras_ui.DatasetFromCameras_UI,
]


Expand All @@ -73,7 +86,7 @@ def post_render(scene):

# clean directory name (unsupported characters replaced) and output path
output_dir = bpy.path.clean_name(method_dataset_name)
output_path = os.path.join(scene.save_path, output_dir)
output_path = os.path.join(bpy.path.abspath(scene.save_path), output_dir)

# compress dataset and remove folder (only keep zip)
shutil.make_archive(output_path, 'zip', output_path) # output filename = output_path
Expand Down
40 changes: 28 additions & 12 deletions blender_nerf_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ def get_camera_intrinsics(self, scene, camera):
s_u = 1 / pixel_size_mm_per_px
s_v = 1 / pixel_size_mm_per_px / pixel_aspect_ratio

K = [[s_u, 0, optical_center_x],
[0, s_v, optical_center_y],
[0, 0, 1]]

camera_intr_dict = {
'camera_angle_x': camera_angle_x,
'camera_angle_y': camera_angle_y,
Expand All @@ -64,6 +68,7 @@ def get_camera_intrinsics(self, scene, camera):
'cy': optical_center_y,
'w': width_res_in_px,
'h': height_res_in_px,
'K': K,
'aabb_scale': scene.aabb
}

Expand All @@ -72,27 +77,38 @@ def get_camera_intrinsics(self, scene, camera):
# camera extrinsics (transform matrices)
def get_camera_extrinsics(self, scene, camera, mode='TRAIN', method='SOF'):
assert mode == 'TRAIN' or mode == 'TEST'
assert method == 'SOF' or method == 'TTC' or method == 'COS'
assert method == 'SOF' or method == 'TTC' or method == 'COS' or method == 'DFC'

scene.camera = camera
initFrame = scene.frame_current
step = scene.train_frame_steps if (mode == 'TRAIN' and method == 'SOF') else scene.frame_step

camera_extr_dict = []
for frame in range(scene.frame_start, scene.frame_end + 1, step):
scene.frame_set(frame)
filename = os.path.basename( scene.render.frame_path(frame=frame) )
if method != "DFC":
for frame in range(scene.frame_start, scene.frame_end + 1, step):
scene.frame_set(frame)
filename = os.path.basename( scene.render.frame_path(frame=frame) )
filedir = OUTPUT_TRAIN * (mode == 'TRAIN') + OUTPUT_TEST * (mode == 'TEST')
filepath = os.path.join(filedir, filename)
frame_data = {
'file_path': filepath,
'transform_matrix': listify_matrix( camera.matrix_world )
}

camera_extr_dict.append(frame_data)

scene.frame_set(initFrame) # set back to initial frame

return camera_extr_dict
else:
filedir = OUTPUT_TRAIN * (mode == 'TRAIN') + OUTPUT_TEST * (mode == 'TEST')

filepath = os.path.join(filedir, f"{camera.name}.png")
frame_data = {
'file_path': os.path.join(filedir, filename),
'file_path': filepath,
'transform_matrix': listify_matrix( camera.matrix_world )
}

camera_extr_dict.append(frame_data)

scene.frame_set(initFrame) # set back to initial frame

return camera_extr_dict
return camera_extr_dict

def save_json(self, directory, filename, data, indent=4):
filepath = os.path.join(directory, filename)
Expand All @@ -103,7 +119,7 @@ def is_power_of_two(self, x):
return math.log2(x).is_integer()

def asserts(self, scene, method='SOF'):
assert method == 'SOF' or method == 'TTC' or method == 'COS'
assert method == 'SOF' or method == 'TTC' or method == 'COS' or method == 'DFC'

camera = scene.camera
train_camera = scene.camera_train_target
Expand Down
52 changes: 52 additions & 0 deletions cos_operator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import os
import shutil
import bpy
import math
import mathutils
import random
import numpy as np


def create_collection(collection_name, num_cameras, radius, above_horizon_only):
collection = bpy.data.collections.new(collection_name)
bpy.context.scene.collection.children.link(collection)
for i in range(num_cameras):
theta = random.uniform(0, 2 * math.pi)
phi = math.acos(random.uniform(-1, 1))
x = radius * math.sin(phi) * math.cos(theta)
y = radius * math.sin(phi) * math.sin(theta)
z = radius * math.cos(phi)

if above_horizon_only:
z = abs(z)

direction = mathutils.Vector((-x, -y, -z))
direction.normalize()
rot_quat = direction.to_track_quat('-Z', 'Y')
# (0, 0, 0) is facing top-down
# we want to face the origin
camera = bpy.data.cameras.new('COS_Camera')
camera_ob = bpy.data.objects.new('COS_Camera', camera)
camera_ob.location = (x, y, z)
camera_ob.rotation_euler = rot_quat.to_euler()
collection.objects.link(camera_ob)
return collection


class RandomCamerasAroundOrigin(bpy.types.Operator):
"""Random Cameras Around Origin Operator
Given a radius and a number n, this operator will randomly create
n cameras that faces the origin on the sphere of radius r around the origin, and leave them selected.
"""
bl_idname = 'object.random_cameras_around_origin'
bl_label = 'Random Cameras Around Origin'

def execute(self, context):
scene = context.scene
radius = scene.cos_radius
# create new collection for cameras
create_collection('Train_Cameras', scene.cos_train_num_samples, radius, scene.cos_above_horizon_only)
create_collection('Test_Cameras', scene.cos_test_num_samples, radius, scene.cos_above_horizon_only)

return {'FINISHED'}

29 changes: 29 additions & 0 deletions cos_ui.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import bpy

class COS_UI(bpy.types.Panel):
'''Camera Oriented Sampling UI'''
bl_idname = 'panel.cos_ui'
bl_label = 'Camera Oriented Sampling COS'
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'BlenderNeRF'
bl_options = {'DEFAULT_CLOSED'}

def draw(self, context):
layout = self.layout
scene = context.scene

layout.alignment = 'CENTER'

layout.use_property_split = True
layout.prop_search(scene, 'camera_target', scene, 'objects')

layout.separator()
layout.prop(scene, 'cos_radius')
layout.use_property_split = True
layout.prop(scene, 'cos_train_num_samples')
layout.prop(scene, 'cos_test_num_samples')
layout.prop(scene, 'cos_above_horizon_only')

layout.separator()
layout.operator('object.random_cameras_around_origin', text='create collection of random cameras')
88 changes: 88 additions & 0 deletions dataset_from_cameras_operator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import os
import shutil
import time

import bpy
from . import blender_nerf_operator


# global addon script variables
OUTPUT_TRAIN = 'images_train'
OUTPUT_TEST = 'images_test'

class DatasetFromCameras(blender_nerf_operator.BlenderNeRF_Operator):
"""
Blender x NeRF Dataset From Cameras Operator, creates a dataset by running all the frames of all the cameras in the scene.
"""
bl_idname = 'object.dataset_from_cameras'
bl_label = 'Dataset From Cameras'

def execute(self, context):
scene = context.scene


error_messages = self.asserts(scene, method='DFC')
if len(error_messages) > 0:
self.report({'ERROR'}, error_messages[0])
return {'FINISHED'}

# clean directory name (unsupported characters replaced) and output path
output_dir = bpy.path.clean_name(scene.dfc_dataset_name)
output_path = os.path.join(scene.save_path, output_dir)

# initial property values might have changed since depsgraph_update_post handler
scene.init_frame_step = scene.frame_step
scene.init_output_path = scene.render.filepath


if scene.train_data and scene.train_camera_collection is not None:
output_train_path = os.path.join(output_path, '%s_train' % output_dir)
output_train = os.path.join(output_train_path, OUTPUT_TRAIN)
os.makedirs(output_train, exist_ok=True)

# training transforms
first_camera = scene.train_camera_collection.objects[0]
output_train_data = self.get_camera_intrinsics(scene, first_camera)
output_train_data['frames'] = []
for camera in scene.train_camera_collection.objects:
output_train_data['frames'] += self.get_camera_extrinsics(scene, camera, mode='TRAIN', method='DFC')
bpy.context.scene.camera = camera
print("Rendering camera: " + camera.name)
if scene.render_frames:
# scene.is_rendering = (False, True, False)
scene.render.filepath = os.path.join(output_train, camera.name) # training frames path
init_frame = scene.frame_current
bpy.ops.render.render(write_still=True) # render scene
scene.frame_set(init_frame) # reset frame
#time.sleep(0.01)


self.save_json(output_train_path, 'transforms_train.json', output_train_data)
scene.frame_step = scene.init_frame_step
scene.render.filepath = scene.init_output_path

if scene.test_data and scene.test_camera_collection is not None:
output_test_path = os.path.join(output_path, '%s_test' % output_dir)
output_test = os.path.join(output_test_path, OUTPUT_TEST)
os.makedirs(output_test, exist_ok=True)

# testing transforms
first_camera = scene.test_camera_collection.objects[0]
output_test_data = self.get_camera_intrinsics(scene, first_camera)
output_test_data['frames'] = []
for camera in scene.test_camera_collection.objects:
output_test_data['frames'] += self.get_camera_extrinsics(scene, camera, mode='TEST', method='DFC')
bpy.context.scene.camera = camera
print("Rendering camera: " + camera.name)
if scene.render_frames:
# scene.is_rendering = (False, True, False)
scene.render.filepath = os.path.join(output_test, camera.name)
init_frame = scene.frame_current
bpy.ops.render.render(write_still=True)
scene.frame_set(init_frame)

self.save_json(output_test_path, 'transforms_test.json', output_test_data)
scene.frame_step = scene.init_frame_step
scene.render.filepath = scene.init_output_path

return {'FINISHED'}
26 changes: 26 additions & 0 deletions dataset_from_cameras_ui.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import bpy

class DatasetFromCameras_UI(bpy.types.Panel):
'''Dataset From Cameras UI'''
bl_idname = 'panel.dataset_from_cameras_ui'
bl_label = 'Dataset From Cameras DFC'
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'BlenderNeRF'
bl_options = {'DEFAULT_CLOSED'}

def draw(self, context):
layout = self.layout
scene = context.scene

layout.alignment = 'CENTER'

layout.use_property_split = True
layout.prop(scene, 'train_camera_collection')
layout.prop(scene, 'test_camera_collection')

layout.separator()
layout.prop(scene, 'dfc_dataset_name')

layout.separator()
layout.operator('object.dataset_from_cameras', text='PLAY DFC')
2 changes: 1 addition & 1 deletion ttc_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def execute(self, context):
if scene.render_frames:
scene.is_rendering = (False, True, False)
scene.render.filepath = os.path.join(output_train, '') # training frames path
bpy.ops.render.render('INVOKE_DEFAULT', animation=True, write_still=True) # render scene
bpy.ops.render.render(scene='INVOKE_DEFAULT', animation=True, write_still=True) # render scene

# if frames are rendered, the below code is executed by the handler function
if not any(scene.is_rendering):
Expand Down