diff --git a/navipy/rendering/BeeSampling.py b/navipy/rendering/BeeSampling.py deleted file mode 100644 index f7e2010543fd3c83b2eb446effa7a176c45f7664..0000000000000000000000000000000000000000 --- a/navipy/rendering/BeeSampling.py +++ /dev/null @@ -1,137 +0,0 @@ -""" - How to test the script: - ----------------------- - >>> blender test.blend --background --python BeeSampling.py - - :Author: Olivier Bertrand (olivier.bertrand@uni-bielefeld.de) - :Parent module: Scene_rendering -""" -import bpy -import os -import numpy as np -import pandas as pd -import warnings -from Cyberbee import Cyberbee -from 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 - """ - [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' - - def get_grid_posorients(self): - """return a copy of the posorientation matrix - - :returns: position-orientations of the grid - :rtype: pandas array - """ - return self.__grid_posorients.copy() - - 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 - """ - self.__grid_posorients.loc[indeces, :] = np.nan - - def render(self, database_filename): - 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.iterrows(): - 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/Cyberbee.py b/navipy/rendering/Cyberbee.py deleted file mode 100644 index 5b6805f4da16fc9eb0d913c1208382ab67e324bd..0000000000000000000000000000000000000000 --- a/navipy/rendering/Cyberbee.py +++ /dev/null @@ -1,263 +0,0 @@ -""" - How to test the script: - ----------------------- - >>> blender test.blend --background --python Cyberbee.py - - :Author: Olivier Bertrand (olivier.bertrand@uni-bielefeld.de) - :Parent module: Scene_rendering -""" -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""" - # 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 - - def set_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 - """ - bpy.data.scenes["Scene"].camera.rotation_mode = mode - - def get_camera_rotation_mode(self): - """get the current camera rotation mode - - :returns: the mode of rotation used by the camera - :rtype: string - """ - return bpy.data.scenes["Scene"].camera.rotation_mode - - def set_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 - """ - bpy.context.scene.cycles.samples = samples - - def get_cycle_samples(self): - """get the samples for rendering with cycle - - :returns: the number of samples used for the rendering - :rtype: int - """ - return bpy.context.scene.cycles.samples - - def set_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 - """ - assert self.camera.data.type == 'PANO', 'Camera is not panoramic' - assert self.camera.data.cycles.panorama_type == 'EQUIRECTANGULAR',\ - '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 - - :returns: the field of view of the camera as min/max,longitude/latitude - in degrees - :rtype: dict - """ - 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_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 - """ - 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 - - :returns: the width of the gaussian filter - :rtype: float - """ - return bpy.context.scene.cycles.filter_width - - def set_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 - """ - 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) - """ - 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. - - :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 - """ - assert len(posorient) == 6, 'posorient should be a 1x6 double array' - self.camera.location = posorient[:3] - self.camera.rotation_euler = posorient[3:] - # Render - bpy.ops.render.render() - - def get_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 - """ - # 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') - 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 - - def get_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 - """ - # 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') - 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/navipy/rendering/bee_sampling.py b/navipy/rendering/bee_sampling.py index 0d91148e273409ba0a098be6ce4ce149cf23ee97..98e1de4004e316ec8b4fa98e8aa4009d41e81c1a 100644 --- a/navipy/rendering/bee_sampling.py +++ b/navipy/rendering/bee_sampling.py @@ -1,5 +1,7 @@ """ The beesampling class + +.. tothinkof: conditional bpy import to build doc from comments """ import bpy import os @@ -71,6 +73,13 @@ harddrive space, as each image is composed of 4 channels of 180x360 pixels. :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() @@ -78,6 +87,9 @@ harddrive space, as each image is composed of 4 channels of 180x360 pixels. """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) """ self.__grid_posorients.loc[indeces, :] = np.nan diff --git a/navipy/rendering/cyber_bee.py b/navipy/rendering/cyber_bee.py index 7184ff8c80ecf4aebb5655d3874e5936c7b4829c..458ed5eb17f8081d69fb3f7c752c8c25b1ec75d9 100644 --- a/navipy/rendering/cyber_bee.py +++ b/navipy/rendering/cyber_bee.py @@ -5,6 +5,9 @@ :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 @@ -23,7 +26,9 @@ class Cyberbee(): """ def __init__(self): - """Initialise the Cyberbee""" + """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' @@ -82,6 +87,9 @@ class Cyberbee(): (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') """ bpy.data.scenes["Scene"].camera.rotation_mode = mode @@ -90,6 +98,9 @@ class Cyberbee(): :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 @@ -98,6 +109,9 @@ class Cyberbee(): :param samples: the number of samples to use when rendering images :type samples: int + + ..todo: Use @property.setter + def cycle_samples(self, samples=30) """ bpy.context.scene.cycles.samples = samples @@ -106,6 +120,9 @@ class Cyberbee(): :returns: the number of samples used for the rendering :rtype: int + + ..todo use @property + def cycle_samples(self) """ return bpy.context.scene.cycles.samples @@ -121,6 +138,13 @@ class Cyberbee(): :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() """ assert self.camera.data.type == 'PANO', 'Camera is not panoramic' assert self.camera.data.cycles.panorama_type == 'EQUIRECTANGULAR',\ @@ -136,6 +160,11 @@ class Cyberbee(): :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',\ @@ -154,6 +183,12 @@ class Cyberbee(): :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() """ bpy.context.scene.cycles.filter_width = gauss_w @@ -162,6 +197,9 @@ class Cyberbee(): :returns: the width of the gaussian filter :rtype: float + + ..todo use @property + def camera_gaussian_width(self) """ return bpy.context.scene.cycles.filter_width @@ -172,6 +210,12 @@ class Cyberbee(): :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 """ bpy.context.scene.render.resolution_x = resolution_x bpy.context.scene.render.resolution_y = resolution_y @@ -182,6 +226,9 @@ class Cyberbee(): :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 @@ -210,6 +257,9 @@ class Cyberbee(): .. 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 @@ -232,6 +282,9 @@ class Cyberbee(): .. 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 diff --git a/todo b/todo index adcd3e1fb223c18b0137397e1bc4df189e109f03..e1a0fa9d49479cc05553767fb207b2b804b2df31 100644 --- a/todo +++ b/todo @@ -34,4 +34,6 @@ Need to propagate the changes through all the code (see rendering / processing / - rename capitalised variable A,ATA, and b as longer variable name [future PEP8 will forbid this] ------------------------------------------------------ -0007: Fix test processing \ No newline at end of file +0007: Improve cyber_bee so that every getter and setter are properties + +0008: Improve bee_sampling so that every getter and setter are properties