From 82223b7d157895f9d5d02e1c18e6e544274bf0d6 Mon Sep 17 00:00:00 2001
From: "Olivier J.N. Bertrand" <olivier.bertrand@uni-bielefeld.de>
Date: Wed, 26 Sep 2018 14:14:15 +0200
Subject: [PATCH] Add viewing dir table to save viewing direction

---
 navipy/database/__init__.py | 64 ++++++++++++++++++++++------
 navipy/sensors/renderer.py  | 85 +++++++++++++++++++++++--------------
 2 files changed, 105 insertions(+), 44 deletions(-)

diff --git a/navipy/database/__init__.py b/navipy/database/__init__.py
index 0083324..73d842d 100644
--- a/navipy/database/__init__.py
+++ b/navipy/database/__init__.py
@@ -12,6 +12,7 @@ import navipy.maths.constants as mconst
 from navipy.trajectories import Trajectory
 from navipy.scene import __spherical_indeces__
 import logging
+import numbers
 
 
 def adapt_array(arr):
@@ -121,7 +122,6 @@ class DataBase():
             filename, channels))
         self.filename = filename
         self.channels = channels
-        self.viewing_directions = None
         self.normalisation_columns = list()
         for chan_n in self.channels:
             self.normalisation_columns.append(str(chan_n) + '_max')
@@ -139,11 +139,10 @@ class DataBase():
         self.tablecolumns['position_orientation']['q_3'] = 'real'
         self.tablecolumns['position_orientation']['frame_i'] = 'real'
         self.tablecolumns['position_orientation']['rotconv_id'] = 'string'
+        self.tablecolumns['viewing_directions'] = dict()
+        self.tablecolumns['viewing_directions']['data'] = 'real'
         self.tablecolumns['image'] = dict()
         self.tablecolumns['image']['data'] = 'array'
-        # self.tablecolumns['viewing_directions'] = dict()
-        # self.tablecolumns['viewing_directions']['elevation'] = 'array'
-        # self.tablecolumns['viewing_directions']['azimuth'] = 'array'
         self.tablecolumns['normalisation'] = dict()
         for col in self.normalisation_columns:
             self.tablecolumns['normalisation'][col] = 'real'
@@ -181,13 +180,52 @@ class DataBase():
                 self.db_cursor.execute(
                     "create table {} {}".format(key, columns))
             self.db.commit()
+        self.__nbaz = None
+        self.__nbel = None
+        self.__viewing_dir = None
 
-        azimuth = np.deg2rad(np.linspace(-180, 180, 360))
-        elevation = np.deg2rad(np.linspace(-90, 90, 180))
-        [ma, me] = np.meshgrid(azimuth, elevation)
-        self.viewing_directions = np.zeros((ma.shape[0], ma.shape[1], 2))
-        self.viewing_directions[..., __spherical_indeces__['elevation']] = me
-        self.viewing_directions[..., __spherical_indeces__['azimuth']] = ma
+    @property
+    def viewing_directions(self):
+        if self.__viewing_dir is None:
+            rowid = 1
+            tablename = 'viewing_directions'
+            self.db_cursor.execute(
+                """
+                SELECT data
+                FROM {}
+                WHERE (rowid=?)
+                """.format(tablename), (rowid,))
+            self.__viewing_dir = self.db_cursor.fetchone()[0]
+        return self.__viewing_dir.copy()
+
+    @viewing_directions.setter
+    def viewing_directions(self, viewdir):
+        """Get the viewing direction from images
+
+        :param az_lim: (min,max) of the azimuth angles
+        :param el_lim: (min,max) of the elevation angles
+        :returns: viewing direction of every pixels
+        :rtype: np.array
+
+        """
+        if self.__viewing_dir is None:
+            msg = 'write viewing direction'
+            self._logger.info(msg)
+            if isinstance(viewdir, np.ndarray):
+                tablename = 'viewing_directions'
+                params = dict()
+                params['rowid'] = 1
+                params['data'] = viewdir
+                self.insert_replace(tablename, params)
+            else:
+                msg = 'viewdir should be a numpy nd.array'
+                msg += ' and not {}'.format(type(viewdir))
+                self._logger.exception(msg)
+                raise TypeError(msg)
+        else:
+            msg = 'viewing direction has already been set'
+            self._logger.exception(msg)
+            raise Exception(msg)
 
     def table_exist(self, tablename):
         """
@@ -293,8 +331,10 @@ class DataBase():
             raise Exception(msg)
         found_convention = False
         index = posorient.index
-        if isinstance(posorient.name, int):
-            msg = 'posorient.name should give the frame #'
+        if not isinstance(posorient.name, numbers.Number):
+            msg = 'posorient.name should give the frame #\n'
+            msg += ' posorient.name: {}\n'.format(posorient.name)
+            msg += ' type(posorient.name): {}'.format(type(posorient.name))
             self._logger.exception(msg)
             raise Exception(msg)
         frame_i = posorient.name
diff --git a/navipy/sensors/renderer.py b/navipy/sensors/renderer.py
index befcae6..59fe438 100644
--- a/navipy/sensors/renderer.py
+++ b/navipy/sensors/renderer.py
@@ -16,6 +16,7 @@ import yaml  # Used to load config files
 import pkg_resources
 from navipy.maths.homogeneous_transformations import compose_matrix
 from navipy.maths.quaternion import matrix as quatmatrix
+from navipy.scene import spherical_indeces
 import navipy.maths.constants as constants
 from navipy.trajectories import Trajectory
 from PIL import Image
@@ -48,6 +49,11 @@ class AbstractRender():
         """ worldlimit"""
         self.__worldlimit = worldlimit
 
+    @property
+    def viewing_directions(self):
+        """ Need to be implemented by children classes """
+        return None
+
     def render_trajectory(self, outputfile,
                           trajectory, imformat='.jpg'):
         """
@@ -84,6 +90,8 @@ class AbstractRender():
                                  mode='a',
                                  channels=['R', 'G', 'B', 'D'],
                                  arr_dtype=np.uint8)
+            self._logger.debug('database created')
+            dataloger.viewing_directions = self.viewing_directions
         # We now can render
         self._logger.info('Start rendering')
         for frame_i, posorient in trajectory.iterrows():
@@ -96,6 +104,7 @@ class AbstractRender():
             # Avoid rerendering by checking if data already
             # exist
             if mode == 'database':
+                self._logger.warning(posorient)
                 rowid = dataloger.get_posid(posorient)
                 if dataloger.check_data_validity(rowid):
                     msg = 'frame_i: {} data is valid rowid {}'
@@ -356,8 +365,8 @@ class BlenderRender(AbstractRender):
     def cycle_samples(self):
         """get the samples for rendering with cycle
 
-        :returns: the number of samples used for the rendering
-        :rtype: int
+        : returns: the number of samples used for the rendering
+        : rtype: int
 
         """
         return bpy.context.scene.cycles.samples
@@ -366,8 +375,8 @@ class BlenderRender(AbstractRender):
     def 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
+        : param samples: the number of samples to use when rendering images
+        : type samples: int
 
         """
         if not isinstance(samples, int):
@@ -378,11 +387,11 @@ class BlenderRender(AbstractRender):
     def camera_fov(self):
         """get fov of camera
 
-        :returns: the field of view of the camera as \
+        : returns: the field of view of the camera as \
         [[minimum latitude, maximum latitude],
                             [minimum longitude, maximum longitude]]
                             (in deg)
-        :rtype: np.array
+        : rtype: np.array
 
         ..todo Change assert to if -> raise TypeError/KeyError
 
@@ -402,10 +411,10 @@ class BlenderRender(AbstractRender):
     def camera_fov(self, resolution):
         """change the field of view of the panoramic camera
 
-        :param resolution: [[minimum latitude, maximum latitude],
+        : param resolution: [[minimum latitude, maximum latitude],
                             [minimum longitude, maximum longitude]]
                             (in deg)
-        :type latmin: 2x2 float array or list
+        : type latmin: 2x2 float array or list
         """
         if not (isinstance(resolution, tuple) or
                 isinstance(resolution, list) or
@@ -428,8 +437,8 @@ class BlenderRender(AbstractRender):
     def camera_gaussian_width(self):
         """get width of the gaussian spatial filter
 
-        :returns: the width of the gaussian filter
-        :rtype: float
+        : returns: the width of the gaussian filter
+        : rtype: float
 
         """
         return bpy.context.scene.cycles.filter_width
@@ -438,9 +447,8 @@ class BlenderRender(AbstractRender):
     def camera_gaussian_width(self, gauss_w):
         """change width of the gaussian spatial filter
 
-        :param gauss_w: width of the gaussian filter
-        :type gauss_w: float
-
+        : param gauss_w: width of the gaussian filter
+        : type gauss_w: float
 
         """
         if not (isinstance(gauss_w, int) or isinstance(gauss_w, float)):
@@ -449,10 +457,10 @@ class BlenderRender(AbstractRender):
 
     @property
     def camera_resolution(self):
-        """return camera resolution (x,y)
+        """return camera resolution(x, y)
 
-        :returns: the resolution of the camera along (x-axis,y-axis)
-        :rtype: (int,int)
+        : returns: the resolution of the camera along(x-axis, y-axis)
+        : rtype: (int, int)
 
         """
         resolution_x = bpy.context.scene.render.resolution_x
@@ -461,12 +469,12 @@ class BlenderRender(AbstractRender):
 
     @camera_resolution.setter
     def camera_resolution(self, resolution):
-        """change the camera resolution (nb of pixels)
+        """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
+        : 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
         """
         if not (isinstance(resolution, list) or
                 isinstance(resolution, np.ndarray)):
@@ -475,12 +483,25 @@ class BlenderRender(AbstractRender):
         bpy.context.scene.render.resolution_y = resolution[1]
         bpy.context.scene.render.resolution_percentage = 100
 
+    @property
+    def viewing_directions(self):
+        self._logger.info('get viewdir')
+        rx, ry = self.camera_resolution
+        fov = self.camera_fov
+        az = np.linspace(fov[1, 0], fov[1, 1], rx)
+        el = np.linspace(fov[0, 0], fov[0, 1], ry)
+        [ma, me] = np.meshgrid(az, el)
+        view_dir = np.zeros((ma.shape[0], ma.shape[1], 2))
+        view_dir[..., spherical_indeces()['elevation']] = me
+        view_dir[..., spherical_indeces()['azimuth']] = ma
+        return view_dir
+
     @property
     def image(self):
         """return the last rendered image as a numpy array
 
-        :returns: the image (height,width,nchannel)
-        :rtype: a double numpy array
+        : returns: the image(height, width, nchannel)
+        : rtype: a double numpy array
 
         .. note: A temporary file will be written on the harddrive,
                  due to API blender limitation
@@ -509,8 +530,8 @@ class BlenderRender(AbstractRender):
     def distance(self):
         """return the last rendered distance map as a numpy array
 
-        :returns: the distance map (height, width)
-        :rtype: a double 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
@@ -543,7 +564,7 @@ class BlenderRender(AbstractRender):
     def update(self, posorient):
         """assign the position and the orientation of the camera.
 
-        :param posorient: is a 1x6 vector containing:
+        : param posorient: is a 1x6 vector containing:
              *in case of euler angeles the index should be
               ['location']['x']
               ['location']['y']
@@ -565,7 +586,7 @@ class BlenderRender(AbstractRender):
                quaternion
              here the angles are euler rotation around the axis
              specified by scene.camera.rotation_mode
-        :type posorient: pandas Series with multi-index
+        : type posorient: pandas Series with multi-index
         """
         self._logger.info('update posorient to {}'.format(posorient))
         if isinstance(posorient, pd.Series):
@@ -613,14 +634,14 @@ class BlenderRender(AbstractRender):
     def scene(self, posorient):
         """ update position orientation and return a RGBD image
 
-        :param posorient: is a 1x6 vector containing:
-             x,y,z, angle_1, angle_2, angle_3,
+        : param posorient: is a 1x6 vector containing:
+             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
-        :returns: a (height,width, channel) array here the last channel \
+        : type posorient: 1x6 double array
+        : returns: a(height, width, channel) array here the last channel \
 is the distance.
-        :rtype: a double numpy array
+        : rtype: a double numpy array
         """
         self._logger.info('get a scene')
         self.update(posorient)
-- 
GitLab