diff --git a/build/lib/navipy/database/__init__.py b/build/lib/navipy/database/__init__.py index 2df843460a1acdec657782cbac74091d54d05847..cd9019245cb2e5c807b10646f5b2d70cb9967c14 100644 --- a/build/lib/navipy/database/__init__.py +++ b/build/lib/navipy/database/__init__.py @@ -403,7 +403,17 @@ database toreturn = toreturn.astype(float) return toreturn - def read_image(self, posorient=None, rowid=None): + def scene(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 + """ + if not isinstance(posorient, pd.Series): ('posorient should be a pandas Series') if posorient is not None: @@ -436,15 +446,6 @@ database raise Exception('posorient and rowid can not be both None') if posorient is not None: rowid = self.get_posid(posorient) - """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 - """ if (posorient is None) and (rowid is None): Exception('posorient and rowid can not be both None') if posorient is not None: @@ -472,7 +473,9 @@ database cmaxminrange.name = cmaxminrange.id cmaxminrange.drop('id') cmaxminrange = cmaxminrange.astype(float) - return self.denormalise_image(image, cmaxminrange) + toreturn = self.denormalise_image(image, cmaxminrange) + toreturn = toreturn[..., np.newaxis] + return toreturn def denormalise_image(self, image, cmaxminrange): if len(image.shape) != 3: diff --git a/build/lib/navipy/moving/agent.py b/build/lib/navipy/moving/agent.py index ad3cd9c6c4370243c9b43b4edb629c4bd3f57340..f6f10f98d5e558e4d75193b81a698a4b33710e8e 100644 --- a/build/lib/navipy/moving/agent.py +++ b/build/lib/navipy/moving/agent.py @@ -15,47 +15,173 @@ from navipy.database import DataBaseLoad import navipy.moving.maths as navimomath -def defaultcallback(database, posorients): +def defaultcallback(*args, **kwargs): raise NameError('No Callback') class AbstractAgent(): + def __init__(self): + self._sensors = defaultcallback + self._motion = defaultcallback + self._alter_posorientvel = defaultcallback + self._posorient_col = ['x', 'y', 'z', + 'alpha_0', 'alpha_1', 'alpha_2'] + self._velocity_col = ['d' + col for col in self._posorient_col] + self._posorient_vel_col = self._posorient_col.copy() + self._posorient_vel_col.extend(self._velocity_col) + self._posorient_vel = pd.Series( + index=self._posorient_vel_col, + data=np.nan) + + @property + def posorient(self): + return self._posorient_vel.loc[self._posorient_col].copy() + + @posorient.setter + def posorient(self, posorient): + if isinstance(posorient, pd.Series) is False: + raise TypeError('posorient should be a pandas Series') + for col in self._posorient_col: + if col not in posorient.index: + raise KeyError( + 'posorient should have {} as index'.format(col)) + self._posorient_vel.loc[self._posorient_col] = \ + posorient.loc[self._posorient_col] + + @property + def velocity(self): + return self._posorient_vel.loc[self._velocity_col].copy() + + @velocity.setter + def velocity(self, velocity): + if isinstance(velocity, pd.Series) is False: + raise TypeError('velocity should be a pandas Series') + for col in self._velocity_col: + if col not in velocity.index: + raise KeyError( + 'velocity should have {} as index'.format(col)) + self._posorient_vel.loc[self._velocity_col] = \ + velocity.loc[self._velocity_col] + + @property + def motion(self): + return inspect.getsourcelines(self._motion) + + @property + def sensors(self): + return inspect.getsourcelines(self._sensors) + + @property + def alter_posorientvel(self): + return inspect.getsourcelines(self._alter_posorientvel) + + def move(self): + scene = self._sensors(self.posorient) + newpos = self._motion(self.posorient, scene) + alteredpos = self._alter_posorientvel(newpos) + self.posorient = alteredpos + self.velocity = alteredpos + + def fly(self, max_nstep, return_tra=False): + """move cyberbee until max step has been performed + """ + if return_tra: + trajectory = pd.DataFrame(index=range(0, max_nstep), + columns=self.posorient_vel_col) + trajectory.loc[0, :] = self._posorient_vel.copy() + for stepi in range(1, max_nstep): + print(stepi, self._posorient_vel) + self.move() + if return_tra: + trajectory.loc[stepi, :] = self._posorient_vel.copy() + if return_tra: + return trajectory + else: + return None + + +class CyberBeeAgent(AbstractAgent): + """ + A common method to make an agent moves is to update the sensory \ + information at the current agent location, then process this \ + information such to deduce the agent motion, and finally displaced\ + the agent at its new location. This iterative method can be used \ + with a wide range of models. + In navipy the update of the sensory information is done by the \ + Cyberbee a class interfacing blender with the navipy, such that \ + visual information can be rendered at the agent location. + To use the CyberBeeAgent you first need to create a function with \ + input the scene and the position orientation of the agent, and \ + with output the agent motion. The CyberBeeAgent can then be moved \ + for a single step, or fly until a given number of movement has \ + been effectuated or the agent stopped. + """ + + def __init__(self, cyberbee): + AbstractAgent.__init__(self) + AbstractAgent._alter_posorientvel = \ + lambda motion_vec: navimomath.next_pos(motion_vec, + move_mode='free_run') + self.sensors = cyberbee.scene + + @AbstractAgent.sensors.setter + def sensors(self, cyberbee): + self._sensors = cyberbee.scene + + @AbstractAgent.motion.setter + def motion(self, motion): + self._motion = motion + + +class GridAgent(AbstractAgent, Process): """ - An abtract class for agent + The use of a close loop model including visual rendering is \ + sometimes too slow to efficiently test several models or tune the \ + parameters of a given models. The GridAgent solves this problem by \ + restricting the agent motion on locations with rendered scene. The \ + agent moves thus on a grid, and its next position is always \ + snapped to the closest grid location. The finer the grid is, the \ + larger the database storing all sceneries and grid location is; \ + but also the more accurate the agent motion is. The grid size \ + depend on the storage space, the time you can wait for the \ + database creation, and how sensitive to exact location your model is. + Similar to the CyberBeeAgent, your navigational model should be \ + contained in a function with input the scene and the position \ + orientation of the agent, and with output the agent motion. The \ + agent can be move a single step, or fly until a given number of \ + movement has been effectuated or the agent stopped. It is here \ + worth mentioning that the GridAgent inherit from the Process \ + class of the multiprocessing module of the standard python \ + library. Thus, several GridAgents can safely be run in parallel. """ - def __init__(self, - database_filename, - memory_friendly=False): + def __init__(self, database_filename, + posorients_queue=None, + results_queue=None): + if (posorients_queue is not None) and (results_queue is not None): + multiprocessing.Process.__init__(self) + AbstractAgent.__init__(self) + self._alter_posorientvel = self.snap_to_grid + self.sensors = database_filename + @AbstractAgent.sensors.setter + def sensors(self, database_filename): self.db = DataBaseLoad(database_filename) - self.dbname = database_filename - if memory_friendly: - self.__posorients = None - else: - self.__posorients = self.db.posorients - # set mode of motion - mode_move = {'mode': 'on_cubic_grid', - 'param': {'grid_spacing': - pd.Series(data=1, - index=['dx', 'dy', 'dz'])}} - self.mode_of_motion = mode_move + self._posorients = self.db.posorients + self._sensors = self.db.scene - @property - def posorients(self): - toreturn = self.__posorients - if toreturn is not None: - toreturn = toreturn.copy() - return toreturn + @AbstractAgent.motion.setter + def motion(self, motion): + self._motion = motion @property def mode_of_motion(self): """ """ - toreturn = self.__mode_move + toreturn = self._mode_move toreturn['describe'] = \ navimomath.mode_moves_supported()[ - self.__mode_move['mode']]['describe'] + self._mode_move['mode']]['describe'] return toreturn @mode_of_motion.setter @@ -75,165 +201,109 @@ class AbstractAgent(): if param not in mode['param']: raise KeyError( "'{}' is not in mode['param']".format(param)) - self.__mode_move = mode + self._mode_move = mode else: raise ValueError('mode is not supported') - def abstractmove(self, posorients_vel): - if isinstance(posorients_vel, pd.Series) is False: - raise TypeError('posorients_vel should be a pandas Series') - for col in ['x', 'y', 'z', 'alpha_0', 'alpha_1', 'alpha_2', - 'dx', 'dy', 'dz', 'dalpha_0', 'dalpha_1', 'dalpha_2']: - if col not in posorients_vel.index: - raise KeyError( - 'posorients_vel should have {} as index'.format(col)) - # Compute the next position - posorients_vel = navimomath.next_pos( - posorients_vel, - move_mode=self.__mode_move['mode'], - move_param=self.__mode_move['param']) - - # Compute the closest possible position - if posorients_vel is None: - tmp = navimomath.closest_pos_memory_friendly( - posorients_vel, - self.db) - posorients_vel[['x', 'y', 'z', - 'alpha_0', 'alpha_1', 'alpha_2']] = tmp - posorients_vel.name = tmp.name - else: - tmp = navimomath.closest_pos( - posorients_vel, - self.__posorients) - posorients_vel[['x', 'y', 'z', - 'alpha_0', 'alpha_1', 'alpha_2']] = tmp - posorients_vel.name = tmp.name - return posorients_vel - - -class Single(AbstractAgent, Process): - - def __init__(self, - database_filename, - initial_condition, - memory_friendly=False, - posorients_queue=None, - results_queue=None): - if (posorients_queue is not None) and (results_queue is not None): - multiprocessing.Process.__init__(self) - AbstractAgent.__init__(self, database_filename, - memory_friendly) - - self.__posorientvel = pd.Series( - data=0, - index=['x', 'y', 'z', - 'alpha_0', 'alpha_1', 'alpha_2', - 'dx', 'dy', 'dz', - 'dalpha_0', 'dalpha_1', 'dalpha_2'], - dtype=np.float) - - if isinstance(initial_condition, pd.Series): - if is_numeric_dtype(initial_condition): - common_id = list(set(initial_condition.index).intersection( - self.__posorientvel.index)) - self.__posorientvel.loc[common_id] = \ - initial_condition.loc[common_id] - else: - raise TypeError('vel should be numeric') + def snap_to_grid(self, posorient_vel): + posorient_vel = navimomath.next_pos( + posorient_vel, + move_mode=self._mode_move['mode'], + move_param=self._mode_move['param']) + tmp = navimomath.closest_pos( + posorient_vel, self._posorients) + posorient_vel.loc[self._posorient_col] = \ + tmp.loc[self._posorient_col] + posorient_vel.name = tmp.name + return posorient_vel + def move(self): + if hasattr(self, '_mode_move'): + AbstractAgent.move(self) else: - raise TypeError('vel should be a pandas Series') + raise AttributeError( + 'GridAgent object has no attribute _mode_move\n' + + 'Please set the mode of motion') - self.__posorients_queue = posorients_queue - self.__results_queue = results_queue - self.__callback_function = defaultcallback - - def move(self): - # Compute the next position - tmp = self.__callback_function(database=self.db, - posorient=self.__posorientvel) - common_id = list(set(tmp.index).intersection( - self.__posorientvel.index)) - self.__posorientvel.loc[common_id] = tmp.loc[common_id] - self.__posorientvel = self.abstractmove(self.__posorientvel) - - def fly(self, nsteps): - """move until either speed is null, or nsteps has been reached""" - prev_move = self.__posorientvel - for stepi in range(nsteps): - self.move() - if prev_move.equals(self.__posorientvel): - break - prev_move = self.__posorientvel + def fly(self, max_nstep, return_tra=False): + if hasattr(self, '_mode_move'): + return AbstractAgent.fly(self, max_nstep, return_tra) + else: + raise AttributeError( + 'GridAgent object has no attribute _mode_move\n' + + 'Please set the mode of motion') def run(self): """ Only supported when multiprocess""" - if self.__posorients_queue is None or self.__results_queue is None: + if self._posorients_queue is None or self._results_queue is None: raise NameError('Single agent class has not be inititialised ' + 'with multiprocessing suppport') proc_name = self.name print('Process {} started'.format(proc_name)) while True: - start_posorient = self.__posorients_queue.get(timeout=1) + start_posorient = self._posorients_queue.get(timeout=1) if start_posorient is None: # Poison pill means shutdown) break common_id = list(set(start_posorient.index).intersection( - self.__posorientvel.index)) - self.__posorientvel.loc[common_id] = start_posorient.loc[common_id] + self._posorientvel.index)) + self._posorientvel.loc[common_id] = start_posorient.loc[common_id] self.move() - next_posorient = self.__posorientvel + next_posorient = self._posorientvel - self.__posorients_queue.task_done() - self.__results_queue.put((start_posorient, next_posorient)) - self.__posorients_queue.task_done() + self._posorients_queue.task_done() + self._results_queue.put((start_posorient, next_posorient)) + self._posorients_queue.task_done() print('Process {} done'.format(proc_name)) - @property - def callback_function(self): - return inspect.getsourcelines(self.__callback_function) - - @callback_function.setter - def callback_function(self, callback_function): - self.__callback_function = callback_function - - @property - def position(self): - return self.__posorientvel.loc[['x', 'y', 'z']] - - @property - def velocity(self): - return self.__posorientvel.loc[['dx', 'dy', 'dz']] - - @property - def orientation(self): - return self.__posorientvel.loc[['alpha_0', 'alpha_1', 'alpha_2']] - - @property - def angular_velocity(self): - return self.__posorientvel.loc[['dalpha_0', 'dalpha_1', 'dalpha_2']] - -class Multi(AbstractAgent): +class GraphAgent(): + """ + As mentioned above, in every model of navigation the agent motion \ + is derived from its current external state, its position \ + orientation as well as the derivatives, and its internal state. \ + However, when the agent motion is only derived from its current \ + position orientation, and what is seen from this location, the \ + simulation of an agent can be drastically simplified. Indeed, \ + not only the scene at relevant location can be pre-rendered, but \ + the motion of the agent from those locations as well. The agent \ + being restricted to move from relevant locations to relevant \ + locations, a graph of interconnected locations can be built. The \ + nodes of the graph are the relevant locations, and the directed \ + edges the motion of the agent from one location to the next. \ + GraphAgent can build such graph by simply using a database of \ + pre-rendered scenery at relevant locations, and a function \ + giving the motion of the agent from a scene and the agent \ + position orientation. Once the graph has been generated, \ + attractors can be found, the number of locations converging to \ + those (i.e. the catchment area or volume), if two locations are \ + connected, etc. + To speed up certain calculations, additional values can stored \ + at each graph node and access from the callback function. It is \ + worth mentioning a warning here. The size of the graph can be \ + incredibly large. Thus, not too much information can be stored \ + at each node. To assess the memory size of the graph before \ + creating it, one can use the tool agent.tools.assess_graphmemsize. + """ def __init__(self, database_filename): - super().__init__(database_filename, False) + self.db = DataBaseLoad(database_filename) # Init the graph - self.__graph = nx.DiGraph() + self._graph = nx.DiGraph() for row_id, posor in self.db.posorients.iterrows(): posor.name = row_id - self.__graph.add_node(row_id, - posorient=posor) + self._graph.add_node(row_id, + posorient=posor) @property def graph(self): - return self.__graph + return self._graph @graph.setter def graph(self, graph): if isinstance(graph, nx.DiGraph) is False: raise TypeError('graph is not a nx.DiGraph') - self.__graph = graph.copy() + self._graph = graph.copy() self.check_graph() def build_graph(self, callback_function, @@ -243,17 +313,14 @@ class Multi(AbstractAgent): results_edges = [] posorients_queue = JoinableQueue() results_queue = Queue() - for node in self.__graph.nodes: - posorients_queue.put(self.__graph.nodes[node]['posorient']) - initpos = 0 * self.__graph.nodes[node]['posorient'] + for node in self._graph.nodes: + posorients_queue.put(self._graph.nodes[node]['posorient']) # Start ndatabase loader num_agents = ncpu - agents = [Single(self.dbname, - initial_condition=initpos, - memory_friendly=False, - posorients_queue=posorients_queue, - results_queue=results_queue) + agents = [GridAgent(self.dbname, + posorients_queue=posorients_queue, + results_queue=results_queue) for _ in range(num_agents)] for w in agents: w.callback_function = callback_function @@ -266,21 +333,21 @@ class Multi(AbstractAgent): # Wait for all of the tasks to finish # posorients_queue.join() - for _ in range(nx.number_of_nodes(self.__graph)): + for _ in range(nx.number_of_nodes(self._graph)): result = results_queue.get(timeout=timeout) results_edges.append((result[0].name, result[1].name)) # print(results_edges[-1]) - self.__graph.add_edges_from(results_edges) + self._graph.add_edges_from(results_edges) self.check_graph() def check_graph(self): self.check_single_target() def check_single_target(self): - for node in self.__graph.nodes: + for node in self._graph.nodes: # not connected -> loop not ran - for count, _ in enumerate(self.__graph.neighbors(node)): + for count, _ in enumerate(self._graph.neighbors(node)): # count == 0 -> connected to one node # count == 1 -> connected to two nodes if count > 0: @@ -291,7 +358,7 @@ class Multi(AbstractAgent): """Return a list of node going to each attractor in a graph """ attractors = list() - for attractor in nx.attracting_components(self.__graph): + for attractor in nx.attracting_components(self._graph): att = dict() att['attractor'] = attractor attractors.append(att) @@ -349,13 +416,13 @@ class Multi(AbstractAgent): def reach_goals(self, goals): """ Return all paths to the goals """ - return nx.shortest_path(self.__graph, target=goals) + return nx.shortest_path(self._graph, target=goals) def neighboring_nodes(self, target): """ Return the nodes going to the target """ # Reverse graph because nx.neighbors give the end node # and we want to find the start node going to target # not where target goes. - tmpgraph = self.__graph.reverse(copy=True) + tmpgraph = self._graph.reverse(copy=True) neighbors = tmpgraph.neighbors(target) return neighbors diff --git a/build/lib/navipy/moving/test_agent.py b/build/lib/navipy/moving/test_agent.py index 8775361fbcede6c9ee87feb54c9d41bd8ca496c5..5f73a7a0e743a0cfba81b0dffd9b85c43f2e7dfe 100644 --- a/build/lib/navipy/moving/test_agent.py +++ b/build/lib/navipy/moving/test_agent.py @@ -1,5 +1,5 @@ """ -Test of maths +Test of agent """ import numpy as np import pandas as pd @@ -17,78 +17,86 @@ class TestNavipyMovingAgent(unittest.TestCase): self.mydb_filename = pkg_resources.resource_filename( 'navipy', 'resources/database.db') self.mydb = navidb.DataBaseLoad(self.mydb_filename) + self.__posorient_col = ['x', 'y', 'z', + 'alpha_0', 'alpha_1', 'alpha_2'] + self.__velocity_col = ['d' + col for col in self.__posorient_col] + self.__posorient_vel_col = self.__posorient_col + self.__posorient_vel_col.extend(self.__velocity_col) - def test_memfriendly(self): - """posorient is loaded if memory_friendly is False """ - agent = naviagent.AbstractAgent(self.mydb_filename, - memory_friendly=False) - self.assertTrue( - isinstance(agent.posorients, pd.DataFrame), - 'posorients should be a pd.DataFrame when memfriendly is false') - agent = naviagent.AbstractAgent(self.mydb_filename, - memory_friendly=True) - self.assertTrue( - agent.posorients is None, - 'posorients should be None when memfriendly is true') - - def test_abstractmove_inputs(self): - """abstractmove should TypeError if not pandas Series """ - agent = naviagent.AbstractAgent(self.mydb_filename, - memory_friendly=False) - with self.assertRaises(TypeError): - agent.abstractmove('NotPandasSeries') - - posorient_vel = pd.Series() - for col in ['x', 'y', 'z', 'alpha_0', 'alpha_1', 'alpha_2', - 'dx', 'dy', 'dz', 'dalpha_0', 'dalpha_1', 'dalpha_2']: - with self.assertRaises(KeyError): - agent.abstractmove(posorient_vel) - posorient_vel[col] = 2 - - def test_abstractmove_null_vellocity(self): - """abstractmove should leads to same point with null vel""" - agent = naviagent.AbstractAgent(self.mydb_filename, - memory_friendly=False) - - posorient_vel = pd.Series(data=0, - index=['x', 'y', 'z', - 'alpha_0', 'alpha_1', 'alpha_2', - 'dx', 'dy', 'dz', - 'dalpha_0', 'dalpha_1', 'dalpha_2']) - pos = self.mydb.read_posorient(rowid=1) - posorient_vel.loc[['x', 'y', 'z']] = pos.loc[['x', 'y', 'z']] - - newpos = agent.abstractmove(posorient_vel) - self.assertTrue(newpos.equals(posorient_vel), - 'Agent moved although velocity is null') + # + # AbstractAgent + # + def test_move_abstractagent(self): + agent = naviagent.AbstractAgent() + with self.assertRaises(NameError): + agent.move() + + def test_fly_abstractagent(self): + agent = naviagent.AbstractAgent() + with self.assertRaises(NameError): + agent.fly(max_nstep=10) # - # Single + # GridAgent # - def test_init_inputs(self): - initial_condition = 'A' - with self.assertRaises(TypeError): - naviagent.Single(self.mydb_filename, - initial_condition, - memory_friendly=False) - - initial_condition = pd.Series(index=['x'], data='a') - with self.assertRaises(TypeError): - naviagent.Single(self.mydb_filename, - initial_condition, - memory_friendly=False) + def test_move_gridagent(self): + agent = naviagent.GridAgent(self.mydb_filename) + initposorient = agent.db.posorients.loc[13, :] + initposovel = pd.Series(data=0, + index=self.__posorient_vel_col) + initposovel.loc[initposorient.index] = initposorient + agent.posorient = initposovel + agent.motion = lambda posorient, scene:\ + pd.Series(data=0, + index=self.__posorient_vel_col) + with self.assertRaises(AttributeError): + agent.move() + mode_move = {'mode': 'on_cubic_grid', + 'param': {'grid_spacing': + pd.Series(data=1, + index=['dx', 'dy', 'dz'])}} + agent.mode_of_motion = mode_move + agent.move() + obtained = agent.posorient + self.assertTrue(np.allclose( + obtained, initposorient.loc[obtained.index])) + + def test_fly_gridagent(self): + agent = naviagent.GridAgent(self.mydb_filename) + initposorient = agent.db.posorients.loc[13, :] + initposovel = pd.Series(data=0, + index=self.__posorient_vel_col) + initposovel.loc[initposorient.index] = initposorient + agent.posorient = initposovel + agent.motion = lambda posorient, scene:\ + pd.Series(data=0, + index=self.__posorient_vel_col) + with self.assertRaises(AttributeError): + agent.fly(max_nstep=10) + mode_move = {'mode': 'on_cubic_grid', + 'param': {'grid_spacing': + pd.Series(data=1, + index=['dx', 'dy', 'dz'])}} + agent.mode_of_motion = mode_move + agent.fly(max_nstep=10) + obtained = agent.posorient + print(obtained) + print(initposorient.loc[obtained.index]) + self.assertTrue(np.allclose(obtained, + initposorient.loc[obtained.index])) # - # Multi + # GraphAgent # - def test_init(self): - agent = naviagent.Multi(self.mydb_filename) + + def test_init_graphagent(self): + agent = naviagent.GraphAgent(self.mydb_filename) self.assertEqual(sorted(agent.graph.nodes), sorted(list(self.mydb.posorients.index)), 'Init of graph failed. Node missmatch') def test_graph_setter(self): - agent = naviagent.Multi(self.mydb_filename) + agent = naviagent.GraphAgent(self.mydb_filename) graph_nodes = list(agent.graph.nodes) graph_edges = list() for gnode in graph_nodes[1:]: @@ -113,7 +121,7 @@ class TestNavipyMovingAgent(unittest.TestCase): 3 Two loops attractors """ # Test all node to first - agent = naviagent.Multi(self.mydb_filename) + agent = naviagent.GraphAgent(self.mydb_filename) graph_nodes = list(agent.graph.nodes) graph_edges = list() @@ -175,7 +183,7 @@ class TestNavipyMovingAgent(unittest.TestCase): 2. Saddle points 3. Local minima """ - agent = naviagent.Multi(self.mydb_filename) + agent = naviagent.GraphAgent(self.mydb_filename) # Local maxima graph_nodes = list(agent.graph.nodes) graph_edges = list() diff --git a/build/lib/navipy/rendering/cyber_bee.py b/build/lib/navipy/rendering/cyber_bee.py index 9c203e55a30fae9d6fdc3189894cf868aa336e1f..530a11b38698a1608a72c506c7e711aeed2ca974 100644 --- a/build/lib/navipy/rendering/cyber_bee.py +++ b/build/lib/navipy/rendering/cyber_bee.py @@ -376,6 +376,20 @@ class Cyberbee(): distance = distance[:, :, 0] return distance + def scene(self, posorient): + """ update position orientation and return a RGBD image + + :param posorient: is a 1x6 vector containing: + 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 + """ + self.update(posorient) + image = self.image + image[:, :, 3] = self.distance + return image + if __name__ == "__main__": # Initiate the Cyberbee diff --git a/navipy.egg-info/SOURCES.txt b/navipy.egg-info/SOURCES.txt index f0f931e235dddb99eb6045a59229abd5918b4d7e..fac8f815f529879e696ebbd5fe66152fd9f0625e 100644 --- a/navipy.egg-info/SOURCES.txt +++ b/navipy.egg-info/SOURCES.txt @@ -1,3 +1,4 @@ +README.md setup.py navipy/__init__.py navipy/python_test.py @@ -12,6 +13,7 @@ navipy/database/__init__.py navipy/database/tools.py navipy/moving/__init__.py navipy/moving/agent.py +navipy/moving/agent_old.py navipy/moving/maths.py navipy/moving/test_agent.py navipy/moving/test_maths.py diff --git a/navipy/database/__init__.py b/navipy/database/__init__.py index 2df843460a1acdec657782cbac74091d54d05847..cd9019245cb2e5c807b10646f5b2d70cb9967c14 100644 --- a/navipy/database/__init__.py +++ b/navipy/database/__init__.py @@ -403,7 +403,17 @@ database toreturn = toreturn.astype(float) return toreturn - def read_image(self, posorient=None, rowid=None): + def scene(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 + """ + if not isinstance(posorient, pd.Series): ('posorient should be a pandas Series') if posorient is not None: @@ -436,15 +446,6 @@ database raise Exception('posorient and rowid can not be both None') if posorient is not None: rowid = self.get_posid(posorient) - """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 - """ if (posorient is None) and (rowid is None): Exception('posorient and rowid can not be both None') if posorient is not None: @@ -472,7 +473,9 @@ database cmaxminrange.name = cmaxminrange.id cmaxminrange.drop('id') cmaxminrange = cmaxminrange.astype(float) - return self.denormalise_image(image, cmaxminrange) + toreturn = self.denormalise_image(image, cmaxminrange) + toreturn = toreturn[..., np.newaxis] + return toreturn def denormalise_image(self, image, cmaxminrange): if len(image.shape) != 3: diff --git a/navipy/moving/agent.py b/navipy/moving/agent.py index e12f39cc4038da280d3d37466ad714bc185932ad..f6f10f98d5e558e4d75193b81a698a4b33710e8e 100644 --- a/navipy/moving/agent.py +++ b/navipy/moving/agent.py @@ -21,64 +21,64 @@ def defaultcallback(*args, **kwargs): class AbstractAgent(): def __init__(self): - self.__sensors = defaultcallback - self.__motion = defaultcallback - self.__alter_posorientvel = defaultcallback - self.__posorient_col = ['x', 'y', 'z', - 'alpha_0', 'alpha_1', 'alpha_2'] - self.__velocity_col = ['d' + col for col in self.__posorient_col] - self.__posorient_vel_col = self.__posorient_col.append( - self.__velocity_col) - self.__posorient_vel = pd.Series( - index=self.__posorient_vel_col, + self._sensors = defaultcallback + self._motion = defaultcallback + self._alter_posorientvel = defaultcallback + self._posorient_col = ['x', 'y', 'z', + 'alpha_0', 'alpha_1', 'alpha_2'] + self._velocity_col = ['d' + col for col in self._posorient_col] + self._posorient_vel_col = self._posorient_col.copy() + self._posorient_vel_col.extend(self._velocity_col) + self._posorient_vel = pd.Series( + index=self._posorient_vel_col, data=np.nan) @property def posorient(self): - return self.__posorient_vel.loc[self.__posorient_col].copy() + return self._posorient_vel.loc[self._posorient_col].copy() @posorient.setter def posorient(self, posorient): if isinstance(posorient, pd.Series) is False: raise TypeError('posorient should be a pandas Series') - for col in self.__posorient_col: + for col in self._posorient_col: if col not in posorient.index: raise KeyError( 'posorient should have {} as index'.format(col)) - self.__posorient_vel.loc[self.__posorient_col] = \ - posorient.loc[self.__posorient_col] + self._posorient_vel.loc[self._posorient_col] = \ + posorient.loc[self._posorient_col] @property def velocity(self): - return self.__posorient_vel.loc[self.__velocity_col].copy() + return self._posorient_vel.loc[self._velocity_col].copy() @velocity.setter def velocity(self, velocity): if isinstance(velocity, pd.Series) is False: raise TypeError('velocity should be a pandas Series') - for col in self.__velocity_col: + for col in self._velocity_col: if col not in velocity.index: raise KeyError( 'velocity should have {} as index'.format(col)) - self.__posorient_vel.loc[self.__velocity_col] = \ - velocity.loc[self.__velocity_col] + self._posorient_vel.loc[self._velocity_col] = \ + velocity.loc[self._velocity_col] @property def motion(self): - return inspect.getsourcelines(self.__motion) + return inspect.getsourcelines(self._motion) @property def sensors(self): - return inspect.getsourcelines(self.__sensors) + return inspect.getsourcelines(self._sensors) @property def alter_posorientvel(self): - return inspect.getsourcelines(self.__alter_posorientvel) + return inspect.getsourcelines(self._alter_posorientvel) def move(self): - scene = self.__sensors(self.posorient) - newpos = self.__motion(self.posorient, scene) - alteredpos = self.__alter_posorientvel(newpos) + scene = self._sensors(self.posorient) + newpos = self._motion(self.posorient, scene) + alteredpos = self._alter_posorientvel(newpos) self.posorient = alteredpos self.velocity = alteredpos @@ -88,11 +88,12 @@ class AbstractAgent(): if return_tra: trajectory = pd.DataFrame(index=range(0, max_nstep), columns=self.posorient_vel_col) - trajectory.loc[0, :] = self.__posorient_col.copy() + trajectory.loc[0, :] = self._posorient_vel.copy() for stepi in range(1, max_nstep): + print(stepi, self._posorient_vel) self.move() if return_tra: - trajectory.loc[stepi, :] = self.__posorient_col.copy() + trajectory.loc[stepi, :] = self._posorient_vel.copy() if return_tra: return trajectory else: @@ -118,18 +119,18 @@ class CyberBeeAgent(AbstractAgent): def __init__(self, cyberbee): AbstractAgent.__init__(self) - AbstractAgent.__alter_posorientvel = \ + AbstractAgent._alter_posorientvel = \ lambda motion_vec: navimomath.next_pos(motion_vec, move_mode='free_run') self.sensors = cyberbee.scene @AbstractAgent.sensors.setter def sensors(self, cyberbee): - self.__sensors = cyberbee.scene + self._sensors = cyberbee.scene @AbstractAgent.motion.setter def motion(self, motion): - self.__motion = motion + self._motion = motion class GridAgent(AbstractAgent, Process): @@ -160,27 +161,27 @@ class GridAgent(AbstractAgent, Process): if (posorients_queue is not None) and (results_queue is not None): multiprocessing.Process.__init__(self) AbstractAgent.__init__(self) - AbstractAgent.__alter_posorientvel = self.snap_to_grid + self._alter_posorientvel = self.snap_to_grid self.sensors = database_filename @AbstractAgent.sensors.setter def sensors(self, database_filename): self.db = DataBaseLoad(database_filename) - self.__posorients = self.db.posorients() - self.__sensors = self.db.scene + self._posorients = self.db.posorients + self._sensors = self.db.scene @AbstractAgent.motion.setter def motion(self, motion): - self.__motion = motion + self._motion = motion @property def mode_of_motion(self): """ """ - toreturn = self.__mode_move + toreturn = self._mode_move toreturn['describe'] = \ navimomath.mode_moves_supported()[ - self.__mode_move['mode']]['describe'] + self._mode_move['mode']]['describe'] return toreturn @mode_of_motion.setter @@ -200,43 +201,59 @@ class GridAgent(AbstractAgent, Process): if param not in mode['param']: raise KeyError( "'{}' is not in mode['param']".format(param)) - self.__mode_move = mode + self._mode_move = mode else: raise ValueError('mode is not supported') def snap_to_grid(self, posorient_vel): posorient_vel = navimomath.next_pos( posorient_vel, - move_mode=self.__mode_move['mode'], - move_param=self.__mode_move['param']) + move_mode=self._mode_move['mode'], + move_param=self._mode_move['param']) tmp = navimomath.closest_pos( - posorient_vel, self.__posorients) - posorient_vel[['x', 'y', 'z', - 'alpha_0', 'alpha_1', 'alpha_2']] = tmp + posorient_vel, self._posorients) + posorient_vel.loc[self._posorient_col] = \ + tmp.loc[self._posorient_col] posorient_vel.name = tmp.name return posorient_vel + def move(self): + if hasattr(self, '_mode_move'): + AbstractAgent.move(self) + else: + raise AttributeError( + 'GridAgent object has no attribute _mode_move\n' + + 'Please set the mode of motion') + + def fly(self, max_nstep, return_tra=False): + if hasattr(self, '_mode_move'): + return AbstractAgent.fly(self, max_nstep, return_tra) + else: + raise AttributeError( + 'GridAgent object has no attribute _mode_move\n' + + 'Please set the mode of motion') + def run(self): """ Only supported when multiprocess""" - if self.__posorients_queue is None or self.__results_queue is None: + if self._posorients_queue is None or self._results_queue is None: raise NameError('Single agent class has not be inititialised ' + 'with multiprocessing suppport') proc_name = self.name print('Process {} started'.format(proc_name)) while True: - start_posorient = self.__posorients_queue.get(timeout=1) + start_posorient = self._posorients_queue.get(timeout=1) if start_posorient is None: # Poison pill means shutdown) break common_id = list(set(start_posorient.index).intersection( - self.__posorientvel.index)) - self.__posorientvel.loc[common_id] = start_posorient.loc[common_id] + self._posorientvel.index)) + self._posorientvel.loc[common_id] = start_posorient.loc[common_id] self.move() - next_posorient = self.__posorientvel + next_posorient = self._posorientvel - self.__posorients_queue.task_done() - self.__results_queue.put((start_posorient, next_posorient)) - self.__posorients_queue.task_done() + self._posorients_queue.task_done() + self._results_queue.put((start_posorient, next_posorient)) + self._posorients_queue.task_done() print('Process {} done'.format(proc_name)) @@ -272,21 +289,21 @@ class GraphAgent(): def __init__(self, database_filename): self.db = DataBaseLoad(database_filename) # Init the graph - self.__graph = nx.DiGraph() + self._graph = nx.DiGraph() for row_id, posor in self.db.posorients.iterrows(): posor.name = row_id - self.__graph.add_node(row_id, - posorient=posor) + self._graph.add_node(row_id, + posorient=posor) @property def graph(self): - return self.__graph + return self._graph @graph.setter def graph(self, graph): if isinstance(graph, nx.DiGraph) is False: raise TypeError('graph is not a nx.DiGraph') - self.__graph = graph.copy() + self._graph = graph.copy() self.check_graph() def build_graph(self, callback_function, @@ -296,8 +313,8 @@ class GraphAgent(): results_edges = [] posorients_queue = JoinableQueue() results_queue = Queue() - for node in self.__graph.nodes: - posorients_queue.put(self.__graph.nodes[node]['posorient']) + for node in self._graph.nodes: + posorients_queue.put(self._graph.nodes[node]['posorient']) # Start ndatabase loader num_agents = ncpu @@ -316,21 +333,21 @@ class GraphAgent(): # Wait for all of the tasks to finish # posorients_queue.join() - for _ in range(nx.number_of_nodes(self.__graph)): + for _ in range(nx.number_of_nodes(self._graph)): result = results_queue.get(timeout=timeout) results_edges.append((result[0].name, result[1].name)) # print(results_edges[-1]) - self.__graph.add_edges_from(results_edges) + self._graph.add_edges_from(results_edges) self.check_graph() def check_graph(self): self.check_single_target() def check_single_target(self): - for node in self.__graph.nodes: + for node in self._graph.nodes: # not connected -> loop not ran - for count, _ in enumerate(self.__graph.neighbors(node)): + for count, _ in enumerate(self._graph.neighbors(node)): # count == 0 -> connected to one node # count == 1 -> connected to two nodes if count > 0: @@ -341,7 +358,7 @@ class GraphAgent(): """Return a list of node going to each attractor in a graph """ attractors = list() - for attractor in nx.attracting_components(self.__graph): + for attractor in nx.attracting_components(self._graph): att = dict() att['attractor'] = attractor attractors.append(att) @@ -399,13 +416,13 @@ class GraphAgent(): def reach_goals(self, goals): """ Return all paths to the goals """ - return nx.shortest_path(self.__graph, target=goals) + return nx.shortest_path(self._graph, target=goals) def neighboring_nodes(self, target): """ Return the nodes going to the target """ # Reverse graph because nx.neighbors give the end node # and we want to find the start node going to target # not where target goes. - tmpgraph = self.__graph.reverse(copy=True) + tmpgraph = self._graph.reverse(copy=True) neighbors = tmpgraph.neighbors(target) return neighbors diff --git a/navipy/moving/test_agent.py b/navipy/moving/test_agent.py index 8775361fbcede6c9ee87feb54c9d41bd8ca496c5..5f73a7a0e743a0cfba81b0dffd9b85c43f2e7dfe 100644 --- a/navipy/moving/test_agent.py +++ b/navipy/moving/test_agent.py @@ -1,5 +1,5 @@ """ -Test of maths +Test of agent """ import numpy as np import pandas as pd @@ -17,78 +17,86 @@ class TestNavipyMovingAgent(unittest.TestCase): self.mydb_filename = pkg_resources.resource_filename( 'navipy', 'resources/database.db') self.mydb = navidb.DataBaseLoad(self.mydb_filename) + self.__posorient_col = ['x', 'y', 'z', + 'alpha_0', 'alpha_1', 'alpha_2'] + self.__velocity_col = ['d' + col for col in self.__posorient_col] + self.__posorient_vel_col = self.__posorient_col + self.__posorient_vel_col.extend(self.__velocity_col) - def test_memfriendly(self): - """posorient is loaded if memory_friendly is False """ - agent = naviagent.AbstractAgent(self.mydb_filename, - memory_friendly=False) - self.assertTrue( - isinstance(agent.posorients, pd.DataFrame), - 'posorients should be a pd.DataFrame when memfriendly is false') - agent = naviagent.AbstractAgent(self.mydb_filename, - memory_friendly=True) - self.assertTrue( - agent.posorients is None, - 'posorients should be None when memfriendly is true') - - def test_abstractmove_inputs(self): - """abstractmove should TypeError if not pandas Series """ - agent = naviagent.AbstractAgent(self.mydb_filename, - memory_friendly=False) - with self.assertRaises(TypeError): - agent.abstractmove('NotPandasSeries') - - posorient_vel = pd.Series() - for col in ['x', 'y', 'z', 'alpha_0', 'alpha_1', 'alpha_2', - 'dx', 'dy', 'dz', 'dalpha_0', 'dalpha_1', 'dalpha_2']: - with self.assertRaises(KeyError): - agent.abstractmove(posorient_vel) - posorient_vel[col] = 2 - - def test_abstractmove_null_vellocity(self): - """abstractmove should leads to same point with null vel""" - agent = naviagent.AbstractAgent(self.mydb_filename, - memory_friendly=False) - - posorient_vel = pd.Series(data=0, - index=['x', 'y', 'z', - 'alpha_0', 'alpha_1', 'alpha_2', - 'dx', 'dy', 'dz', - 'dalpha_0', 'dalpha_1', 'dalpha_2']) - pos = self.mydb.read_posorient(rowid=1) - posorient_vel.loc[['x', 'y', 'z']] = pos.loc[['x', 'y', 'z']] - - newpos = agent.abstractmove(posorient_vel) - self.assertTrue(newpos.equals(posorient_vel), - 'Agent moved although velocity is null') + # + # AbstractAgent + # + def test_move_abstractagent(self): + agent = naviagent.AbstractAgent() + with self.assertRaises(NameError): + agent.move() + + def test_fly_abstractagent(self): + agent = naviagent.AbstractAgent() + with self.assertRaises(NameError): + agent.fly(max_nstep=10) # - # Single + # GridAgent # - def test_init_inputs(self): - initial_condition = 'A' - with self.assertRaises(TypeError): - naviagent.Single(self.mydb_filename, - initial_condition, - memory_friendly=False) - - initial_condition = pd.Series(index=['x'], data='a') - with self.assertRaises(TypeError): - naviagent.Single(self.mydb_filename, - initial_condition, - memory_friendly=False) + def test_move_gridagent(self): + agent = naviagent.GridAgent(self.mydb_filename) + initposorient = agent.db.posorients.loc[13, :] + initposovel = pd.Series(data=0, + index=self.__posorient_vel_col) + initposovel.loc[initposorient.index] = initposorient + agent.posorient = initposovel + agent.motion = lambda posorient, scene:\ + pd.Series(data=0, + index=self.__posorient_vel_col) + with self.assertRaises(AttributeError): + agent.move() + mode_move = {'mode': 'on_cubic_grid', + 'param': {'grid_spacing': + pd.Series(data=1, + index=['dx', 'dy', 'dz'])}} + agent.mode_of_motion = mode_move + agent.move() + obtained = agent.posorient + self.assertTrue(np.allclose( + obtained, initposorient.loc[obtained.index])) + + def test_fly_gridagent(self): + agent = naviagent.GridAgent(self.mydb_filename) + initposorient = agent.db.posorients.loc[13, :] + initposovel = pd.Series(data=0, + index=self.__posorient_vel_col) + initposovel.loc[initposorient.index] = initposorient + agent.posorient = initposovel + agent.motion = lambda posorient, scene:\ + pd.Series(data=0, + index=self.__posorient_vel_col) + with self.assertRaises(AttributeError): + agent.fly(max_nstep=10) + mode_move = {'mode': 'on_cubic_grid', + 'param': {'grid_spacing': + pd.Series(data=1, + index=['dx', 'dy', 'dz'])}} + agent.mode_of_motion = mode_move + agent.fly(max_nstep=10) + obtained = agent.posorient + print(obtained) + print(initposorient.loc[obtained.index]) + self.assertTrue(np.allclose(obtained, + initposorient.loc[obtained.index])) # - # Multi + # GraphAgent # - def test_init(self): - agent = naviagent.Multi(self.mydb_filename) + + def test_init_graphagent(self): + agent = naviagent.GraphAgent(self.mydb_filename) self.assertEqual(sorted(agent.graph.nodes), sorted(list(self.mydb.posorients.index)), 'Init of graph failed. Node missmatch') def test_graph_setter(self): - agent = naviagent.Multi(self.mydb_filename) + agent = naviagent.GraphAgent(self.mydb_filename) graph_nodes = list(agent.graph.nodes) graph_edges = list() for gnode in graph_nodes[1:]: @@ -113,7 +121,7 @@ class TestNavipyMovingAgent(unittest.TestCase): 3 Two loops attractors """ # Test all node to first - agent = naviagent.Multi(self.mydb_filename) + agent = naviagent.GraphAgent(self.mydb_filename) graph_nodes = list(agent.graph.nodes) graph_edges = list() @@ -175,7 +183,7 @@ class TestNavipyMovingAgent(unittest.TestCase): 2. Saddle points 3. Local minima """ - agent = naviagent.Multi(self.mydb_filename) + agent = naviagent.GraphAgent(self.mydb_filename) # Local maxima graph_nodes = list(agent.graph.nodes) graph_edges = list()