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