diff --git a/navipy/database/__init__.py~ b/navipy/database/__init__.py~ new file mode 100644 index 0000000000000000000000000000000000000000..5828dddd76cb670ab3418d7be26f89ef894379b9 --- /dev/null +++ b/navipy/database/__init__.py~ @@ -0,0 +1,492 @@ +""" +Database are generated by the rendering module, and contains all \ +images and there corresponding position-orientations. + +* position_orientation: containing all position and orientation of where \ +images were rendered. The position-orientation is described by \ +['x','y','z','alpha_0','alpha_1','alpha_2'] +* image: containing all images ever rendered. Each channel of each image \ +are normalised, so to use the full coding range. +* normalisation: the normalisation constantes + + +How to load a database +---------------------- + +.. code-block:: python + + from database import DataBaseLoad + mydb_filename = 'database.db' + mydb = DataBaseLoad(mydb_filename) + +How to load all position-orientation +------------------------------------ + +The database contains all position-orientation \ +at which an image as been rendered. In certain \ +situation, it may be usefull to know all \ +position-orientation in the database. More technically \ +speaking, loading the full table of position-orientaiton. + +.. code-block:: python + + posorients = mydb.get_posorients() + posorients.head() + +How to load an image +-------------------- + +The database contains images which can be processed differently \ +depending on the navigation strategy beeing used. + +Images are at given position-orientations. To load an image \ +the position-orientation can be given. The DataBaseLoader will \ +look if this position-orientation has been rendered. If it is \ +the case, the image will be returned. + +.. code-block:: python + + posorient = pd.Series(index=['x', 'y', 'z', + 'alpha_0', 'alpha_1', 'alpha_2']) + posorient.x = -0.6 + posorient.y = -7.2 + posorient.z = 2.35 + posorient.alpha_0 = np.pi / 2 + posorient.alpha_1 = 0 + posorient.alpha_2 = 0 + image = mydb.read_image(posorient=posorient) + +.. plot:: example/database/load_image_posorient.py + +However, looking in the database if an image has already been \ +rendered at a given position-orientation can cost time. To speed up \ +certain calculation, image can instead be access by row number. \ +Indeed each position-orientation can be identified by a unique row \ +number. This number is consistant through the entire database. Thus, \ +an image can be loaded by providing the row number. + +.. code-block:: python + + rowid = 1000 + image = mydb.read_image(rowid=rowid) + +.. plot:: example/database/load_image_rowid.py + + + +.. todo: channels as part of database + +""" + +import os +import numpy as np +import pandas as pd +import sqlite3 +import io +import warnings + + +def adapt_array(arr): + """ + http://stackoverflow.com/a/31312102/190597 (SoulNibbler) + """ + out = io.BytesIO() + np.save(out, arr) + out.seek(0) + return sqlite3.Binary(out.read()) + + +def convert_array(text): + out = io.BytesIO(text) + out.seek(0) + return np.load(out) + + +# Converts np.array to TEXT when inserting +sqlite3.register_adapter(np.ndarray, adapt_array) +# Converts TEXT to np.array when selecting +sqlite3.register_converter("array", convert_array) + + +class DataBase(): + """DataBase is the parent class of DataBaseLoad and DataBaseSave. +It creates three sql table on initialisation. + """ + __float_tolerance = 1e-14 + + def __init__(self, filename, channels=['R', 'G', 'B', 'D']): + """Initialisation of the database """ + if not isinstance(filename, str): + raise TypeError('filename should be a string') + if not isinstance(channels, list): + raise TypeError('nb_channel should be an integer') + self.filename = filename + self.channels = channels + self.normalisation_columns = list() + for chan_n in self.channels: + self.normalisation_columns.append(str(chan_n) + '_max') + self.normalisation_columns.append(str(chan_n) + '_min') + self.normalisation_columns.append(str(chan_n) + '_range') + + self.tablecolumns = dict() + self.tablecolumns['position_orientation'] = dict() + self.tablecolumns['position_orientation']['x'] = 'real' + self.tablecolumns['position_orientation']['y'] = 'real' + self.tablecolumns['position_orientation']['z'] = 'real' + self.tablecolumns['position_orientation']['alpha_0'] = 'real' + self.tablecolumns['position_orientation']['alpha_1'] = 'real' + self.tablecolumns['position_orientation']['alpha_2'] = 'real' + self.tablecolumns['image'] = dict() + self.tablecolumns['image']['data'] = 'array' + self.tablecolumns['normalisation'] = dict() + for col in self.normalisation_columns: + self.tablecolumns['normalisation'][col] = 'real' + + if os.path.exists(filename): + # Check database + self.db = sqlite3.connect( + 'file:' + filename + '?cache=shared', uri=True, + detect_types=sqlite3.PARSE_DECLTYPES, + timeout=10) + self.db_cursor = self.db.cursor() + for tablename, _ in self.tablecolumns.items(): + assert self.table_exist(tablename),\ + '{} does not contain a table named {}'.format( + filename, tablename) + elif self.create: + # Create database + self.db = sqlite3.connect( + filename, detect_types=sqlite3.PARSE_DECLTYPES) + self.db_cursor = self.db.cursor() + for key, val in self.tablecolumns.items(): + columns = "(id integer primary key autoincrement" + for colname, coltype in val.items(): + columns += ' , ' + colname + ' ' + coltype + columns += ')' + self.db_cursor.execute( + "create table {} {}".format(key, columns)) + self.db.commit() + else: + raise NameError('Database {} does not exist'.format(filename)) + + azimuth = np.linspace(-180, 180, 360) + elevation = 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[..., 0] = me + self.viewing_directions[..., 1] = ma + + def table_exist(self, tablename): + assert isinstance(tablename, str), 'tablename should be a string' + self.db_cursor.execute( + """ + SELECT count(*) + FROM sqlite_master + WHERE type='table' and name=?; + """, (tablename,)) + return bool(self.db_cursor.fetchone()) + + def check_data_validity(self, rowid): + self.db_cursor.execute( + """ + SELECT count(*) + FROM position_orientation + WHERE rowid=?; + """, (rowid,)) + valid = bool(self.db_cursor.fetchone()[0]) + self.db_cursor.execute( + """ + SELECT count(*) + FROM normalisation + WHERE rowid=?; + """, (rowid,)) + valid = valid and bool(self.db_cursor.fetchone()[0]) + self.db_cursor.execute( + """ + SELECT count(*) + FROM image + WHERE rowid=?; + """, (rowid,)) + valid = valid and bool(self.db_cursor.fetchone()[0]) + return valid + + def get_posid(self, posorient): + assert isinstance(posorient, pd.Series),\ + 'posorient should be a pandas Series' + where = """x>=? and x<=?""" + where += """and y>=? and y<=?""" + where += """and z>=? and z<=?""" + where += """and alpha_0>=? and alpha_0<=?""" + where += """and alpha_1>=? and alpha_1<=?""" + where += """and alpha_2>=? and alpha_2<=?""" + params = ( + posorient['x'] - self.__float_tolerance, + posorient['x'] + self.__float_tolerance, + posorient['y'] - self.__float_tolerance, + posorient['y'] + self.__float_tolerance, + posorient['z'] - self.__float_tolerance, + posorient['z'] + self.__float_tolerance, + posorient['alpha_0'] - self.__float_tolerance, + posorient['alpha_0'] + self.__float_tolerance, + posorient['alpha_1'] - self.__float_tolerance, + posorient['alpha_1'] + self.__float_tolerance, + posorient['alpha_2'] - self.__float_tolerance, + posorient['alpha_2'] + self.__float_tolerance) + self.db_cursor.execute( + """ + SELECT count(*) + FROM position_orientation + WHERE {};""".format(where), params) + exist = self.db_cursor.fetchone()[0] # [0] because of tupple + if bool(exist): + self.db_cursor.execute( + """ + SELECT rowid + FROM position_orientation + WHERE {}; + """.format(where), params) + return self.db_cursor.fetchone()[0] + elif self.create: + self.db_cursor.execute( + """ + INSERT + INTO position_orientation(x,y,z,alpha_0,alpha_1,alpha_2) + VALUES (?,?,?,?,?,?) + """, ( + posorient['x'], + posorient['y'], + posorient['z'], + posorient['alpha_0'], + posorient['alpha_1'], + posorient['alpha_2'])) + rowid = self.db_cursor.lastrowid + self.db.commit() + return rowid + else: + print(posorient) + raise ValueError('posorient not found') + + @property + def create(self): + return False + + +class DataBaseLoad(DataBase): + """A database generated by the rendering module is based on sqlite3. + """ + + def __init__(self, filename, channels=['R', 'G', 'B', 'D']): + """Initialise the DataBaseLoader""" + DataBase.__init__(self, filename, channels=channels) + + @property + def create(self): + """use to decide weather to alter the database or not + return False because we do not want + to write on database (Load class)""" + return False + + def iter_posorients(self): + """Iter through all position orientation in the database + """ + self.db_cursor.execute( + """ + SELECT * + FROM position_orientation + """) + + columns_names = [] + for col in self.db_cursor.description: + columns_names.append(col[0]) + for row in self.db_cursor: + toyield = pd.Series(data=row, index=columns_names) + toyield.name = toyield.id + toyield.drop('id', inplace=True) + yield toyield + + @property + def posorients(self): + """Return the position orientations of all points in the \ +database + """ + posorient = pd.read_sql_query( + "select * from position_orientation;", self.db) + posorient.set_index('id', inplace=True) + return posorient + + def read_posorient(self, posorient=None, rowid=None): + assert (posorient is None) or (rowid is None),\ + 'posorient and rowid can not be both None' + if posorient is not None: + rowid = self.get_posid(posorient) + # Read images + tablename = 'position_orientation' + toreturn = pd.read_sql_query( + """ + SELECT * + FROM {} + WHERE (rowid={}) + """.format(tablename, rowid), self.db) + toreturn = toreturn.loc[0, :] + toreturn.name = toreturn.id + toreturn.drop('id') + toreturn = toreturn.astype(float) + return toreturn + + def read_image(self, posorient=None, rowid=None): + """Read an image at a given position-orientation or given id of row in the \ + database. + + :param posorient: a pandas Series with index \ + ['x','y','z','alpha_0','alpha_1','alpha_2'] + :param rowid: an integer + :returns: an image + :rtype: numpy.ndarray + """ + assert (posorient is None) or (rowid is None),\ + 'posorient and rowid can not be both None' + if posorient is not None: + rowid = self.get_posid(posorient) + # Read images + tablename = 'image' + self.db_cursor.execute( + """ + SELECT data + FROM {} + WHERE (rowid=?) + """.format(tablename), (rowid,)) + image = self.db_cursor.fetchone()[0] + # Read cmaxminrange + tablename = 'normalisation' + cmaxminrange = pd.read_sql_query( + """ + SELECT * + FROM {} + WHERE (rowid={}) + """.format(tablename, rowid), self.db) + assert cmaxminrange.shape[0] == 1,\ + 'Error while reading normalisation factors' + cmaxminrange = cmaxminrange.iloc[0, :] + cmaxminrange.name = cmaxminrange.id + cmaxminrange.drop('id') + cmaxminrange = cmaxminrange.astype(float) + return self.denormalise_image(image, cmaxminrange) + + def denormalise_image(self, image, cmaxminrange): + assert len(image.shape) == 3,\ + 'image should be 3D array' + assert image.shape[2] == len(self.channels),\ + 'image does not have the required number of channels {}'.format( + len(self.channels)) + assert isinstance(cmaxminrange, pd.Series),\ + 'cmaxminrange should be a pandas Series' + denormed_im = np.zeros(image.shape, dtype=np.float) + maxval_nim = np.iinfo(image.dtype).max + # + for chan_i, chan_n in enumerate(self.channels): + cimage = image[:, :, chan_i].astype(float) + cmax = cmaxminrange.loc[str(chan_n) + '_max'] + cmin = cmaxminrange.loc[str(chan_n) + '_min'] + crange = cmaxminrange.loc[str(chan_n) + '_range'] + cimage /= maxval_nim + cimage *= crange + cimage += cmin + denormed_im[:, :, chan_i] = cimage + if np.max(cimage) != cmax: + print(cmaxminrange.name) + # raise ValueError( + # 'denormalisation failed {}!={} + # '.format(np.max(cimage), cmax)) + return denormed_im + + +class DataBaseSave(DataBase): + def __init__(self, filename, channels=['R', 'G', 'B', 'D'], + arr_dtype=np.uint8): + """ + """ + DataBase.__init__(self, filename, channels=channels) + self.arr_dtype = arr_dtype + + + def create(self): + """use to decide weather to alter the database or not + return True because we will need + to write on database (Save class)""" + return True + + def write_image(self, posorient, image): + normed_im, cmaxminrange = self.normalise_image(image, self.arr_dtype) + rowid = self.get_posid(posorient) + # Write image + tablename = 'image' + params = dict() + params['rowid'] = rowid + params['data'] = normed_im + self.insert_replace(tablename, params) + # + tablename = 'normalisation' + params = dict() + params['rowid'] = rowid + for chan_n in self.normalisation_columns: + params[chan_n] = cmaxminrange.loc[chan_n] + self.insert_replace(tablename, params) + + def insert_replace(self, tablename, params): + assert isinstance(tablename, str),\ + 'table are named by string' + assert isinstance(params, dict),\ + 'params should be dictionary columns:val' + params_list = list() + columns_str = '' + for key, val in params.items(): + columns_str += key + ',' + params_list.append(val) + columns_str = columns_str[:-1] # remove last comma + if len(params_list) == 0: + warnings.warn('nothing to be done in {}'.format(tablename)) + return + questionsmarks = '?' + for _ in range(1, len(params_list)): + questionsmarks += ',?' + self.db_cursor.execute( + """ + INSERT OR REPLACE + INTO {} ({}) + VALUES ({}) + """.format(tablename, + columns_str, + questionsmarks), + tuple(params_list) + ) + self.db.commit() + + def normalise_image(self, image, dtype=np.uint8): + normed_im = np.zeros(image.shape, dtype=dtype) + maxval_nim = np.iinfo(normed_im.dtype).max + # + columns = list() + for chan_n in self.channels: + columns.append(str(chan_n) + '_max') + columns.append(str(chan_n) + '_min') + columns.append(str(chan_n) + '_range') + + cmaxminrange = pd.Series(index=columns) + for chan_i, chan_n in enumerate(self.channels): + cimage = image[:, :, chan_i].astype(float) + cmax = cimage.max() + cmin = cimage.min() + crange = cmax - cmin + cimage -= cmin + if np.isclose(crange, 0): + # cimage should be equal to 0 + # so crange is irelevant we can assign it to 1 + crange = 1 + cimage /= crange + cimage *= maxval_nim + cimage = cimage.astype(normed_im.dtype) + normed_im[:, :, chan_i] = cimage + cmaxminrange.loc[str(chan_n) + '_max'] = cmax + cmaxminrange.loc[str(chan_n) + '_min'] = cmin + cmaxminrange.loc[str(chan_n) + '_range'] = crange + return normed_im, cmaxminrange diff --git a/navipy/rendering/bee_sampling.py b/navipy/rendering/bee_sampling.py index 2d77e2ccf59dfbfab1aebe87a3c6046b303f70c3..c1f388a237d0dfa1eb4ac24c3de2256202aab837 100644 --- a/navipy/rendering/bee_sampling.py +++ b/navipy/rendering/bee_sampling.py @@ -124,7 +124,7 @@ harddrive space, as each image is composed of 4 channels of 180x360 pixels. if np.any(np.isnan(posorient)): # warnings.warn('frame_i: {} posorient nans'.format(frame_i)) continue - rowid = dataloger.get_posid(posorient) + rowid = dataloger.posid(posorient) if dataloger.check_data_validity(rowid): warnings.warn( 'frame_i: {} data is valid rowid {}'.format(frame_i, @@ -134,9 +134,9 @@ harddrive space, as each image is composed of 4 channels of 180x360 pixels. # and the cmaxminrange has not already been assigned # so the image need to be rendered self.update(posorient) - distance = self.get_distance() + distance = self.distance distance[distance > self.world_dim] = self.world_dim - image = self.get_image() + image = self.image image[:, :, 3] = distance dataloger.write_image(posorient, image) print('rendering completed') @@ -156,9 +156,9 @@ if __name__ == "__main__": bee_samp.create_sampling_grid( x, y, z, alpha1=alpha_1, alpha2=alpha_2, alpha3=alpha_3) bee_samp.world_dim = world_dim - grid_pos = bee_samp.get_grid_posorients() + grid_pos = bee_samp.grid_posorients condition = (grid_pos.x**2 + grid_pos.y**2) < ((bee_samp.world_dim / 2)**2) bee_samp.set_gridindeces2nan(condition[condition == 0].index) - bee_samp.set_cycle_samples(samples=5) + bee_samp.cycle_samples(samples=5) with tempfile.TemporaryDirectory() as folder: bee_samp.render(folder + '/database.db') diff --git a/navipy/rendering/bee_sampling.py~ b/navipy/rendering/bee_sampling.py~ index 247ccc2e49fa7b960ae301a1b56750eb245f4a36..44688df21ccb6d429cf40414c34232e5fc05f17f 100644 --- a/navipy/rendering/bee_sampling.py~ +++ b/navipy/rendering/bee_sampling.py~ @@ -96,8 +96,7 @@ harddrive space, as each image is composed of 4 channels of 180x360 pixels. """ return self.__grid_posorients.copy() - @gridindeces2nan.setter - def gridindeces2nan(self, indeces): + def set_gridindeces2nan(self, indeces): """Set certain grid point to nan, so they will be ignore in the rendering :param indeces: a list of indeces to be set to nan @@ -125,7 +124,7 @@ harddrive space, as each image is composed of 4 channels of 180x360 pixels. if np.any(np.isnan(posorient)): # warnings.warn('frame_i: {} posorient nans'.format(frame_i)) continue - rowid = dataloger.get_posid(posorient) + rowid = dataloger.posid(posorient) if dataloger.check_data_validity(rowid): warnings.warn( 'frame_i: {} data is valid rowid {}'.format(frame_i, @@ -135,9 +134,9 @@ harddrive space, as each image is composed of 4 channels of 180x360 pixels. # and the cmaxminrange has not already been assigned # so the image need to be rendered self.update(posorient) - distance = self.get_distance() + distance = self.distance distance[distance > self.world_dim] = self.world_dim - image = self.get_image() + image = self.image image[:, :, 3] = distance dataloger.write_image(posorient, image) print('rendering completed') @@ -157,7 +156,7 @@ if __name__ == "__main__": bee_samp.create_sampling_grid( x, y, z, alpha1=alpha_1, alpha2=alpha_2, alpha3=alpha_3) bee_samp.world_dim = world_dim - grid_pos = bee_samp.get_grid_posorients() + grid_pos = bee_samp.grid_posorients condition = (grid_pos.x**2 + grid_pos.y**2) < ((bee_samp.world_dim / 2)**2) bee_samp.set_gridindeces2nan(condition[condition == 0].index) bee_samp.set_cycle_samples(samples=5) diff --git a/navipy/rendering/cyber_bee.py b/navipy/rendering/cyber_bee.py index fd9550f5febfcd13faa3eefdca1ac77a2d8040bb..2bec28a8dc4b93697f8d645cf8f95773634f0f91 100644 --- a/navipy/rendering/cyber_bee.py +++ b/navipy/rendering/cyber_bee.py @@ -47,11 +47,11 @@ class Cyberbee(): # Filtering props bpy.context.scene.cycles.filter_type = 'GAUSSIAN' # Call all set function with default values - self.set_camera_rotation_mode() - self.set_camera_fov() - self.set_camera_gaussian_width() - self.set_camera_resolution() - self.set_cycle_samples() + self.camera_rotation_mode='XYZ' + self.camera_fov() + self.camera_gaussian_width=1.5 + self.camera_resolution() + self.cycle_samples=30 # switch on nodes # Create render link to OutputFile with Image and Z buffer bpy.context.scene.use_nodes = True @@ -183,7 +183,7 @@ class Cyberbee(): self.camera.data.cycles.longitude_max) return fov - @camera_fov.setter + #@camera_fov.setter def camera_fov(self, latmin=-90, latmax=+90, longmin=-180, longmax=+180): """change the field of view of the panoramic camera @@ -269,7 +269,7 @@ class Cyberbee(): resolution_y = bpy.context.scene.render.resolution_y return resolution_x, resolution_y - @camera_resolution.setter + #@camera_resolution.setter def camera_resolution(self, resolution_x=360, resolution_y=180): """change the camera resolution (nb of pixels) @@ -338,7 +338,7 @@ class Cyberbee(): filename = os.path.join(self.tmp_fileoutput['Folder'], self.tmp_fileoutput['Image'] + '0001' + self.tmp_fileoutput['ext']) - im_width, im_height = self.get_camera_resolution() + im_width, im_height = self.camera_resolution im = bpy.data.images.load(filename) pixels = np.array(im.pixels) # im=PIL.Image.open(filename) @@ -364,7 +364,7 @@ class Cyberbee(): filename = os.path.join(self.tmp_fileoutput['Folder'], self.tmp_fileoutput['Depth'] + '0001' + self.tmp_fileoutput['ext']) - im_width, im_height = self.get_camera_resolution() + im_width, im_height = self.camera_resolution im = bpy.data.images.load(filename) distance = np.array(im.pixels) # im=PIL.Image.open(filename) @@ -385,7 +385,7 @@ if __name__ == "__main__": for frame_i, posorient in enumerate(posorients): mybee.update(posorient) # Test image - image = mybee.get_image() + image = mybee.image # Test distance - distance = mybee.get_distance() + distance = mybee.distance print('Cyberbee OK') diff --git a/navipy/rendering/cyber_bee.py~ b/navipy/rendering/cyber_bee.py~ index f2d0efe89570a68cd2a9018cd4703d6ae54c465e..2bec28a8dc4b93697f8d645cf8f95773634f0f91 100644 --- a/navipy/rendering/cyber_bee.py~ +++ b/navipy/rendering/cyber_bee.py~ @@ -47,11 +47,11 @@ class Cyberbee(): # Filtering props bpy.context.scene.cycles.filter_type = 'GAUSSIAN' # Call all set function with default values - self.set_camera_rotation_mode() - self.set_camera_fov() - self.set_camera_gaussian_width() - self.set_camera_resolution() - self.set_cycle_samples() + self.camera_rotation_mode='XYZ' + self.camera_fov() + self.camera_gaussian_width=1.5 + self.camera_resolution() + self.cycle_samples=30 # switch on nodes # Create render link to OutputFile with Image and Z buffer bpy.context.scene.use_nodes = True @@ -183,7 +183,7 @@ class Cyberbee(): self.camera.data.cycles.longitude_max) return fov - @camera_fov.setter + #@camera_fov.setter def camera_fov(self, latmin=-90, latmax=+90, longmin=-180, longmax=+180): """change the field of view of the panoramic camera @@ -269,7 +269,7 @@ class Cyberbee(): resolution_y = bpy.context.scene.render.resolution_y return resolution_x, resolution_y - @camera_resolution.setter + #@camera_resolution.setter def camera_resolution(self, resolution_x=360, resolution_y=180): """change the camera resolution (nb of pixels) @@ -313,7 +313,7 @@ class Cyberbee(): if len(posorient) != 6: raise Exception('posorient should be a 1x6 double array') if not (isinstance(posorient, np.ndarray) or - isinstance(posorient, list)): + isinstance(posorient, list)): raise TypeError('posorient must be of type array or list') self.camera.location = posorient[:3] self.camera.rotation_euler = posorient[3:] @@ -338,7 +338,7 @@ class Cyberbee(): filename = os.path.join(self.tmp_fileoutput['Folder'], self.tmp_fileoutput['Image'] + '0001' + self.tmp_fileoutput['ext']) - im_width, im_height = self.get_camera_resolution() + im_width, im_height = self.camera_resolution im = bpy.data.images.load(filename) pixels = np.array(im.pixels) # im=PIL.Image.open(filename) @@ -364,7 +364,7 @@ class Cyberbee(): filename = os.path.join(self.tmp_fileoutput['Folder'], self.tmp_fileoutput['Depth'] + '0001' + self.tmp_fileoutput['ext']) - im_width, im_height = self.get_camera_resolution() + im_width, im_height = self.camera_resolution im = bpy.data.images.load(filename) distance = np.array(im.pixels) # im=PIL.Image.open(filename) @@ -385,7 +385,7 @@ if __name__ == "__main__": for frame_i, posorient in enumerate(posorients): mybee.update(posorient) # Test image - image = mybee.get_image() + image = mybee.image # Test distance - distance = mybee.get_distance() + distance = mybee.distance print('Cyberbee OK')