diff --git a/navipy/database/__init__.py b/navipy/database/__init__.py index e94a6d7cc684f0ace9aa548748b8250a22fd5a1f..ea54ebb5e4cc98615997468b9466ba4e97c2cf2c 100644 --- a/navipy/database/__init__.py +++ b/navipy/database/__init__.py @@ -9,6 +9,8 @@ import sqlite3 import io import warnings from navipy.scene import is_numeric_array, check_scene +import navipy.maths.constants as mconst +from navipy.tools.trajectory import Trajectory def adapt_array(arr): @@ -98,7 +100,7 @@ class DataBase(): for col in self.normalisation_columns: self.tablecolumns['normalisation'][col] = 'real' - if os.path.exists(filename): + if (os.path.exists(filename)) and (self.create is False): # Check database self.db = sqlite3.connect( 'file:' + filename + '?cache=shared', uri=True, @@ -220,62 +222,58 @@ class DataBase(): """ if not isinstance(posorient, pd.Series): raise TypeError('posorient should be a pandas Series') - if posorient is not None: - if not isinstance(posorient, pd.Series): - raise TypeError('posorient should be a pandas Series') - if posorient.empty: - raise Exception('position must not be empty') - found_convention = False - index = posorient.index - convention = index.get_level_values(0)[-1] - if convention == 'rxyz': - found_convention = True - elif convention == 'ryzx': - found_convention = True - elif convention == 'rxzy': - found_convention = True - elif convention == 'ryxz': - found_convention = True - elif convention == 'rzxy': - found_convention = True - elif convention == 'rzyx': - found_convention = True - elif convention == 'quaternion': - found_convention = True - if not found_convention: - raise ValueError("your convention is not supported") - if convention != 'quaternion': - if 'x' not in posorient.index.get_level_values(1): - raise ValueError('missing index x') - if 'y' not in posorient.index.get_level_values(1): - raise ValueError('missing index y') - if 'z' not in posorient.index.get_level_values(1): - raise ValueError('missing index z') - if 'alpha_0' not in posorient.index.get_level_values(1): - raise ValueError('missing index alpha_0') - if 'alpha_1' not in posorient.index.get_level_values(1): - raise ValueError('missing index alpha_1') - if 'alpha_2' not in posorient.index.get_level_values(1): - raise ValueError('missing index alpha_2') - elif convention == 'quaternion': - if 'x' not in posorient.index.get_level_values(1): - raise ValueError('missing index x') - if 'y' not in posorient.index.get_level_values(1): - raise ValueError('missing index y') - if 'z' not in posorient.index.get_level_values(1): - raise ValueError('missing index z') - if 'q_0' not in posorient.index.get_level_values(1): - raise ValueError('missing index alpha_0') - if 'q_1' not in posorient.index.get_level_values(1): - raise ValueError('missing index alpha_1') - if 'q_2' not in posorient.index.get_level_values(1): - raise ValueError('missing index alpha_2') - 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') + if posorient.empty: + raise Exception('position must not be empty') + found_convention = False index = posorient.index - convention = index.get_level_values(0)[-1] + convention = index.levels[0][-1] + if (convention in mconst._AXES2TUPLE.keys()) or \ + convention == 'quaternion': + found_convention = True + if not found_convention: + msg = 'convention for rotation {} is not suppored\n' + msg += msg.format(convention) + msg += 'the following convention are supported\n:' + for rconv in mconst._AXES2TUPLE.keys(): + msg += '{}\n'.format(rconv) + msg += 'quaternion\n' + raise KeyError(msg) + index_2ndlevel = posorient.index.get_level_values(1) + # Check that the posorient contains valid columns + # The user may be using alpha_ or q_ + # and we therefore want to be able to handle both type + for val in ['x', 'y', 'z']: + if val not in index_2ndlevel: + raise ValueError('missing index {}'.format(val)) + naming_map = list() + for ii in range(3): + if ('alpha_{}'.format(ii) not in index_2ndlevel) and \ + ('q_{}'.format(ii) not in index_2ndlevel): + raise ValueError( + 'missing index alpha_{0:} or q_{0:}'.format(ii)) + elif ('alpha_{}'.format(ii) in index_2ndlevel) and \ + ('q_{}'.format(ii) in index_2ndlevel): + raise ValueError( + 'posorient should contains either alphas or qs') + elif ('alpha_{}'.format(ii) in index_2ndlevel): + naming_map.append('alpha_{}'.format(ii)) + else: + naming_map.append('q_{}'.format(ii)) + if convention == 'quaternion': + if 'q_3' not in index_2ndlevel: + raise ValueError('missing index q_3') + else: + naming_map.append('q_{}'.format(3)) + else: + # q_3 is unnecessary for convention + # different than quaternion. The value + # should be set to nan, and wil therefore block during check of + # any nan. We drop it now. + if 'q_3' in index_2ndlevel: + posorient.drop((convention, 'q_3'), inplace=True) + if np.any(pd.isnull(posorient)): + raise ValueError( + 'posorient must not contain nan\n {}'.format(posorient)) cursor = self.db_cursor.execute('select * from position_orientation') names = list(map(lambda x: x[0], cursor.description)) where = "" @@ -296,12 +294,12 @@ class DataBase(): posorient['location']['y'] + self.__float_tolerance, posorient['location']['z'] - self.__float_tolerance, posorient['location']['z'] + self.__float_tolerance, - posorient[convention]['alpha_0'] - self.__float_tolerance, - posorient[convention]['alpha_0'] + self.__float_tolerance, - posorient[convention]['alpha_1'] - self.__float_tolerance, - posorient[convention]['alpha_1'] + self.__float_tolerance, - posorient[convention]['alpha_2'] - self.__float_tolerance, - posorient[convention]['alpha_2'] + self.__float_tolerance) + posorient[convention][naming_map[0]] - self.__float_tolerance, + posorient[convention][naming_map[0]] + self.__float_tolerance, + posorient[convention][naming_map[1]] - self.__float_tolerance, + posorient[convention][naming_map[1]] + self.__float_tolerance, + posorient[convention][naming_map[2]] - self.__float_tolerance, + posorient[convention][naming_map[2]] + self.__float_tolerance) elif convention != 'quaternion': where += """x>=? and x<=?""" where += """and y>=? and y<=?""" @@ -317,12 +315,12 @@ class DataBase(): posorient['location']['y'] + self.__float_tolerance, posorient['location']['z'] - self.__float_tolerance, posorient['location']['z'] + self.__float_tolerance, - posorient[convention]['alpha_0'] - self.__float_tolerance, - posorient[convention]['alpha_0'] + self.__float_tolerance, - posorient[convention]['alpha_1'] - self.__float_tolerance, - posorient[convention]['alpha_1'] + self.__float_tolerance, - posorient[convention]['alpha_2'] - self.__float_tolerance, - posorient[convention]['alpha_2'] + self.__float_tolerance, + posorient[convention][naming_map[0]] - self.__float_tolerance, + posorient[convention][naming_map[0]] + self.__float_tolerance, + posorient[convention][naming_map[1]] - self.__float_tolerance, + posorient[convention][naming_map[1]] + self.__float_tolerance, + posorient[convention][naming_map[2]] - self.__float_tolerance, + posorient[convention][naming_map[2]] + self.__float_tolerance, convention) else: where += """x>=? and x<=?""" @@ -340,14 +338,14 @@ class DataBase(): posorient['location']['y'] + self.__float_tolerance, posorient['location']['z'] - self.__float_tolerance, posorient['location']['z'] + self.__float_tolerance, - posorient[convention]['q_0'] - self.__float_tolerance, - posorient[convention]['q_0'] + self.__float_tolerance, - posorient[convention]['q_1'] - self.__float_tolerance, - posorient[convention]['q_1'] + self.__float_tolerance, - posorient[convention]['q_2'] - self.__float_tolerance, - posorient[convention]['q_2'] + self.__float_tolerance, - posorient[convention]['q_3'] - self.__float_tolerance, - posorient[convention]['q_3'] + self.__float_tolerance, + posorient[convention][naming_map[0]] - self.__float_tolerance, + posorient[convention][naming_map[0]] + self.__float_tolerance, + posorient[convention][naming_map[1]] - self.__float_tolerance, + posorient[convention][naming_map[1]] + self.__float_tolerance, + posorient[convention][naming_map[2]] - self.__float_tolerance, + posorient[convention][naming_map[2]] + self.__float_tolerance, + posorient[convention][naming_map[3]] - self.__float_tolerance, + posorient[convention][naming_map[3]] + self.__float_tolerance, convention) self.db_cursor.execute( """ @@ -400,7 +398,8 @@ class DataBase(): self.db.commit() return rowid else: - raise ValueError('posorient not found') + raise ValueError('posorient not found \n {} \n {} \n {}'.format( + posorient, where, params)) @property def create(self): @@ -458,24 +457,21 @@ class DataBaseLoad(DataBase): posorient = pd.read_sql_query( "select * from position_orientation;", self.db) posorient.set_index('id', inplace=True) - if not isinstance(posorient.index, pd.core.index.MultiIndex): + if 'rotconv_id' in posorient.columns: + rotconv = posorient.loc[:, 'rotconv_id'] + if np.all(rotconv == rotconv.iloc[0]): + posorients = Trajectory( + rotconv.iloc[0], indeces=posorient.index) + posorients.from_dataframe(posorient) + else: + posorients = posorient + else: warnings.warn("you are loading a database with old\ - conventions, it will be transformed\ - automatically into the new one") - convention = 'rxyz' - tuples = [] - for n in posorient.columns: - if n in ['x', 'y', 'z']: - tuples.append(('location', n)) - else: - tuples.append((convention, n)) - index = pd.MultiIndex.from_tuples(tuples, - names=['position', - 'orientation']) - posorient.columns = index - return posorient - - return posorient + conventions, it will be transformed\ + automatically into the new one") + posorients = Trajectory(rotconv, indeces=posorient.index) + posorients.from_dataframe(posorient, rotconv='rxyz') + return posorients @property def normalisations(self): @@ -490,60 +486,6 @@ class DataBaseLoad(DataBase): return posorient def read_posorient(self, posorient=None, rowid=None): - if posorient is not None: - if not isinstance(posorient, pd.Series): - raise TypeError('posorient should be a pandas Series') - if posorient.empty: - raise Exception('position must not be empty') - found_convention = False - index = posorient.index - convention = index.get_level_values(0)[-1] - if convention == 'rxyz': - found_convention = True - elif convention == 'ryzx': - found_convention = True - elif convention == 'rxzy': - found_convention = True - elif convention == 'ryxz': - found_convention = True - elif convention == 'rzxy': - found_convention = True - elif convention == 'rzyx': - found_convention = True - elif convention == 'quaternion': - found_convention = True - if not found_convention: - raise ValueError("your convention is not supported") - if convention != 'quaternion': - if 'x' not in posorient.index.get_level_values(1): - raise ValueError('missing index x') - if 'y' not in posorient.index.get_level_values(1): - raise ValueError('missing index y') - if 'z' not in posorient.index.get_level_values(1): - raise ValueError('missing index z') - if 'alpha_0' not in posorient.index.get_level_values(1): - raise ValueError('missing index alpha_0') - if 'alpha_1' not in posorient.index.get_level_values(1): - raise ValueError('missing index alpha_1') - if 'alpha_2' not in posorient.index.get_level_values(1): - raise ValueError('missing index alpha_2') - elif convention == 'quaternion': - if 'x' not in posorient.index.get_level_values(1): - raise ValueError('missing index x') - if 'y' not in posorient.index.get_level_values(1): - raise ValueError('missing index y') - if 'z' not in posorient.index.get_level_values(1): - raise ValueError('missing index z') - if 'q_0' not in posorient.index.get_level_values(1): - raise ValueError('missing index alpha_0') - if 'q_1' not in posorient.index.get_level_values(1): - raise ValueError('missing index alpha_1') - if 'q_2' not in posorient.index.get_level_values(1): - raise ValueError('missing index alpha_2') - 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') if rowid is not None: if not isinstance(rowid, int): raise TypeError('rowid must be an integer') @@ -629,61 +571,6 @@ class DataBaseLoad(DataBase): :returns: an image :rtype: numpy.ndarray """ - - if posorient is not None: - if not isinstance(posorient, pd.Series): - raise TypeError('posorient should be a pandas Series') - if posorient.empty: - raise Exception('position must not be empty') - found_convention = False - index = posorient.index - convention = index.get_level_values(0)[-1] - if convention == 'rxyz': - found_convention = True - elif convention == 'ryzx': - found_convention = True - elif convention == 'rxzy': - found_convention = True - elif convention == 'ryxz': - found_convention = True - elif convention == 'rzxy': - found_convention = True - elif convention == 'rzyx': - found_convention = True - elif convention == 'quaternion': - found_convention = True - if not found_convention: - raise ValueError("your convention is not supported") - if convention != 'quaternion': - if 'x' not in posorient.index.get_level_values(1): - raise ValueError('missing index x') - if 'y' not in posorient.index.get_level_values(1): - raise ValueError('missing index y') - if 'z' not in posorient.index.get_level_values(1): - raise ValueError('missing index z') - if 'alpha_0' not in posorient.index.get_level_values(1): - raise ValueError('missing index alpha_0') - if 'alpha_1' not in posorient.index.get_level_values(1): - raise ValueError('missing index alpha_1') - if 'alpha_2' not in posorient.index.get_level_values(1): - raise ValueError('missing index alpha_2') - elif convention == 'quaternion': - if 'x' not in posorient.index.get_level_values(1): - raise ValueError('missing index x') - if 'y' not in posorient.index.get_level_values(1): - raise ValueError('missing index y') - if 'z' not in posorient.index.get_level_values(1): - raise ValueError('missing index z') - if 'q_0' not in posorient.index.get_level_values(1): - raise ValueError('missing index alpha_0') - if 'q_1' not in posorient.index.get_level_values(1): - raise ValueError('missing index alpha_1') - if 'q_2' not in posorient.index.get_level_values(1): - raise ValueError('missing index alpha_2') - 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') if rowid is not None: if not isinstance(rowid, int): raise TypeError('rowid must be an integer') diff --git a/navipy/resources/database.db b/navipy/resources/database.db index 450fe54728c12e42263541e1541b71d2e9e2a815..7fd82a0e02adad748ceec1126ae10d0f9744d128 100644 Binary files a/navipy/resources/database.db and b/navipy/resources/database.db differ diff --git a/navipy/sensors/blendtest_renderer.py b/navipy/sensors/blendtest_renderer.py index c6ce93aeea1fe892f436f05cba69cc5949e4876a..76c1ae56c9038ca0b3f3c9c441fe9062a42ec54c 100644 --- a/navipy/sensors/blendtest_renderer.py +++ b/navipy/sensors/blendtest_renderer.py @@ -77,23 +77,25 @@ class TestBlenderRender_renderer(unittest.TestCase): """ x = np.linspace(-0.5, 0.5, 5) y = np.linspace(-0.5, 0.5, 5) - z = [0] - rotconv = 'rxyz' + z = [3] + alpha_0 = [np.pi/2] + rotconv = 'rzyx' 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) + tfile = tempfile.NamedTemporaryFile() + outputfile = tfile.name + self.renderer.render_ongrid(outputfile, + x, y, z, alpha_0, + 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 {}'.format(db.posorients) + msg += '\n{}'.format(posorient) + self.assertEqual(False, True, msg) + np.testing.assert_allclose(scene, refscene) diff --git a/navipy/sensors/blendunittest.py b/navipy/sensors/blendunittest.py index 889dc2e5b8fba33d7f42c6107322ebe14692928b..4dabe5527dcaaef8744a1c0f530e8c4705f6063b 100644 --- a/navipy/sensors/blendunittest.py +++ b/navipy/sensors/blendunittest.py @@ -25,6 +25,11 @@ def parser_blendnavipy(): type=str, default='navipy', help=arghelp) + arghelp = 'Set pattern to look for in unittest (default: blendtest*.py)' + parser.add_argument('--pattern', + type=str, + default='blendtest*.py', + 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')" @@ -40,12 +45,13 @@ def parser_blendnavipy(): action='count', default=0, help=arghelp) + return parser -def run(start_dir): +def run(start_dir, pattern): suite = unittest.defaultTestLoader.discover(start_dir=start_dir, - pattern='blendtest*.py') + pattern=pattern) success = unittest.TextTestRunner().run(suite).wasSuccessful() print(success) if not success: @@ -69,7 +75,8 @@ def main(): tfile.write(line.encode(encoding)) tfile.write('\n\n'.encode(encoding)) tfile.write('try:\n'.encode(encoding)) - tfile.write(' run("{}")\n'.format(args.start_dir).encode(encoding)) + tfile.write(' run("{}","{}")\n'.format( + args.start_dir, args.pattern).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)) diff --git a/navipy/sensors/renderer.py b/navipy/sensors/renderer.py index 10b3935443de22143e3930138f88a15b743c6899..fef4b8891012f19e5aad37362b4fe9208e52dce8 100644 --- a/navipy/sensors/renderer.py +++ b/navipy/sensors/renderer.py @@ -207,7 +207,7 @@ class BlenderRender(AbstractRender): """Initialise the Cyberbee ..todo check that TemporaryDirectory is writtable and readable """ - super(AbstractRender).__init__() + super(BlenderRender, self).__init__() # Rendering engine needs to be Cycles to support panoramic # equirectangular camera bpy.context.scene.render.engine = 'CYCLES' diff --git a/navipy/tools/trajectory.py b/navipy/tools/trajectory.py index 7c049d9a57f58ea638b094766e18defc2cbdcf4c..cc2c890768bbab3b4cf9bbb1ea7937a2fc413ab8 100644 --- a/navipy/tools/trajectory.py +++ b/navipy/tools/trajectory.py @@ -2,11 +2,17 @@ Trajectory in navipy """ import pandas as pd +import numpy as np import navipy.maths.constants as mconst class Trajectory(pd.DataFrame): def __init__(self, rotconv, indeces): + columns = self.__build_columns(rotconv) + super().__init__(index=indeces, columns=columns) + self.__rotconv = rotconv + + def __build_columns(self, rotconv): if rotconv == 'quaternion': index = pd.MultiIndex.from_tuples( [('location', 'x'), ('location', 'y'), @@ -26,8 +32,7 @@ class Trajectory(pd.DataFrame): msg += '{}\n'.format(rconv) msg += 'quaternion\n' raise KeyError(msg) - super().__init__(index=indeces, columns=index) - self.__rotconv = rotconv + return index @property def x(self): @@ -137,5 +142,53 @@ class Trajectory(pd.DataFrame): def q_3(self, q_3): self.__set_q_i(3, q_3) + def from_dataframe(self, df, rotconv=None): + """ Assign trajectory from a dataframe + """ + if 'rotconv_id' in df.columns: + rotconv = df.loc[:, 'rotconv_id'] + if not np.all(rotconv == rotconv.iloc[0]): + raise ValueError('More than one rotconv detected') + rotconv = rotconv.iloc[0] # They are all the same :) + elif rotconv is None: + msg = 'When dataframe does not contains rotconv_id,' + msg += 'a convention should be given' + raise ValueError(msg) + + indeces = df.index + columns = self.__build_columns(rotconv) + super().__init__(index=indeces, columns=columns) + self.__rotconv = rotconv + # Position + self.x = df.x + self.y = df.y + self.z = df.z + # Orientation + if self.__rotconv == 'quaternion': + self.q_0 = df.q_0 + self.q_1 = df.q_1 + self.q_2 = df.q_2 + else: + if 'q_0' in df.columns: + self.alpha_0 = df.q_0 + elif 'alpha_0' in df.columns: + self.alpha_0 = df.alpha_0 + else: + raise KeyError('df should contains q_0 or alpha_0') + + if 'q_1' in df.columns: + self.alpha_1 = df.q_1 + elif 'alpha_1' in df.columns: + self.alpha_1 = df.alpha_1 + else: + raise KeyError('df should contains q_1 or alpha_1') + + if 'q_2' in df.columns: + self.alpha_2 = df.q_2 + elif 'alpha_2' in df.columns: + self.alpha_2 = df.alpha_2 + else: + raise KeyError('df should contains q_2 or alpha_2') + def lollipops(self): raise NameError('Not implemented')