Skip to content
Snippets Groups Projects
Commit 2ba8c0fb authored by Olivier Bertrand's avatar Olivier Bertrand
Browse files

Update todo list

parent 91ebfe89
No related branches found
No related tags found
No related merge requests found
"""
How to test the script:
-----------------------
>>> blender test.blend --background --python BeeSampling.py
:Author: Olivier Bertrand (olivier.bertrand@uni-bielefeld.de)
:Parent module: Scene_rendering
"""
import bpy
import os
import numpy as np
import pandas as pd
import warnings
from Cyberbee import Cyberbee
from DataBase import DataBaseSave
class BeeSampling(Cyberbee):
"""
BeeSampling is a class deriving from Cyberbee.
The BeeSampling can be used to generate a database of
images taken on a rectangular regular grid. For the database,
the BeeSampling rely on DataBase
It worth noting that the generated database can take a large
harddrive space, as each image is composed of 4 channels of 180x360 pixels.
"""
def __init__(self):
"""Initialise the BeeSampling"""
Cyberbee.__init__(self)
self.blenddirname = os.path.dirname(bpy.data.filepath)
self.blendfilename = os.path.basename(bpy.data.filepath)
self.__grid_posorients = None
self.__grid_size = None
self.world_dim = np.inf
def create_sampling_grid(self, x, y, z, alpha1, alpha2, alpha3):
"""Create a cubic grid from all the sampling points
:param x: the positions along the x-axis
:param y: the positions along the y-axis
:param z: the positions along the z-axis
:param alpha1: the first euler angles
:param alpha2: the 2nd euler angles
:param alpha3: the 3rd euler angles
"""
[mx, my, mz, ma1, ma2, ma3] = np.meshgrid(x,
y,
z,
alpha1,
alpha2,
alpha3)
self.grid_size = mx.shape
mx = mx.flatten()
my = my.flatten()
mz = mz.flatten()
ma1 = ma1.flatten()
ma2 = ma2.flatten()
ma3 = ma3.flatten()
self.__grid_posorients = pd.DataFrame(index=range(mx.shape[0]),
columns=['x', 'y', 'z',
'alpha_0',
'alpha_1',
'alpha_2'])
self.__grid_posorients.loc[:, 'x'] = mx
self.__grid_posorients.loc[:, 'y'] = my
self.__grid_posorients.loc[:, 'z'] = mz
self.__grid_posorients.loc[:, 'alpha_0'] = ma1
self.__grid_posorients.loc[:, 'alpha_1'] = ma2
self.__grid_posorients.loc[:, 'alpha_2'] = ma3
self.__grid_posorients.index.name = 'grid_index'
self.__grid_posorients.name = 'grid_position_orientation'
def get_grid_posorients(self):
"""return a copy of the posorientation matrix
:returns: position-orientations of the grid
:rtype: pandas array
"""
return self.__grid_posorients.copy()
def set_gridindeces2nan(self, indeces):
"""Set certain grid point to nan, so they will be ignore in the rendering
:param indeces: a list of indeces to be set to nan
"""
self.__grid_posorients.loc[indeces, :] = np.nan
def render(self, database_filename):
database_folder = os.path.dirname(database_filename)
if not os.path.exists(database_folder):
os.makedirs(database_folder)
dataloger = DataBaseSave(database_filename,
channels=['R', 'G', 'B', 'D'],
arr_dtype=np.uint8)
for frame_i, posorient in self.__grid_posorients.iterrows():
if np.any(np.isnan(posorient)):
# warnings.warn('frame_i: {} posorient nans'.format(frame_i))
continue
rowid = dataloger.get_posid(posorient)
if dataloger.check_data_validity(rowid):
warnings.warn(
'frame_i: {} data is valid rowid {}'.format(frame_i,
rowid))
continue
# The position-orientatios is valid (none nan)
# and the cmaxminrange has not already been assigned
# so the image need to be rendered
self.update(posorient)
distance = self.get_distance()
distance[distance > self.world_dim] = self.world_dim
image = self.get_image()
image[:, :, 3] = distance
dataloger.write_image(posorient, image)
print('rendering completed')
if __name__ == "__main__":
import tempfile
bee_samp = BeeSampling()
# Create mesh
world_dim = 15.0
x = np.linspace(-7.5, 7.5, 5)
y = np.linspace(-7.5, 7.5, 5)
z = np.arange(1, 8, 2)
alpha_1 = np.array([0]) + np.pi / 2
alpha_2 = np.array([0])
alpha_3 = np.array([0])
bee_samp.create_sampling_grid(
x, y, z, alpha1=alpha_1, alpha2=alpha_2, alpha3=alpha_3)
bee_samp.world_dim = world_dim
grid_pos = bee_samp.get_grid_posorients()
condition = (grid_pos.x**2 + grid_pos.y**2) < ((bee_samp.world_dim / 2)**2)
bee_samp.set_gridindeces2nan(condition[condition == 0].index)
bee_samp.set_cycle_samples(samples=5)
with tempfile.TemporaryDirectory() as folder:
bee_samp.render(folder + '/database.db')
"""
How to test the script:
-----------------------
>>> blender test.blend --background --python Cyberbee.py
:Author: Olivier Bertrand (olivier.bertrand@uni-bielefeld.de)
:Parent module: Scene_rendering
"""
import bpy
import numpy as np
import tempfile
import os
class Cyberbee():
"""
Cyberbee is a small class binding python with blender.
With Cyberbee one can move the bee to a position, and render what
the bee see at this position.
The Bee eye is a panoramic camera with equirectangular projection
The light rays attaining the eyes are filtered with a gaussian.
"""
def __init__(self):
"""Initialise the Cyberbee"""
# Rendering engine needs to be Cycles to support panoramic
# equirectangular camera
bpy.context.scene.render.engine = 'CYCLES'
bpy.context.scene.render.layers["RenderLayer"].use_pass_z = True
# Look for object camera
camera_found = False
for obj in bpy.context.scene.objects:
if obj.type == 'CAMERA':
self.camera = obj
camera_found = True
break
assert camera_found, 'The blender file does not contain a camera'
# The bee eye is panoramic, and with equirectangular projection
self.camera.data.type = 'PANO'
self.camera.data.cycles.panorama_type = 'EQUIRECTANGULAR'
# Filtering props
bpy.context.scene.cycles.filter_type = 'GAUSSIAN'
# Call all set function with default values
self.set_camera_rotation_mode()
self.set_camera_fov()
self.set_camera_gaussian_width()
self.set_camera_resolution()
self.set_cycle_samples()
# switch on nodes
# Create render link to OutputFile with Image and Z buffer
bpy.context.scene.use_nodes = True
scene = bpy.context.scene
nodes = scene.node_tree.nodes
render_layers = nodes['Render Layers']
output_file = nodes.new("CompositorNodeOutputFile")
output_file.format.file_format = "OPEN_EXR"
output_file.file_slots.remove(output_file.inputs[0])
tmp_fileoutput = dict()
tmp_fileoutput['Image'] = 'Image'
tmp_fileoutput['Depth'] = 'Depth'
tmp_fileoutput['Folder'] = tempfile.TemporaryDirectory().name
tmp_fileoutput['ext'] = '.exr'
output_file.file_slots.new(tmp_fileoutput['Image'])
output_file.file_slots.new(tmp_fileoutput['Depth'])
output_file.base_path = tmp_fileoutput['Folder']
scene.node_tree.links.new(
render_layers.outputs['Image'],
output_file.inputs['Image']
)
scene.node_tree.links.new(
render_layers.outputs['Z'],
output_file.inputs['Depth']
)
self.tmp_fileoutput = tmp_fileoutput
def set_camera_rotation_mode(self, mode='XYZ'):
"""change the camera rotation mode
:param mode: the mode of rotation for the camera see blender doc
(default: 'XYZ').
:type mode: a string
.. seealso: blender bpy.data.scenes["Scene"].camera.rotation_mode
"""
bpy.data.scenes["Scene"].camera.rotation_mode = mode
def get_camera_rotation_mode(self):
"""get the current camera rotation mode
:returns: the mode of rotation used by the camera
:rtype: string
"""
return bpy.data.scenes["Scene"].camera.rotation_mode
def set_cycle_samples(self, samples=30):
"""change the samples for rendering with cycle
:param samples: the number of samples to use when rendering images
:type samples: int
"""
bpy.context.scene.cycles.samples = samples
def get_cycle_samples(self):
"""get the samples for rendering with cycle
:returns: the number of samples used for the rendering
:rtype: int
"""
return bpy.context.scene.cycles.samples
def set_camera_fov(self, latmin=-90, latmax=+90,
longmin=-180, longmax=+180):
"""change the field of view of the panoramic camera
:param latmin: minimum latitude (in deg)
:type latmin: float
:param latmax: maximum latitude (in deg)
:type latmax: float
:param longmin: minimum longitude (in deg)
:type longmin: float
:param longmin: maximum longitude (in deg)
:type longmin: float
"""
assert self.camera.data.type == 'PANO', 'Camera is not panoramic'
assert self.camera.data.cycles.panorama_type == 'EQUIRECTANGULAR',\
'Camera is not equirectangular'
self.camera.data.cycles.latitude_min = np.deg2rad(latmin)
self.camera.data.cycles.latitude_max = np.deg2rad(latmax)
self.camera.data.cycles.longitude_min = np.deg2rad(longmin)
self.camera.data.cycles.longitude_max = np.deg2rad(longmax)
def get_camera_fov(self):
"""get fov of camera
:returns: the field of view of the camera as min/max,longitude/latitude
in degrees
:rtype: dict
"""
assert self.camera.data.type == 'PANO', 'Camera is not panoramic'
assert self.camera.cycles.panorama_type == 'EQUIRECTANGULAR',\
'Camera is not equirectangular'
fov = dict()
fov['latitude_min'] = np.rad2ged(self.camera.data.cycles.latitude_min)
fov['latitude_max'] = np.rad2ged(self.camera.data.cycles.latitude_max)
fov['longitude_min'] = np.rad2ged(
self.camera.data.cycles.longitude_min)
fov['longitude_max'] = np.rad2ged(
self.camera.data.cycles.longitude_max)
return fov
def set_camera_gaussian_width(self, gauss_w=1.5):
"""change width of the gaussian spatial filter
:param gauss_w: width of the gaussian filter
:type gauss_w: float
"""
bpy.context.scene.cycles.filter_width = gauss_w
def get_camera_gaussian_width(self, gauss_w=1.5):
"""get width of the gaussian spatial filter
:returns: the width of the gaussian filter
:rtype: float
"""
return bpy.context.scene.cycles.filter_width
def set_camera_resolution(self, resolution_x=360, resolution_y=180):
"""change the camera resolution (nb of pixels)
:param resolution_x: number of pixels along the x-axis of the camera
:type resolution_x: int
:param resolution_y: number of pixels along the y-axis of the camera
:type resolution_y: int
"""
bpy.context.scene.render.resolution_x = resolution_x
bpy.context.scene.render.resolution_y = resolution_y
bpy.context.scene.render.resolution_percentage = 100
def get_camera_resolution(self):
"""return camera resolution (x,y)
:returns: the resolution of the camera along (x-axis,y-axis)
:rtype: (int,int)
"""
resolution_x = bpy.context.scene.render.resolution_x
resolution_y = bpy.context.scene.render.resolution_y
return resolution_x, resolution_y
def update(self, posorient):
"""assign the position and the orientation of the camera.
:param posorient: is a 1x6 vector continaing:
x,y,z, angle_1, angle_2, angle_3,
here the angles are euler rotation around the axis
specified by scene.camera.rotation_mode
:type posorient: 1x6 double array
"""
assert len(posorient) == 6, 'posorient should be a 1x6 double array'
self.camera.location = posorient[:3]
self.camera.rotation_euler = posorient[3:]
# Render
bpy.ops.render.render()
def get_image(self):
"""return the last rendered image as a numpy array
:returns: the image (height,width,4)
:rtype: a double numpy array
.. note: A temporary file will be written on the harddrive,
due to API blender limitation
"""
# save image as a temporary file, and then loaded
# sadly the rendered image pixels can not directly be access
filename = os.path.join(self.tmp_fileoutput['Folder'],
self.tmp_fileoutput['Image'] + '0001')
im_width, im_height = self.get_camera_resolution()
im = bpy.data.images.load(filename)
pixels = np.array(im.pixels)
# im=PIL.Image.open(filename)
# pixels=np.asarray(im)
pixels = pixels.reshape([im_height, im_width, 4])
return pixels
def get_distance(self):
"""return the last rendered distance map as a numpy array
:returns: the distance map (height, width)
:rtype: a double numpy array
.. note: A temporary file will be written on the harddrive,
due to API blender limitation
"""
# save image as a temporary file, and then loaded
# sadly the rendered image pixels can not directly be access
filename = os.path.join(self.tmp_fileoutput['Folder'],
self.tmp_fileoutput['Depth'] + '0001')
im_width, im_height = self.get_camera_resolution()
im = bpy.data.images.load(filename)
distance = np.array(im.pixels)
# im=PIL.Image.open(filename)
# distance=np.asarray(im)
distance = distance.reshape([im_height, im_width, 4])
distance = distance[:, :, 0]
return distance
if __name__ == "__main__":
# Initiate the Cyberbee
mybee = Cyberbee()
frames_per_revolution = 5.0
step_size = 2 * np.pi / frames_per_revolution
posorients = np.zeros((frames_per_revolution, 6))
posorients[:, 0] = np.sin(np.arange(frames_per_revolution) * step_size) * 5
posorients[:, 1] = np.cos(np.arange(frames_per_revolution) * step_size) * 5
for frame_i, posorient in enumerate(posorients):
mybee.update(posorient)
# Test image
image = mybee.get_image()
# Test distance
distance = mybee.get_distance()
print('Cyberbee OK')
"""
The beesampling class
.. tothinkof: conditional bpy import to build doc from comments
"""
import bpy
import os
......@@ -71,6 +73,13 @@ harddrive space, as each image is composed of 4 channels of 180x360 pixels.
:returns: position-orientations of the grid
:rtype: pandas array
.. todo: use @property
def grid_posorients(self)
.. todo: create @property.setter
def grid_posorients(self,posorients)
(need a type check, and col check, and copy of df)
"""
return self.__grid_posorients.copy()
......@@ -78,6 +87,9 @@ harddrive space, as each image is composed of 4 channels of 180x360 pixels.
"""Set certain grid point to nan, so they will be ignore in the rendering
:param indeces: a list of indeces to be set to nan
.. todo: use @property.setter
def blacklist_indeces(self,indeces)
"""
self.__grid_posorients.loc[indeces, :] = np.nan
......
......@@ -5,6 +5,9 @@
:Author: Olivier Bertrand (olivier.bertrand@uni-bielefeld.de)
:Parent module: Scene_rendering
..tothinkof for the doc bpy will raise an issue.
conditional import of bpy
"""
import bpy
import numpy as np
......@@ -23,7 +26,9 @@ class Cyberbee():
"""
def __init__(self):
"""Initialise the Cyberbee"""
"""Initialise the Cyberbee
..todo check that TemporaryDirectory is writtable and readable
"""
# Rendering engine needs to be Cycles to support panoramic
# equirectangular camera
bpy.context.scene.render.engine = 'CYCLES'
......@@ -82,6 +87,9 @@ class Cyberbee():
(default: 'XYZ').
:type mode: a string
.. seealso: blender bpy.data.scenes["Scene"].camera.rotation_mode
..todo: Use @property.setter
def camera_rotation_mode(self, mode='XYZ')
"""
bpy.data.scenes["Scene"].camera.rotation_mode = mode
......@@ -90,6 +98,9 @@ class Cyberbee():
:returns: the mode of rotation used by the camera
:rtype: string
..todo: Use @property
def camera_rotation_mode(self)
"""
return bpy.data.scenes["Scene"].camera.rotation_mode
......@@ -98,6 +109,9 @@ class Cyberbee():
:param samples: the number of samples to use when rendering images
:type samples: int
..todo: Use @property.setter
def cycle_samples(self, samples=30)
"""
bpy.context.scene.cycles.samples = samples
......@@ -106,6 +120,9 @@ class Cyberbee():
:returns: the number of samples used for the rendering
:rtype: int
..todo use @property
def cycle_samples(self)
"""
return bpy.context.scene.cycles.samples
......@@ -121,6 +138,13 @@ class Cyberbee():
:type longmin: float
:param longmin: maximum longitude (in deg)
:type longmin: float
..todo use @property.setter
def camera_fov(self, latlongrange)
here latlongrange is a a 2x2 list or array:
[[latmin,latmax],[longmin,longmax]]
..todo Change assert to if -> raise TypeError()/KeyError()
"""
assert self.camera.data.type == 'PANO', 'Camera is not panoramic'
assert self.camera.data.cycles.panorama_type == 'EQUIRECTANGULAR',\
......@@ -136,6 +160,11 @@ class Cyberbee():
:returns: the field of view of the camera as min/max,longitude/latitude
in degrees
:rtype: dict
..todo use @property
def camera_fov()
..todo Change assert to if -> raise TypeError/KeyError
"""
assert self.camera.data.type == 'PANO', 'Camera is not panoramic'
assert self.camera.cycles.panorama_type == 'EQUIRECTANGULAR',\
......@@ -154,6 +183,12 @@ class Cyberbee():
:param gauss_w: width of the gaussian filter
:type gauss_w: float
..todo use @property.setter
def camera_gaussian_width(self)
..todo check that input argument is of correct type,
if not raise TypeError()
"""
bpy.context.scene.cycles.filter_width = gauss_w
......@@ -162,6 +197,9 @@ class Cyberbee():
:returns: the width of the gaussian filter
:rtype: float
..todo use @property
def camera_gaussian_width(self)
"""
return bpy.context.scene.cycles.filter_width
......@@ -172,6 +210,12 @@ class Cyberbee():
:type resolution_x: int
:param resolution_y: number of pixels along the y-axis of the camera
:type resolution_y: int
..todo use @property.setter
def camera_resolution(self,resolution)
here resolution is [res_x,res_y]
..todo check type and raise TypeError
"""
bpy.context.scene.render.resolution_x = resolution_x
bpy.context.scene.render.resolution_y = resolution_y
......@@ -182,6 +226,9 @@ class Cyberbee():
:returns: the resolution of the camera along (x-axis,y-axis)
:rtype: (int,int)
..todo use @property
def camera_resolution(self)
"""
resolution_x = bpy.context.scene.render.resolution_x
resolution_y = bpy.context.scene.render.resolution_y
......@@ -210,6 +257,9 @@ class Cyberbee():
.. note: A temporary file will be written on the harddrive,
due to API blender limitation
.. todo: use @property
def image(self)
"""
# save image as a temporary file, and then loaded
# sadly the rendered image pixels can not directly be access
......@@ -232,6 +282,9 @@ class Cyberbee():
.. note: A temporary file will be written on the harddrive,
due to API blender limitation
.. todo: use @property
def distance(self)
"""
# save image as a temporary file, and then loaded
# sadly the rendered image pixels can not directly be access
......
......@@ -34,4 +34,6 @@ Need to propagate the changes through all the code (see rendering / processing /
- rename capitalised variable A,ATA, and b as longer variable name [future PEP8 will forbid this]
------------------------------------------------------
0007: Fix test processing
\ No newline at end of file
0007: Improve cyber_bee so that every getter and setter are properties
0008: Improve bee_sampling so that every getter and setter are properties
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment