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

Add tools for rendering on grid

A command line can be used to create grid based database
parent 59109e0c
No related branches found
No related tags found
No related merge requests found
#
# Default configuration file used by BlendOnGridRender
#
# Define the parameter for the renderer
BlenderRender:
# field of view of the camera
# elevation, two numbers [min, max]
# azimuth, two numbers [min, max]
fov:
elevation: [-90,90]
azimuth: [-180, 180]
# Number of pixel along the azimuth, and elevation
resolution: [360, 180]
# Filter width for each pixel
gaussian_width: 1.5
# Number of rays (samples) used for redering
# Higher number give better results, but
# require more computation time.
samples: 30
# Define the parameter for the grid
# A n-mesh will be build from position and
#orientation.
OnGrid:
# Each coordinates have 3 values,
# min, max , and number of samples
x: [-0.5,0.5,5]
y: [-0.5,0.5,5]
z: [3,3,1]
alpha_0: [1.570796 , 1.570796 ,1]
alpha_1: [0 , 0 ,1]
alpha_2: [0 , 0 ,1]
# The orientation convention is defined as:
rotconv: 'rzyx'
# Note when rotconv 'quaternion' is used:
# the coordinate q_3 is required
# q_3: [0, 0, 1]
\ No newline at end of file
"""
Bee sampler / database creator
"""
import warnings
try:
import bpy
except ModuleNotFoundError: # noqa F821
warnings.warn(
'bpy could not be imported, ' +
'please run your python program in blender')
import os
import numpy as np
import pandas as pd
from navipy.database import DataBaseSave
from navipy.scene import check_scene
class BeeSampling():
"""
BeeSampling allows to create a DataBase using Cyberbee as the renderer.
"""
def __init__(self, renderer):
"""Initialise the BeeSampling
:param renderer: A object used to render images
:type renderer: a python object
"""
# Check that the renderer can return scene
scene_func = getattr(renderer, "scene", None)
if not callable(scene_func):
raise TypeError('The renderer does not have a callable ' +
'scene function')
self.renderer = renderer
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
"""
if not (isinstance(x, np.ndarray) or isinstance(x, list)):
raise TypeError('x must be list or np.array')
if not (isinstance(y, np.ndarray) or isinstance(y, list)):
raise TypeError('y must be list or np.array')
if not (isinstance(z, np.ndarray) or isinstance(z, list)):
raise TypeError('z must be list or np.array')
if not (isinstance(alpha1, np.ndarray) or
isinstance(alpha1, list)):
raise TypeError('alpha1 must be list or np.array')
if not (isinstance(alpha2, np.ndarray) or
isinstance(alpha2, list)):
raise TypeError('alpha2 must be list or np.array')
if not (isinstance(alpha3, np.ndarray) or
isinstance(alpha3, list)):
raise TypeError('alpha3 must be list or np.array')
[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'
@property
def grid_posorients(self):
"""Position orientations to be rendered
:getter: position-orientations of the grid
:setter: set a list of locations to be randered
:type: pandas array
"""
return self.__grid_posorients.copy()
@grid_posorients.setter
def grid_posorients(self, grid_df):
"""Position orientation to be rendered
"""
columns = ['x', 'y', 'z',
'alpha_0',
'alpha_1',
'alpha_2']
for col in grid_df.columns:
if col not in columns:
raise KeyError(
'Grid dataframe should contains {} column'.format(col))
self.__grid_posorients = grid_df.copy()
@property
def blacklist_indeces(self):
""" Blacklist certain indeces of the grid posorients\
so that they are not rendered
:getter: return a list of blacklisted indeces
:setter: set a blacklisted indeces
:type: list
"""
@blacklist_indeces.setter
def blacklist_indeces(self, indeces):
""" Blacklist certain indeces
"""
if not isinstance(indeces, list):
raise TypeError('indeces must be a list')
if any(np.isnan(indeces)):
raise ValueError('indeces must not contain nans')
self.__grid_posorients.loc[indeces, :] = np.nan
def render(self, database_filename):
""" Render all images at position specified by grid_posorients.
:param database_filename: path to the database
:type database_filename: str
"""
if not isinstance(database_filename, str):
raise TypeError('filename must be a string')
database_folder = os.path.dirname(database_filename)
if not os.path.exists(database_folder):
if database_folder: # Check empty string
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.iloc[::-1].iterrows():
print(frame_i)
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
scene = self.renderer.scene(posorient)
check_scene(scene)
scene = scene[..., 0]
distance = scene[..., -1]
distance[distance > self.world_dim] = self.world_dim
scene[..., -1] = distance
dataloger.write_image(posorient, scene)
print('rendering completed')
"""
"""
import sys
import argparse
import os
import inspect
import pkg_resources
import tempfile
# Following need to be imported in blender as well
import yaml
import numpy as np
from navipy.sensors.renderer import BlenderRender
import navipy.maths.constants as mconst
importwithinblender = [
'import yaml',
'import numpy as np',
'from navipy.sensors.renderer import BlenderRender',
'import navipy.maths.constants as mconst']
def parser_blend_ongrid():
# Create command line options
parser = argparse.ArgumentParser()
arghelp = 'Path to the environment (.blend) in which your agent lives'
defaultworld = pkg_resources.resource_filename(
'navipy', 'resources/twocylinders_world.blend')
defaultconfig = pkg_resources.resource_filename(
'navipy', 'resources/configs/BlenderOnGridRender.yaml')
defaultoutput = tempfile.NamedTemporaryFile().name
parser.add_argument('--blender-world',
type=str,
default=defaultworld,
help=arghelp)
arghelp = 'Outputfile to store the rendered database'
parser.add_argument('--output-file',
type=str,
default=defaultoutput,
help=arghelp)
arghelp = 'Configuration file'
parser.add_argument('--config-file',
type=str,
default=defaultconfig,
help=arghelp)
arghelp = 'Command to run blender\n'
arghelp += 'If not provided, the script will try to find the command'
arghelp += " by using: shutil.which('blender')"
parser.add_argument('--blender-command',
type=str,
default=None,
help=arghelp)
arghelp = 'To display some stuff \n'
arghelp += ' * -v print command \n'
arghelp += ' * -vv print also script'
parser.add_argument('-v', '--verbose',
action='count',
default=0,
help=arghelp)
return parser
def run(config_file, outputfile):
renderer = BlenderRender()
renderer.config_file = config_file
try:
with open(config_file, 'r') as stream:
try:
config = yaml.load(stream)
except yaml.YAMLError as exc:
print(exc)
except IOError:
print("The file could not be read")
if 'OnGrid' not in config.keys():
raise KeyError(
'OnGrid should be a section in the yaml config file')
ongrid_config = config['OnGrid']
grid_param = dict()
for val in ['x', 'y', 'z']:
if val in ongrid_config.keys():
xs = ongrid_config[val]
if len(xs) != 3:
raise ValueError(
'{} should have 3 values (min, max, nsample)'.format(val))
grid_param[val] = np.linspace(xs[0], xs[1], xs[2])
else:
raise KeyError('Yaml config file should contain {}'.format(val))
if 'rotconv' in ongrid_config.keys():
rotconv = ongrid_config['rotconv']
if (rotconv in mconst._AXES2TUPLE) or (rotconv == 'quaternion'):
for ii in range(3):
val_a = 'alpha_{}'.format(ii)
val_q = 'q_{}'.format(ii)
if val_a in ongrid_config.keys():
xs = ongrid_config[val_a]
if len(xs) != 3:
msg = '{} should have 3 values (min, max, nsample)'
msg = msg.format(val_a)
raise ValueError(msg)
grid_param[val_a] = np.linspace(xs[0], xs[1], xs[2])
elif val_q in ongrid_config.keys():
xs = ongrid_config[val_q]
if len(xs) != 3:
msg = '{} should have 3 values (min, max, nsample)'
msg = msg.format(val_q)
raise ValueError(msg)
grid_param[val_q] = np.linspace(xs[0], xs[1], xs[2])
else:
msg = 'Yaml config file should contain {} or {}'
msg = msg.format(val_a, val_q)
raise KeyError(msg)
if rotconv == 'quaternion':
val_q = 'q_{}'.format(3)
if val_q in ongrid_config.keys():
xs = ongrid_config[val_q]
if len(xs) != 3:
msg = '{} should have 3 values (min, max, nsample)'
msg = msg.format(val_q)
raise ValueError(msg)
grid_param[val_q] = np.linspace(xs[0], xs[1], xs[2])
else:
msg = 'Yaml config file should contain {}'
msg = msg.format(val_q)
raise KeyError(msg)
else:
raise KeyError('Yaml config file should contain {}'.format(rotconv))
renderer.render_ongrid(outputfile, rotconv=rotconv, **grid_param)
def main():
# encoding for temporary file
encoding = 'utf-8'
# Fetch arguments
args = parser_blend_ongrid().parse_args()
# Some output
print('-----')
print('Config file:\n{}'.format(args.config_file))
print('Blender file:\n{}'.format(args.blender_world))
print('Output file:\n{}'.format(args.output_file))
print('-----')
# Create tempfile with testing code and then call blendnavipy
header = '# Generated by {}\n'.format(sys.argv[0])
with tempfile.NamedTemporaryFile() as tfile:
# Start of file
tfile.write(header.encode(encoding))
for line in importwithinblender:
tfile.write(line.encode(encoding))
tfile.write('\n'.encode(encoding))
for line in inspect.getsourcelines(run)[0]:
tfile.write(line.encode(encoding))
tfile.write('\n\n'.encode(encoding))
tfile.write('try:\n'.encode(encoding))
tfile.write(' run("{}","{}")\n'.format(
args.config_file, args.output_file).encode(encoding))
tfile.write(' sys.exit(0)\n'.encode(encoding))
tfile.write('except Exception:\n'.encode(encoding))
tfile.write(' sys.exit(1)\n'.encode(encoding))
tfile.seek(0)
command = 'blendnavipy --blender-world {} --python-script {}'
command = command.format(args.blender_world, tfile.name)
if args.blender_command is not None:
command += ' --blender-command {}'.format(args.blender_command)
for _ in range(args.verbose):
command += ' -v'
os.system(command)
if __name__ == "__main__":
# execute only if run as a script
main()
......@@ -10,7 +10,7 @@ import os
import inspect
def parser_blendnavipy():
def parser_blendunittest():
# Create command line options
parser = argparse.ArgumentParser()
arghelp = 'Path to the environment (.blend) in which your agent lives'
......@@ -63,7 +63,7 @@ def main():
encoding = 'utf-8'
# Fetch arguments
args = parser_blendnavipy().parse_args()
args = parser_blendunittest().parse_args()
# Create tempfile with testing code and then call blendnavipy
header = '# Generated by {}\n'.format(sys.argv[0])
......
......@@ -288,8 +288,6 @@ class BlenderRender(AbstractRender):
'BlenderRender should be a section in the yaml config file')
blendconfig = config['BlenderRender']
# Loading the field of view in the camera
print(blendconfig)
print(blendconfig.keys())
if 'fov' in blendconfig.keys():
fov = np.zeros((2, 2))
if 'elevation' in blendconfig['fov'].keys():
......@@ -327,6 +325,7 @@ class BlenderRender(AbstractRender):
# Load worldlimit
if 'worldlimit' in blendconfig.keys():
self.worldlimit = blendconfig['worldlimit']
self.__config_file = config_file
@property
def cycle_samples(self):
......
......@@ -56,6 +56,7 @@ setup_dict = {'name': 'navipy',
'entry_points': {
'console_scripts': [
'blendnavipy=navipy.sensors.blendnavipy:main',
'blendunittest=navipy.sensors.blendunittest:main']}, }
'blendunittest=navipy.sensors.blendunittest:main',
'blendongrid=navipy.sensors.blend_ongrid:main']}, }
setup(**setup_dict)
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