diff --git a/navipy/database/__init__.py b/navipy/database/__init__.py index 185d8016b3a874e455f182d00f8f7ff9300414b9..e94a6d7cc684f0ace9aa548748b8250a22fd5a1f 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 372faea96de60441c9a94152817af4c9bd1b67f4..359d1dd151694a41a525a7a23960de72f2fccf00 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 c4b3b492a9fce982ad2fb373fe1bd10186e4b212..c6ce93aeea1fe892f436f05cba69cc5949e4876a 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 36f9ab4627815a608bccccba0ce53bf3997d6e83..10b3935443de22143e3930138f88a15b743c6899 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 05f383ab09d687860ce11a4f479de9380a8c756a..7c049d9a57f58ea638b094766e18defc2cbdcf4c 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 46e6e66836def72080e9f1f1187feb8ea4dc4001..0b97bb53667f7a588773b11b74547d7526971daa 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',