diff --git a/navipy/rendering/bee_sampling.py b/navipy/rendering/bee_sampling.py index 98e1de4004e316ec8b4fa98e8aa4009d41e81c1a..2d77e2ccf59dfbfab1aebe87a3c6046b303f70c3 100644 --- a/navipy/rendering/bee_sampling.py +++ b/navipy/rendering/bee_sampling.py @@ -41,6 +41,18 @@ harddrive space, as each image is composed of 4 channels of 180x360 pixels. :param alpha2: the 2nd euler angles :param alpha3: the 3rd euler angles """ + if not (isinstance(x, int) or isinstance(x, float)): + raise TypeError('x must be integer') + if not (isinstance(y, int) or isinstance(y, float)): + raise TypeError('y must be integer') + if not (isinstance(z, int) or isinstance(z, float)): + raise TypeError('z must be integer') + if not (isinstance(alpha1, float) or isinstance(alpha1, int)): + raise TypeError('alpha1 must be float') + if not (isinstance(alpha2, float) or isinstance(alpha2, int)): + raise TypeError('alpha2 must be float') + if not (isinstance(alpha3, float) or isinstance(alpha3, int)): + raise TypeError('alpha3 must be float') [mx, my, mz, ma1, ma2, ma3] = np.meshgrid(x, y, z, @@ -68,7 +80,8 @@ harddrive space, as each image is composed of 4 channels of 180x360 pixels. self.__grid_posorients.index.name = 'grid_index' self.__grid_posorients.name = 'grid_position_orientation' - def get_grid_posorients(self): + @property + def grid_posorients(self): """return a copy of the posorientation matrix :returns: position-orientations of the grid @@ -91,9 +104,15 @@ harddrive space, as each image is composed of 4 channels of 180x360 pixels. .. todo: use @property.setter def blacklist_indeces(self,indeces) """ + if not isinstance(indeces, list): + raise TypeError('indeces must be a list') + if any(np.isnan(indeces)): + raise ValueError('indeces must not contain nans') self.__grid_posorients.loc[indeces, :] = np.nan def render(self, database_filename): + if not isinstance(database_filename, str): + raise TypeError('filename must be a string') database_folder = os.path.dirname(database_filename) if not os.path.exists(database_folder): os.makedirs(database_folder) diff --git a/navipy/rendering/bee_sampling.py~ b/navipy/rendering/bee_sampling.py~ new file mode 100644 index 0000000000000000000000000000000000000000..247ccc2e49fa7b960ae301a1b56750eb245f4a36 --- /dev/null +++ b/navipy/rendering/bee_sampling.py~ @@ -0,0 +1,165 @@ +""" +The beesampling class + +.. tothinkof: conditional bpy import to build doc from comments +""" +import bpy +import os +import numpy as np +import pandas as pd +import warnings +from navipy.rendering.cyber_bee import Cyberbee +from navipy.database import DataBaseSave + + +class BeeSampling(Cyberbee): + """ + BeeSampling is a class deriving from Cyberbee. + The BeeSampling can be used to generate a database of +images taken on a rectangular regular grid. For the database, +the BeeSampling rely on DataBase + It worth noting that the generated database can take a large +harddrive space, as each image is composed of 4 channels of 180x360 pixels. + """ + + def __init__(self): + """Initialise the BeeSampling""" + Cyberbee.__init__(self) + self.blenddirname = os.path.dirname(bpy.data.filepath) + self.blendfilename = os.path.basename(bpy.data.filepath) + self.__grid_posorients = None + self.__grid_size = None + self.world_dim = np.inf + + def create_sampling_grid(self, x, y, z, alpha1, alpha2, alpha3): + """Create a cubic grid from all the sampling points + + :param x: the positions along the x-axis + :param y: the positions along the y-axis + :param z: the positions along the z-axis + :param alpha1: the first euler angles + :param alpha2: the 2nd euler angles + :param alpha3: the 3rd euler angles + """ + if not (isinstance(x, int) or isinstance(x, float)): + raise TypeError('x must be integer') + if not (isinstance(y, int) or isinstance(y, float)): + raise TypeError('y must be integer') + if not (isinstance(z, int) or isinstance(z, float)): + raise TypeError('z must be integer') + if not (isinstance(alpha1, float) or isinstance(alpha1, int)): + raise TypeError('alpha1 must be float') + if not (isinstance(alpha2, float) or isinstance(alpha2, int)): + raise TypeError('alpha2 must be float') + if not (isinstance(alpha3, float) or isinstance(alpha3, int)): + raise TypeError('alpha3 must be float') + [mx, my, mz, ma1, ma2, ma3] = np.meshgrid(x, + y, + z, + alpha1, + alpha2, + alpha3) + self.grid_size = mx.shape + mx = mx.flatten() + my = my.flatten() + mz = mz.flatten() + ma1 = ma1.flatten() + ma2 = ma2.flatten() + ma3 = ma3.flatten() + self.__grid_posorients = pd.DataFrame(index=range(mx.shape[0]), + columns=['x', 'y', 'z', + 'alpha_0', + 'alpha_1', + 'alpha_2']) + self.__grid_posorients.loc[:, 'x'] = mx + self.__grid_posorients.loc[:, 'y'] = my + self.__grid_posorients.loc[:, 'z'] = mz + self.__grid_posorients.loc[:, 'alpha_0'] = ma1 + self.__grid_posorients.loc[:, 'alpha_1'] = ma2 + self.__grid_posorients.loc[:, 'alpha_2'] = ma3 + self.__grid_posorients.index.name = 'grid_index' + self.__grid_posorients.name = 'grid_position_orientation' + + @property + def grid_posorients(self): + """return a copy of the posorientation matrix + + :returns: position-orientations of the grid + :rtype: pandas array + + .. todo: use @property + def grid_posorients(self) + + .. todo: create @property.setter + def grid_posorients(self,posorients) + (need a type check, and col check, and copy of df) + """ + return self.__grid_posorients.copy() + + @gridindeces2nan.setter + def 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 + + .. todo: use @property.setter + def blacklist_indeces(self,indeces) + """ + if not isinstance(indeces, list): + raise TypeError('indeces must be a list') + if any(np.isnan(indeces)): + raise ValueError('indeces must not contain nans') + self.__grid_posorients.loc[indeces, :] = np.nan + + def render(self, database_filename): + if not isinstance(database_filename, str): + raise TypeError('filename must be a string') + database_folder = os.path.dirname(database_filename) + if not os.path.exists(database_folder): + os.makedirs(database_folder) + dataloger = DataBaseSave(database_filename, + channels=['R', 'G', 'B', 'D'], + arr_dtype=np.uint8) + for frame_i, posorient in self.__grid_posorients.iloc[::-1].iterrows(): + print(frame_i) + if np.any(np.isnan(posorient)): + # warnings.warn('frame_i: {} posorient nans'.format(frame_i)) + continue + rowid = dataloger.get_posid(posorient) + if dataloger.check_data_validity(rowid): + warnings.warn( + 'frame_i: {} data is valid rowid {}'.format(frame_i, + rowid)) + continue + # The position-orientatios is valid (none nan) + # and the cmaxminrange has not already been assigned + # so the image need to be rendered + self.update(posorient) + distance = self.get_distance() + distance[distance > self.world_dim] = self.world_dim + image = self.get_image() + image[:, :, 3] = distance + dataloger.write_image(posorient, image) + print('rendering completed') + + +if __name__ == "__main__": + import tempfile + bee_samp = BeeSampling() + # Create mesh + world_dim = 15.0 + x = np.linspace(-7.5, 7.5, 5) + y = np.linspace(-7.5, 7.5, 5) + z = np.arange(1, 8, 2) + alpha_1 = np.array([0]) + np.pi / 2 + alpha_2 = np.array([0]) + alpha_3 = np.array([0]) + 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() + 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) + with tempfile.TemporaryDirectory() as folder: + bee_samp.render(folder + '/database.db') diff --git a/navipy/rendering/cyber_bee.py b/navipy/rendering/cyber_bee.py index c1b508c949cfbd39451eba7a7715ea7474c1515b..fd9550f5febfcd13faa3eefdca1ac77a2d8040bb 100644 --- a/navipy/rendering/cyber_bee.py +++ b/navipy/rendering/cyber_bee.py @@ -80,54 +80,112 @@ class Cyberbee(): ) self.tmp_fileoutput = tmp_fileoutput - def set_camera_rotation_mode(self, mode='XYZ'): + @property + def camera_rotation_mode(self): + """get the current camera rotation mode + + :returns: the mode of rotation used by the camera + :rtype: string + + ..todo: Use @property + def camera_rotation_mode(self) + """ + return bpy.data.scenes["Scene"].camera.rotation_mode + + @camera_rotation_mode.setter + def camera_rotation_mode(self, mode='XYZ'): """change the camera rotation mode + + :param mode: the mode of rotation for the camera see blender doc + (default: 'XYZ'). + :type mode: a string + .. seealso: blender bpy.data.scenes["Scene"].camera.rotation_mode + + ..todo: Use @property.setter + def camera_rotation_mode(self, mode='XYZ') + """ + if not isinstance(mode, str): + + raise TypeError('mode must be a string') bpy.data.scenes["Scene"].camera.rotation_mode = mode - def get_camera_rotation_mode(self): - """get the current camera rotation mode + @property + def cycle_samples(self): + """get the samples for rendering with cycle - :returns: the mode of rotation used by the camera - :rtype: string + :returns: the number of samples used for the rendering + :rtype: int - ..todo: Use @property - def camera_rotation_mode(self) + ..todo use @property + def cycle_samples(self) """ - return bpy.data.scenes["Scene"].camera.rotation_mode + return bpy.context.scene.cycles.samples - def set_cycle_samples(self, samples=30): + @cycle_samples.setter + 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 + ..todo: Use @property.setter + def cycle_samples(self, samples=30) """ + if not isinstance(samples, int): + raise TypeError('samples must be an integer') bpy.context.scene.cycles.samples = samples - def get_cycle_samples(self): - """get the samples for rendering with cycle + @property + def camera_fov(self): + """get fov of camera + + + + :returns: the field of view of the camera as min/max,longitude/latitude + + in degrees + + :rtype: dict + - :returns: the number of samples used for the rendering - :rtype: int ..todo use @property - def cycle_samples(self) + + def camera_fov() + + + + ..todo Change assert to if -> raise TypeError/KeyError + """ - return bpy.context.scene.cycles.samples + assert self.camera.data.type == 'PANO', 'Camera is not panoramic' + assert self.camera.cycles.panorama_type == 'EQUIRECTANGULAR',\ + 'Camera is not equirectangular' + fov = dict() + fov['latitude_min'] = np.rad2ged(self.camera.data.cycles.latitude_min) + fov['latitude_max'] = np.rad2ged(self.camera.data.cycles.latitude_max) + fov['longitude_min'] = np.rad2ged( + self.camera.data.cycles.longitude_min) + fov['longitude_max'] = np.rad2ged( + self.camera.data.cycles.longitude_max) + return fov - def set_camera_fov(self, latmin=-90, latmax=+90, - longmin=-180, longmax=+180): + @camera_fov.setter + def camera_fov(self, latmin=-90, latmax=+90, + longmin=-180, longmax=+180): """change the field of view of the panoramic camera :param latmin: minimum latitude (in deg) @@ -146,94 +204,103 @@ class Cyberbee(): ..todo Change assert to if -> raise TypeError()/KeyError() """ - assert self.camera.data.type == 'PANO', 'Camera is not panoramic' - assert self.camera.data.cycles.panorama_type == 'EQUIRECTANGULAR',\ - 'Camera is not equirectangular' + if not (isinstance(latmin, int) or isinstance(latmin, float)): + raise TypeError('latmin must be of type integer or float') + if not (isinstance(latmax, int) or isinstance(latmax, float)): + raise TypeError('latmin must be of type integer or float') + if not (isinstance(longmin, int) or isinstance(longmin, float)): + raise TypeError('latmin must be of type integer or float') + if not (isinstance(longmax, int) or isinstance(longmax, float)): + raise TypeError('latmin must be of type integer or float') + if not self.camera.data.type == 'PANO': + raise Exception('Camera is not panoramic') + if not self.camera.data.cycles.panorama_type == 'EQUIRECTANGULAR': + raise Exception('Camera is not equirectangular') self.camera.data.cycles.latitude_min = np.deg2rad(latmin) self.camera.data.cycles.latitude_max = np.deg2rad(latmax) self.camera.data.cycles.longitude_min = np.deg2rad(longmin) self.camera.data.cycles.longitude_max = np.deg2rad(longmax) - def get_camera_fov(self): - """get fov of camera + @property + def camera_gaussian_width(self, gauss_w=1.5): + """get width of the gaussian spatial filter - :returns: the field of view of the camera as min/max,longitude/latitude - in degrees - :rtype: dict + :returns: the width of the gaussian filter + :rtype: float ..todo use @property - def camera_fov() - - ..todo Change assert to if -> raise TypeError/KeyError + def camera_gaussian_width(self) """ - assert self.camera.data.type == 'PANO', 'Camera is not panoramic' - assert self.camera.cycles.panorama_type == 'EQUIRECTANGULAR',\ - 'Camera is not equirectangular' - fov = dict() - fov['latitude_min'] = np.rad2ged(self.camera.data.cycles.latitude_min) - fov['latitude_max'] = np.rad2ged(self.camera.data.cycles.latitude_max) - fov['longitude_min'] = np.rad2ged( - self.camera.data.cycles.longitude_min) - fov['longitude_max'] = np.rad2ged( - self.camera.data.cycles.longitude_max) - return fov + if not (isinstance(gauss_w, int) or isinstance(gauss_w, float)): + raise TypeError('gauss window must be integer or float') + return bpy.context.scene.cycles.filter_width - def set_camera_gaussian_width(self, gauss_w=1.5): + @camera_gaussian_width.setter + def camera_gaussian_width(self, gauss_w=1.5): """change width of the gaussian spatial filter :param gauss_w: width of the gaussian filter + :type gauss_w: float ..todo use @property.setter def camera_gaussian_width(self) + ..todo check that input argument is of correct type, if not raise TypeError() + """ + if not (isinstance(gauss_w, int) or isinstance(gauss_w, float)): + raise TypeError('gauss window must be integer or float') bpy.context.scene.cycles.filter_width = gauss_w - def get_camera_gaussian_width(self, gauss_w=1.5): - """get width of the gaussian spatial filter + @property + def camera_resolution(self): + """return camera resolution (x,y) - :returns: the width of the gaussian filter - :rtype: float + :returns: the resolution of the camera along (x-axis,y-axis) + :rtype: (int,int) ..todo use @property - def camera_gaussian_width(self) + def camera_resolution(self) """ - return bpy.context.scene.cycles.filter_width + resolution_x = bpy.context.scene.render.resolution_x + resolution_y = bpy.context.scene.render.resolution_y + return resolution_x, resolution_y - def set_camera_resolution(self, resolution_x=360, resolution_y=180): + @camera_resolution.setter + def camera_resolution(self, resolution_x=360, resolution_y=180): """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 + ..todo use @property.setter + def camera_resolution(self,resolution) + here resolution is [res_x,res_y] + + ..todo check type and raise TypeError + """ + if not isinstance(resolution_x, int): + raise TypeError('resolution must be integer') + if not isinstance(resolution_y, int): + raise TypeError('resolution must be integer') bpy.context.scene.render.resolution_x = resolution_x bpy.context.scene.render.resolution_y = resolution_y bpy.context.scene.render.resolution_percentage = 100 - def get_camera_resolution(self): - """return camera resolution (x,y) - - :returns: the resolution of the camera along (x-axis,y-axis) - :rtype: (int,int) - - ..todo use @property - def camera_resolution(self) - """ - resolution_x = bpy.context.scene.render.resolution_x - resolution_y = bpy.context.scene.render.resolution_y - return resolution_x, resolution_y - def update(self, posorient): """assign the position and the orientation of the camera. @@ -243,13 +310,18 @@ class Cyberbee(): specified by scene.camera.rotation_mode :type posorient: 1x6 double array """ - assert len(posorient) == 6, 'posorient should be a 1x6 double array' + if len(posorient) != 6: + raise Exception('posorient should be a 1x6 double array') + if not (isinstance(posorient, np.ndarray) or + isinstance(posorient, list)): + raise TypeError('posorient must be of type array or list') self.camera.location = posorient[:3] self.camera.rotation_euler = posorient[3:] # Render bpy.ops.render.render() - def get_image(self): + @property + def image(self): """return the last rendered image as a numpy array :returns: the image (height,width,4) @@ -274,7 +346,8 @@ class Cyberbee(): pixels = pixels.reshape([im_height, im_width, 4]) return pixels - def get_distance(self): + @property + def distance(self): """return the last rendered distance map as a numpy array :returns: the distance map (height, width) diff --git a/navipy/rendering/cyber_bee.py~ b/navipy/rendering/cyber_bee.py~ new file mode 100644 index 0000000000000000000000000000000000000000..f2d0efe89570a68cd2a9018cd4703d6ae54c465e --- /dev/null +++ b/navipy/rendering/cyber_bee.py~ @@ -0,0 +1,391 @@ +""" + How to test the script: + ----------------------- + >>> blender test.blend --background --python Cyberbee.py + + :Author: Olivier Bertrand (olivier.bertrand@uni-bielefeld.de) + :Parent module: Scene_rendering + + ..tothinkof for the doc bpy will raise an issue. + conditional import of bpy +""" +import bpy +import numpy as np +import tempfile +import os + + +class Cyberbee(): + """ + Cyberbee is a small class binding python with blender. + With Cyberbee one can move the bee to a position, and render what + the bee see at this position. + + The Bee eye is a panoramic camera with equirectangular projection + The light rays attaining the eyes are filtered with a gaussian. + """ + + def __init__(self): + """Initialise the Cyberbee + ..todo check that TemporaryDirectory is writtable and readable + """ + # Rendering engine needs to be Cycles to support panoramic + # equirectangular camera + bpy.context.scene.render.engine = 'CYCLES' + bpy.context.scene.render.layers["RenderLayer"].use_pass_z = True + # Look for object camera + camera_found = False + for obj in bpy.context.scene.objects: + if obj.type == 'CAMERA': + self.camera = obj + camera_found = True + break + assert camera_found, 'The blender file does not contain a camera' + # The bee eye is panoramic, and with equirectangular projection + self.camera.data.type = 'PANO' + self.camera.data.cycles.panorama_type = 'EQUIRECTANGULAR' + # 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() + # switch on nodes + # Create render link to OutputFile with Image and Z buffer + bpy.context.scene.use_nodes = True + scene = bpy.context.scene + nodes = scene.node_tree.nodes + + render_layers = nodes['Render Layers'] + output_file = nodes.new("CompositorNodeOutputFile") + output_file.format.file_format = "OPEN_EXR" + output_file.file_slots.remove(output_file.inputs[0]) + tmp_fileoutput = dict() + tmp_fileoutput['Image'] = 'Image' + tmp_fileoutput['Depth'] = 'Depth' + tmp_fileoutput['Folder'] = tempfile.TemporaryDirectory().name + tmp_fileoutput['ext'] = '.exr' + output_file.file_slots.new(tmp_fileoutput['Image']) + output_file.file_slots.new(tmp_fileoutput['Depth']) + output_file.base_path = tmp_fileoutput['Folder'] + scene.node_tree.links.new( + render_layers.outputs['Image'], + output_file.inputs['Image'] + ) + scene.node_tree.links.new( + render_layers.outputs['Z'], + output_file.inputs['Depth'] + ) + self.tmp_fileoutput = tmp_fileoutput + + @property + def camera_rotation_mode(self): + """get the current camera rotation mode + + :returns: the mode of rotation used by the camera + :rtype: string + + ..todo: Use @property + def camera_rotation_mode(self) + """ + return bpy.data.scenes["Scene"].camera.rotation_mode + + @camera_rotation_mode.setter + def camera_rotation_mode(self, mode='XYZ'): + """change the camera rotation mode + + + + :param mode: the mode of rotation for the camera see blender doc + + (default: 'XYZ'). + + :type mode: a string + + .. seealso: blender bpy.data.scenes["Scene"].camera.rotation_mode + + + + ..todo: Use @property.setter + + def camera_rotation_mode(self, mode='XYZ') + + """ + if not isinstance(mode, str): + + raise TypeError('mode must be a string') + bpy.data.scenes["Scene"].camera.rotation_mode = mode + + @property + def cycle_samples(self): + """get the samples for rendering with cycle + + :returns: the number of samples used for the rendering + :rtype: int + + ..todo use @property + def cycle_samples(self) + """ + return bpy.context.scene.cycles.samples + + @cycle_samples.setter + 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 + + + ..todo: Use @property.setter + + def cycle_samples(self, samples=30) + """ + if not isinstance(samples, int): + raise TypeError('samples must be an integer') + bpy.context.scene.cycles.samples = samples + + @property + def camera_fov(self): + """get fov of camera + + + + :returns: the field of view of the camera as min/max,longitude/latitude + + in degrees + + :rtype: dict + + + + ..todo use @property + + def camera_fov() + + + + ..todo Change assert to if -> raise TypeError/KeyError + + """ + assert self.camera.data.type == 'PANO', 'Camera is not panoramic' + assert self.camera.cycles.panorama_type == 'EQUIRECTANGULAR',\ + 'Camera is not equirectangular' + fov = dict() + fov['latitude_min'] = np.rad2ged(self.camera.data.cycles.latitude_min) + fov['latitude_max'] = np.rad2ged(self.camera.data.cycles.latitude_max) + fov['longitude_min'] = np.rad2ged( + self.camera.data.cycles.longitude_min) + fov['longitude_max'] = np.rad2ged( + self.camera.data.cycles.longitude_max) + return fov + + @camera_fov.setter + def camera_fov(self, latmin=-90, latmax=+90, + longmin=-180, longmax=+180): + """change the field of view of the panoramic camera + + :param latmin: minimum latitude (in deg) + :type latmin: float + :param latmax: maximum latitude (in deg) + :type latmax: float + :param longmin: minimum longitude (in deg) + :type longmin: float + :param longmin: maximum longitude (in deg) + :type longmin: float + + ..todo use @property.setter + def camera_fov(self, latlongrange) + here latlongrange is a a 2x2 list or array: + [[latmin,latmax],[longmin,longmax]] + + ..todo Change assert to if -> raise TypeError()/KeyError() + """ + if not (isinstance(latmin, int) or isinstance(latmin, float)): + raise TypeError('latmin must be of type integer or float') + if not (isinstance(latmax, int) or isinstance(latmax, float)): + raise TypeError('latmin must be of type integer or float') + if not (isinstance(longmin, int) or isinstance(longmin, float)): + raise TypeError('latmin must be of type integer or float') + if not (isinstance(longmax, int) or isinstance(longmax, float)): + raise TypeError('latmin must be of type integer or float') + if not self.camera.data.type == 'PANO': + raise Exception('Camera is not panoramic') + if not self.camera.data.cycles.panorama_type == 'EQUIRECTANGULAR': + raise Exception('Camera is not equirectangular') + self.camera.data.cycles.latitude_min = np.deg2rad(latmin) + self.camera.data.cycles.latitude_max = np.deg2rad(latmax) + self.camera.data.cycles.longitude_min = np.deg2rad(longmin) + self.camera.data.cycles.longitude_max = np.deg2rad(longmax) + + @property + def camera_gaussian_width(self, gauss_w=1.5): + """get width of the gaussian spatial filter + + :returns: the width of the gaussian filter + :rtype: float + + ..todo use @property + def camera_gaussian_width(self) + """ + if not (isinstance(gauss_w, int) or isinstance(gauss_w, float)): + raise TypeError('gauss window must be integer or float') + return bpy.context.scene.cycles.filter_width + + @camera_gaussian_width.setter + def camera_gaussian_width(self, gauss_w=1.5): + """change width of the gaussian spatial filter + + :param gauss_w: width of the gaussian filter + + :type gauss_w: float + + ..todo use @property.setter + def camera_gaussian_width(self) + + + ..todo check that input argument is of correct type, + if not raise TypeError() + + """ + if not (isinstance(gauss_w, int) or isinstance(gauss_w, float)): + raise TypeError('gauss window must be integer or float') + bpy.context.scene.cycles.filter_width = gauss_w + + @property + def camera_resolution(self): + """return camera resolution (x,y) + + :returns: the resolution of the camera along (x-axis,y-axis) + :rtype: (int,int) + + ..todo use @property + def camera_resolution(self) + """ + resolution_x = bpy.context.scene.render.resolution_x + resolution_y = bpy.context.scene.render.resolution_y + return resolution_x, resolution_y + + @camera_resolution.setter + def camera_resolution(self, resolution_x=360, resolution_y=180): + """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 + + + ..todo use @property.setter + + def camera_resolution(self,resolution) + + here resolution is [res_x,res_y] + + + + ..todo check type and raise TypeError + + """ + if not isinstance(resolution_x, int): + raise TypeError('resolution must be integer') + if not isinstance(resolution_y, int): + raise TypeError('resolution must be integer') + bpy.context.scene.render.resolution_x = resolution_x + bpy.context.scene.render.resolution_y = resolution_y + bpy.context.scene.render.resolution_percentage = 100 + + def update(self, posorient): + """assign the position and the orientation of the camera. + + :param posorient: is a 1x6 vector continaing: + 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 + """ + if len(posorient) != 6: + raise Exception('posorient should be a 1x6 double array') + if not (isinstance(posorient, np.ndarray) or + isinstance(posorient, list)): + raise TypeError('posorient must be of type array or list') + self.camera.location = posorient[:3] + self.camera.rotation_euler = posorient[3:] + # Render + bpy.ops.render.render() + + @property + def image(self): + """return the last rendered image as a numpy array + + :returns: the image (height,width,4) + :rtype: a double numpy array + + .. note: A temporary file will be written on the harddrive, + due to API blender limitation + + .. todo: use @property + def image(self) + """ + # save image as a temporary file, and then loaded + # sadly the rendered image pixels can not directly be access + 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 = bpy.data.images.load(filename) + pixels = np.array(im.pixels) + # im=PIL.Image.open(filename) + # pixels=np.asarray(im) + pixels = pixels.reshape([im_height, im_width, 4]) + return pixels + + @property + def distance(self): + """return the last rendered distance map as a 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 + + .. todo: use @property + def distance(self) + """ + # save image as a temporary file, and then loaded + # sadly the rendered image pixels can not directly be access + 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 = bpy.data.images.load(filename) + distance = np.array(im.pixels) + # im=PIL.Image.open(filename) + # distance=np.asarray(im) + distance = distance.reshape([im_height, im_width, 4]) + distance = distance[:, :, 0] + return distance + + +if __name__ == "__main__": + # Initiate the Cyberbee + mybee = Cyberbee() + frames_per_revolution = 5.0 + step_size = 2 * np.pi / frames_per_revolution + posorients = np.zeros((frames_per_revolution, 6)) + posorients[:, 0] = np.sin(np.arange(frames_per_revolution) * step_size) * 5 + posorients[:, 1] = np.cos(np.arange(frames_per_revolution) * step_size) * 5 + for frame_i, posorient in enumerate(posorients): + mybee.update(posorient) + # Test image + image = mybee.get_image() + # Test distance + distance = mybee.get_distance() + print('Cyberbee OK') diff --git a/todo~ b/todo~ new file mode 100644 index 0000000000000000000000000000000000000000..e1a0fa9d49479cc05553767fb207b2b804b2df31 --- /dev/null +++ b/todo~ @@ -0,0 +1,39 @@ +======= +------------------------------------------------------ +0003: Improve database +In the init database I would like to use class properties instead of get/read +line: 263,273,and 394 + def create(self) +should be replaced by: + @property + def create(self) + +It implies that at every point that self.create() is called should be changed to self.create [Note the absence of parenthesis] + +line: 298 + def get_posorients(self) +should be replaced by + @property + def posorients(self) + +Need to propagate the changes through all the code (see rendering / processing / moving ) +------------------------------------------------------ +0004: Change every assert by a if () raise TypeError/IOError/KeyError/... +- present in processing +- present in database + +------------------------------------------------------ +0005: Write test function for raise Error +- for every raise error create a test function, checking that the error is correctly thrown (see moving/test_agent for inspiration) + +------------------------------------------------------ +0006: Improve comparing/__init__.py +- Change comment in simple_image_diff +- call simple_image_diff in imagediff. you can then remove all the assert (that should be raise Error at that point) from imagediff +- Add comment to diff_optic_flow / add reference to article. +- rename capitalised variable A,ATA, and b as longer variable name [future PEP8 will forbid this] + +------------------------------------------------------ +0007: Improve cyber_bee so that every getter and setter are properties + +0008: Improve bee_sampling so that every getter and setter are properties