From 363589729c59bd1d31e209828e344c0fde4adc38 Mon Sep 17 00:00:00 2001 From: "Olivier J.N. Bertrand" <olivier.bertrand@uni-bielefeld.de> Date: Fri, 1 Jun 2018 16:54:10 +0200 Subject: [PATCH] Add tools for rendering on grid A command line can be used to create grid based database --- .../configs/BlenderOnGridRender.yaml | 38 ++++ navipy/sensors/bee_sampling.py | 173 ----------------- navipy/sensors/blend_ongrid.py | 175 ++++++++++++++++++ navipy/sensors/blendunittest.py | 4 +- navipy/sensors/renderer.py | 3 +- setup.py | 3 +- 6 files changed, 218 insertions(+), 178 deletions(-) create mode 100644 navipy/resources/configs/BlenderOnGridRender.yaml delete mode 100644 navipy/sensors/bee_sampling.py create mode 100644 navipy/sensors/blend_ongrid.py diff --git a/navipy/resources/configs/BlenderOnGridRender.yaml b/navipy/resources/configs/BlenderOnGridRender.yaml new file mode 100644 index 0000000..4a91dec --- /dev/null +++ b/navipy/resources/configs/BlenderOnGridRender.yaml @@ -0,0 +1,38 @@ +# +# 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 diff --git a/navipy/sensors/bee_sampling.py b/navipy/sensors/bee_sampling.py deleted file mode 100644 index baf6120..0000000 --- a/navipy/sensors/bee_sampling.py +++ /dev/null @@ -1,173 +0,0 @@ -""" -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') diff --git a/navipy/sensors/blend_ongrid.py b/navipy/sensors/blend_ongrid.py new file mode 100644 index 0000000..19477dd --- /dev/null +++ b/navipy/sensors/blend_ongrid.py @@ -0,0 +1,175 @@ +""" + +""" +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() diff --git a/navipy/sensors/blendunittest.py b/navipy/sensors/blendunittest.py index 4dabe55..cc7aedd 100644 --- a/navipy/sensors/blendunittest.py +++ b/navipy/sensors/blendunittest.py @@ -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]) diff --git a/navipy/sensors/renderer.py b/navipy/sensors/renderer.py index fef4b88..9bd2b39 100644 --- a/navipy/sensors/renderer.py +++ b/navipy/sensors/renderer.py @@ -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): diff --git a/setup.py b/setup.py index 0b97bb5..4f8cc7e 100644 --- a/setup.py +++ b/setup.py @@ -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) -- GitLab