From 71cf89a033b5d7065b1c5568d03667d4ac7cba2b Mon Sep 17 00:00:00 2001
From: "Olivier J.N. Bertrand" <olivier.bertrand@uni-bielefeld.de>
Date: Fri, 1 Jun 2018 08:59:54 +0200
Subject: [PATCH] Write Render Abstract class So that grid and trajectory
 rendering can be performed seemlessly The test fucntion does not run yet

---
 navipy/database/__init__.py          |   4 +-
 navipy/database/test.py              |   6 +-
 navipy/sensors/blendtest_renderer.py |  36 ++++++-
 navipy/sensors/renderer.py           | 148 +++++++++++++++++++++------
 navipy/tools/trajectory.py           |  16 +--
 setup.py                             |   6 +-
 6 files changed, 169 insertions(+), 47 deletions(-)

diff --git a/navipy/database/__init__.py b/navipy/database/__init__.py
index 185d801..e94a6d7 100644
--- a/navipy/database/__init__.py
+++ b/navipy/database/__init__.py
@@ -543,7 +543,7 @@ class DataBaseLoad(DataBase):
                 if 'q_3' not in posorient.index.get_level_values(1):
                     raise ValueError('missing index alpha_2')
             if np.any(pd.isnull(posorient)):
-                    raise ValueError('posorient must not contain nan')
+                raise ValueError('posorient must not contain nan')
         if rowid is not None:
             if not isinstance(rowid, int):
                 raise TypeError('rowid must be an integer')
@@ -696,7 +696,7 @@ class DataBaseLoad(DataBase):
             if posorient is not None:
                 rowid = self.get_posid(posorient)
         if (posorient is None) and (rowid is None):
-            Exception('posorient and rowid can not be both None')
+            raise Exception('posorient and rowid can not be both None')
         if posorient is not None:
             rowid = self.get_posid(posorient)
         # Read images
diff --git a/navipy/database/test.py b/navipy/database/test.py
index 372faea..359d1dd 100644
--- a/navipy/database/test.py
+++ b/navipy/database/test.py
@@ -13,7 +13,7 @@ class TestCase(unittest.TestCase):
     def setUp(self):
         self.mydb_filename = pkg_resources.resource_filename(
             'navipy', 'resources/database2.db')
-        self.mydb = database.DataBaseLoad(self.mydb_filename)
+        self.mydb = DataBaseLoad(self.mydb_filename)
 
     def test_DataBase_init_(self):
         """
@@ -552,7 +552,7 @@ class TestCase(unittest.TestCase):
         cminmaxrange.D_min = 0
         cminmaxrange.D_max = 1
         cminmaxrange.D_range = 1
-        imagecorrect = (image.copy()*500).astype(int)
+        imagecorrect = (image.copy() * 500).astype(int)
         self.mydb.denormalise_image(imagecorrect, cminmaxrange)
 
         # not working
@@ -590,7 +590,7 @@ class TestCase(unittest.TestCase):
         cminmaxrange2.D_min = 0
         cminmaxrange2.D_max = 1
         cminmaxrange2.D_range = 1
-        imagecorrect = (image.copy()*500).astype(int)
+        imagecorrect = (image.copy() * 500).astype(int)
         with self.assertRaises(ValueError):
             self.mydb.denormalise_image(imagecorrect, cminmaxrange2)
 
diff --git a/navipy/sensors/blendtest_renderer.py b/navipy/sensors/blendtest_renderer.py
index c4b3b49..c6ce93a 100644
--- a/navipy/sensors/blendtest_renderer.py
+++ b/navipy/sensors/blendtest_renderer.py
@@ -4,6 +4,9 @@ from navipy.maths.quaternion import from_matrix as quat_matrix
 import pandas as pd
 import numpy as np
 import unittest
+import pkg_resources
+import tempfile
+from navipy.database import DataBaseLoad
 
 
 class TestBlenderRender_renderer(unittest.TestCase):
@@ -20,9 +23,9 @@ class TestBlenderRender_renderer(unittest.TestCase):
         self.posorient.loc['location']['x'] = 0
         self.posorient.loc['location']['y'] = 0
         self.posorient.loc['location']['z'] = 1
-        self.posorient.loc[convention]['alpha_0'] = np.pi/4
-        self.posorient.loc[convention]['alpha_1'] = np.pi/7
-        self.posorient.loc[convention]['alpha_2'] = np.pi/3
+        self.posorient.loc[convention]['alpha_0'] = np.pi / 4
+        self.posorient.loc[convention]['alpha_1'] = np.pi / 7
+        self.posorient.loc[convention]['alpha_2'] = np.pi / 3
 
         convention = self.posorient.index.get_level_values(0)[-1]
         a, b, c = self.posorient.loc[convention]
@@ -67,3 +70,30 @@ class TestBlenderRender_renderer(unittest.TestCase):
         posorient2.loc[convention] = [at, bt, ct, dt]
         image2 = self.renderer.scene(posorient2)
         np.testing.assert_allclose(image2, self.image_ref, atol=1.2)
+
+    def test_gridrender(self):
+        """
+        Test the rendering on 5x5 grid
+        """
+        x = np.linspace(-0.5, 0.5, 5)
+        y = np.linspace(-0.5, 0.5, 5)
+        z = [0]
+        rotconv = 'rxyz'
+        db_reffilename = pkg_resources.resource_filename(
+            'navipy', 'resources/database.db')
+        db_ref = DataBaseLoad(db_reffilename)
+        with tempfile.NamedTemporaryFile() as tfile:
+            outputfile = tfile.name
+            self.renderer.render_ongrid(outputfile,
+                                        x, y, z,
+                                        rotconv=rotconv)
+            db = DataBaseLoad(outputfile)
+            posorients = db_ref.posorients
+            for row_i, posorient in posorients.iterrows():
+                refscene = db_ref.scene(posorient)
+                try:
+                    scene = db.scene(posorient)
+                except ValueError:
+                    msg = 'Scene has not been found'
+                    self.assertEqual(False, True, msg)
+                np.testing.assert_allclose(scene, refscene)
diff --git a/navipy/sensors/renderer.py b/navipy/sensors/renderer.py
index 36f9ab4..10b3935 100644
--- a/navipy/sensors/renderer.py
+++ b/navipy/sensors/renderer.py
@@ -17,6 +17,9 @@ import pkg_resources
 from navipy.maths.homogeneous_transformations import compose_matrix
 import navipy.maths.constants as constants
 from navipy.tools.trajectory import Trajectory
+from PIL import Image
+from navipy.scene import check_scene
+from navipy.database import DataBaseSave
 
 
 class AbstractRender():
@@ -25,14 +28,99 @@ class AbstractRender():
     """
 
     def __init__(self):
-        pass
+        self.__worldlimit = np.inf
 
-    def render_trajectory(trajectory, outputfile, imformat='.jpg'):
-        pass
+    @property
+    def worldlimit(self):
+        """ worldlimit is the max distance return in scene
+
+        :getter: return a worldlimit
+        :setter: set a worldlimit
+        :type: list
+        """
+        return self.__worldlimit
+
+    @worldlimit.setter
+    def worldlimit(self, worldlimit):
+        """ worldlimit"""
+        self.__worldlimit = worldlimit
 
-    def render_ongrid(x, y, z, alpha_0=[0], alpha_1=[0], alpha_2=[0],
+    def render_trajectory(self, outputfile,
+                          trajectory, imformat='.jpg'):
+        """
+        Render on a trajectory
+        """
+        # Check the user input
+        if not isinstance(outputfile, str):
+            raise TypeError(
+                'OutputFile should be a path {}'.format(outputfile))
+        if not isinstance(trajectory, Trajectory):
+            raise TypeError('trajectory should be navipy Trajectory')
+        # Create the directory name if it does
+        # not exist
+        dirname = os.path.dirname(outputfile)
+        if not os.path.isdir(dirname):
+            os.makedirs(dirname)
+        # Check the file type
+        if os.path.isdir(outputfile):
+            mode = 'array'
+            nbdigit = np.floor(np.log10(trajectory.shape[0])) + 1
+            fileformat = os.path.join(
+                outputfile,
+                'frame_{:0' + str(nbdigit) + 'd}' + imformat)
+        else:
+            mode = 'database'
+            dataloger = DataBaseSave(outputfile,
+                                     channels=['R', 'G', 'B', 'D'],
+                                     arr_dtype=np.uint8)
+        # We now can render
+        for frame_i, posorient in trajectory.iterrows():
+            # Check if posorient is a valid one
+            # otherwise skip to next position
+            if np.any(np.isnan(posorient)):
+                # Skip because we can not render when night
+                continue
+            # Avoid rerendering by checking if data already
+            # exist
+            if mode == 'database':
+                rowid = dataloger.get_posid(posorient)
+                if dataloger.check_data_validity(rowid):
+                    msg = 'frame_i: {} data is valid rowid {}'
+                    msg = msg.format(frame_i, rowid)
+                    warnings.warn(msg)
+                    continue
+                elif mode == 'array':
+                    filename = fileformat.format(frame_i)
+                    if os.path.exists(filename):
+                        msg = 'frame_i: {} already exist'
+                        msg = msg.format(frame_i)
+                        warnings.warn(msg)
+                        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.scene(posorient)
+            check_scene(scene)
+            scene = scene[..., 0]
+            distance = scene[..., -1]
+            distance[distance > self.worldlimit] = self.worldlimit
+            scene[..., -1] = distance
+            if mode == 'database':
+                dataloger.write_image(posorient, scene)
+            elif mode == 'array':
+                if imformat == '.npy':
+                    np.save(filename, scene)
+                else:
+                    result = Image.fromarray(
+                        (scene * 255).astype(np.uint8))
+                    result.save(filename)
+
+    def render_ongrid(self, outputfile,
+                      x, y, z,
+                      alpha_0=[0], alpha_1=[0], alpha_2=[0],
                       q_0=None, q_1=None, q_2=None, q_3=None,
                       rotconv='rzyx'):
+        # Check inputs
         if rotconv == 'quaternion':
             if (q_0 is None) or \
                (q_1 is None) or \
@@ -47,7 +135,6 @@ class AbstractRender():
             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 rotconv == 'quaternion':
             if not (isinstance(q_0, np.ndarray) or
                     isinstance(q_0, list)):
@@ -61,16 +148,25 @@ class AbstractRender():
             if not (isinstance(q_3, np.ndarray) or
                     isinstance(q_3, list)):
                 raise TypeError('q_3 must be list or np.array')
-            [mx, my, mz, mq0, mq1, mq2, mq3] = np.meshgrid(x,
-                                                           y,
-                                                           z,
-                                                           q_0,
-                                                           q_1,
-                                                           q_2,
-                                                           q_3)
+        else:
+            if not (isinstance(alpha_0, np.ndarray) or
+                    isinstance(alpha_0, list)):
+                raise TypeError('alpha_0 must be list or np.array')
+            if not (isinstance(alpha_1, np.ndarray) or
+                    isinstance(alpha_1, list)):
+                raise TypeError('alpha_1 must be list or np.array')
+            if not (isinstance(alpha_2, np.ndarray) or
+                    isinstance(alpha_2, list)):
+                raise TypeError('alpha_2 must be list or np.array')
+        # We create data on a grid.
+        # To then render it as a trajectory
+        if rotconv == 'quaternion':
+            [mx, my, mz, mq0, mq1, mq2, mq3] = \
+                np.meshgrid(x, y, z, q_0, q_1, q_2, q_3)
 
             mx = mx.flatten()
-            grid_point = Trajectory(rotconv, indeces=range(0, mx.shape[0]))
+            grid_point = Trajectory(rotconv,
+                                    indeces=range(0, mx.shape[0]))
             grid_point.x = mx
             grid_point.y = my.flatten()
             grid_point.z = mz.flatten()
@@ -80,30 +176,20 @@ class AbstractRender():
             grid_point.q3 = mq3.flatten()
 
         else:
-            if not (isinstance(alpha_0, np.ndarray) or
-                    isinstance(alpha_0, list)):
-                raise TypeError('alpha_0 must be list or np.array')
-            if not (isinstance(alpha_1, np.ndarray) or
-                    isinstance(alpha_1, list)):
-                raise TypeError('alpha_1 must be list or np.array')
-            if not (isinstance(alpha_2, np.ndarray) or
-                    isinstance(alpha_2, list)):
-                raise TypeError('alpha_2 must be list or np.array')
-            [mx, my, mz, ma0, ma1, ma2] = np.meshgrid(x,
-                                                      y,
-                                                      z,
-                                                      alpha_0,
-                                                      alpha_1,
-                                                      alpha_2)
+            [mx, my, mz, ma0, ma1, ma2] = \
+                np.meshgrid(x, y, z, alpha_0, alpha_1, alpha_2)
 
             mx = mx.flatten()
-            grid_point = Trajectory(rotconv, indeces=range(0, mx.shape[0]))
+            grid_point = Trajectory(rotconv,
+                                    indeces=range(0, mx.shape[0]))
             grid_point.x = mx
             grid_point.y = my.flatten()
             grid_point.z = mz.flatten()
             grid_point.alpha_0 = ma0.flatten()
             grid_point.alpha_1 = ma1.flatten()
             grid_point.alpha_2 = ma2.flatten()
+        #
+        self.render_trajectory(outputfile, grid_point)
 
 
 class BlenderRender(AbstractRender):
@@ -121,6 +207,7 @@ class BlenderRender(AbstractRender):
         """Initialise the Cyberbee
         ..todo check that TemporaryDirectory is writtable and readable
         """
+        super(AbstractRender).__init__()
         # Rendering engine needs to be Cycles to support panoramic
         # equirectangular camera
         bpy.context.scene.render.engine = 'CYCLES'
@@ -237,6 +324,9 @@ class BlenderRender(AbstractRender):
         else:
             raise KeyError(
                 'Yaml config file should contain samples')
+        # Load worldlimit
+        if 'worldlimit' in blendconfig.keys():
+            self.worldlimit = blendconfig['worldlimit']
 
     @property
     def cycle_samples(self):
diff --git a/navipy/tools/trajectory.py b/navipy/tools/trajectory.py
index 05f383a..7c049d9 100644
--- a/navipy/tools/trajectory.py
+++ b/navipy/tools/trajectory.py
@@ -54,15 +54,15 @@ class Trajectory(pd.DataFrame):
         self.loc[:, ('location', 'z')] = z
 
     def __get_alpha_i(self, alphai):
-        if self.__rotconf != 'quaternion':
-            return self.loc[:, (self.__rotconf, 'alpha_{}'.format(alphai))]
+        if self.__rotconv != 'quaternion':
+            return self.loc[:, (self.__rotconv, 'alpha_{}'.format(alphai))]
         else:
             msg = 'alpha_{0:} does not exist for quaternion (try q_{0:})'
             raise ValueError(msg.format(alphai))
 
     def __set_alpha_i(self, alphai, val):
-        if self.__rotconf != 'quaternion':
-            self.loc[:, (self.__rotconf, 'alpha_{}'.format(alphai))] = val
+        if self.__rotconv != 'quaternion':
+            self.loc[:, (self.__rotconv, 'alpha_{}'.format(alphai))] = val
         else:
             msg = 'alpha_{0:} does not exist for quaternion (try q_{0:})'
             raise ValueError(msg.format(alphai))
@@ -92,15 +92,15 @@ class Trajectory(pd.DataFrame):
         self.__set_alpha_i(2, alpha_2)
 
     def __get_q_i(self, qi):
-        if self.__rotconf == 'quaternion':
-            return self.loc[:, (self.__rotconf, 'q_{}'.format(qi))]
+        if self.__rotconv == 'quaternion':
+            return self.loc[:, (self.__rotconv, 'q_{}'.format(qi))]
         else:
             msg = 'q_{0:} does not exist for none quaternion (try alpha_{0:})'
             raise ValueError(msg.format(qi))
 
     def __set_q_i(self, qi, val):
-        if self.__rotconf != 'quaternion':
-            self.loc[:, (self.__rotconf, 'q_{}'.format(qi))] = val
+        if self.__rotconv != 'quaternion':
+            self.loc[:, (self.__rotconv, 'q_{}'.format(qi))] = val
         else:
             msg = 'q_{0:} does not exist for none quaternion (try alpha_{0:})'
             raise ValueError(msg.format(qi))
diff --git a/setup.py b/setup.py
index 46e6e66..0b97bb5 100644
--- a/setup.py
+++ b/setup.py
@@ -34,7 +34,8 @@ setup_dict = {'name': 'navipy',
                            'scipy',
                            'networkx',
                            'ipython',
-                           'yaml'],
+                           'yaml',
+                           'PIL'],
               'install_requires': ["numpy",
                                    'pandas',
                                    'matplotlib',
@@ -45,7 +46,8 @@ setup_dict = {'name': 'navipy',
                                    'ipython',
                                    'flake8',
                                    'tox',
-                                   'pyyaml'],
+                                   'pyyaml',
+                                   'Pillow'],
               'package_data': {'navipy':
                                ['resources/*.db',
                                 'resources/*.blend',
-- 
GitLab