diff --git a/pyrevolve/revolve_bot/brain_nn.py b/pyrevolve/revolve_bot/brain_nn.py index 0b0699d8d8..929c315afa 100644 --- a/pyrevolve/revolve_bot/brain_nn.py +++ b/pyrevolve/revolve_bot/brain_nn.py @@ -44,6 +44,8 @@ def FromYaml(yaml_object): params.generate_params(yaml_object['params'][k_node]) brain.params[k_node] = params + return brain + def to_yaml(self): yaml_dict_brain = OrderedDict() diff --git a/pyrevolve/revolve_bot/measure.py b/pyrevolve/revolve_bot/measure.py index b6a9ccb6da..8f65654e57 100644 --- a/pyrevolve/revolve_bot/measure.py +++ b/pyrevolve/revolve_bot/measure.py @@ -41,7 +41,8 @@ def count_branching_bricks(self, module=None): for core_slot, child_module in module.iter_children(): if child_module is None: continue - children_count += 1 + if not isinstance(child_module, TouchSensorModule) and not isinstance(child_module, BrickSensorModule): + children_count += 1 self.count_branching_bricks(child_module) if (isinstance(module, BrickModule) and children_count == 3) or (isinstance(module, CoreModule) and children_count == 4): self.branching_modules_count += 1 @@ -70,25 +71,27 @@ def measure_branching(self): def calculate_extremities_extensiveness(self, module=None, extremities=False, extensiveness=False): """ Calculate extremities or extensiveness in body + @param extremities: calculate extremities in body if true + @param extensiveness: calculate extensiveness in body if true """ try: if module is None: module = self.body - if module.has_children(): - children_count = 0 - for core_slot, child_module in module.iter_children(): - if child_module is None: - continue + children_count = 0 + for core_slot, child_module in module.iter_children(): + if child_module is None: + continue + if not isinstance(child_module, TouchSensorModule): children_count += 1 - if extremities: - self.calculate_extremities_extensiveness(child_module, True, False) - if extensiveness: - self.calculate_extremities_extensiveness(child_module, False, True) - if children_count == 1 and not isinstance(module, CoreModule) and extremities: - self.extremities += 1 - if children_count == 2 and not isinstance(module, CoreModule) and extensiveness: - self.extensiveness += 1 + if extremities: + self.calculate_extremities_extensiveness(child_module, True, False) + if extensiveness: + self.calculate_extremities_extensiveness(child_module, False, True) + if children_count == 0 and not (isinstance(module, CoreModule) or isinstance(module, TouchSensorModule)) and extremities: + self.extremities += 1 + if children_count == 1 and not (isinstance(module, CoreModule) or isinstance(module, TouchSensorModule)) and extensiveness: + self.extensiveness += 1 except Exception as e: print('Failed calculating extremities or extensiveness') print('Exception: {}'.format(e)) @@ -104,7 +107,7 @@ def measure_limbs(self): if self.absolute_size < 6: practical_limit_limbs = self.absolute_size - 1 else: - practical_limit_limbs = 2 * math.floor((self.absolute_size-6)/3) + (self.absolute_size - 6) % 3 + 4 + practical_limit_limbs = 2 * math.floor((self.absolute_size - 6) / 3) + ((self.absolute_size - 6) % 3) + 4 self.calculate_extremities_extensiveness(None, True, False) if self.extremities == 0: self.limbs = 0 @@ -181,7 +184,6 @@ def count_active_hinges(self, module=None): try: if module is None: module = self.body - if module.has_children(): if isinstance(module, ActiveHingeModule): self.active_hinges_count += 1 @@ -204,7 +206,10 @@ def measure_joints(self): self.joints = 0 return 0 self.count_active_hinges() - practical_limit_active_hinges = math.floor((self.absolute_size-1)/2) + practical_limit_active_hinges = self.absolute_size - 2 + if self.active_hinges_count == 0: + self.joints = 0 + return 0 self.joints = self.active_hinges_count / practical_limit_active_hinges return self.joints @@ -227,8 +232,9 @@ def measure_absolute_size(self, module=None): :return: """ try: - self.calculate_count() - self.absolute_size = self.brick_count + self.hinge_count + 1 + if self.absolute_size is None: + self.calculate_count() + self.absolute_size = self.brick_count + self.hinge_count + 1 return self.absolute_size except Exception as e: print('Failed measuring absolute size') @@ -298,9 +304,9 @@ def measure_all(self): self.measure_coverage() self.measure_symmetry() self.measure_branching() - return self.get_all_measurements() + return self.measurement_to_dict() - def get_all_measurements(self): + def measurement_to_dict(self): """ Return dict of all measurements :return: @@ -309,7 +315,9 @@ def get_all_measurements(self): 'branching': self.branching, 'branching_modules_count': self.branching_modules_count, 'limbs': self.limbs, + 'extremeties': self.extremities, 'length_of_limbs': self.length_of_limbs, + 'extensiveness': self.extensiveness, 'coverage': self.coverage, 'joints': self.joints, 'hinge_count': self.hinge_count, diff --git a/pyrevolve/revolve_bot/render/brain_graph.py b/pyrevolve/revolve_bot/render/brain_graph.py new file mode 100644 index 0000000000..4e86bbe48f --- /dev/null +++ b/pyrevolve/revolve_bot/render/brain_graph.py @@ -0,0 +1,60 @@ +from graphviz import Digraph, render +# belong to TODO +import fnmatch + + +class BrainGraph: + def __init__(self, brain, name='brain', typename='brain'): + self.graph = Digraph(typename, filename=name, format='png') + self.brain = brain + + def add_node(self, node_id, node_type, text): + """ + Add node to graph + @param node_id: id of node + @param node_type: type of node + @param text: text to show inside node + """ + if node_type == 'Input': + self.graph.attr('node', shape='circle') + elif node_type == 'Oscillator': + self.graph.attr('node', shape='square') + self.graph.node(node_id, label=text) + + def add_edge(self, source_id, desitnation_id, label): + """ + Add edge to graph + @param source_id: id of source node + @param destination_id: id of destination node + @param label: label of edge + """ + self.graph.edge(source_id, desitnation_id, label) + + def save_graph(self): + """ + Save graph + """ + self.graph.render() + + def brain_to_graph(self): + """ + Export complete brain to graph + """ + + nodes = self.brain.nodes + params = self.brain.params + # belongs to TODO + duplicates = fnmatch.filter(nodes, 'node*-*') + for node in nodes: + # TODO REMOVE condition WHEN duplicated nodes bug is fixed -- duplicated nodes end in '-[0-9]+' or '-core[0-9]+' (node2-2, node2-core1) + if node not in duplicates: + node_id = nodes[node].id + text = node_id + if node_id in params: + param = params[node_id] + text += '\n Oscillator {0} \n period: {1} \n phase_offset: {2} \n amplitude: {3}'.format( + nodes[node].part_id, params[node_id].period, params[node_id].phase_offset, params[node_id].amplitude) + self.add_node(node_id, nodes[node].type, text) + + for connection in self.brain.connections: + self.add_edge(str(connection.src), str(connection.dst), str(connection.weight)) diff --git a/pyrevolve/revolve_bot/render/grid.py b/pyrevolve/revolve_bot/render/grid.py index dd43eb8d39..02c3eb8891 100644 --- a/pyrevolve/revolve_bot/render/grid.py +++ b/pyrevolve/revolve_bot/render/grid.py @@ -130,10 +130,11 @@ def move_back(self): Grid.y_pos = last_movement[1] Grid.orientation = last_movement[2] - def add_to_visited(self): + def add_to_visited(self, include_sensors=True, is_sensor=False): """Add current position to visited coordinates list""" self.calculate_orientation() - self.visited_coordinates.append([Grid.x_pos, Grid.y_pos]) + if (include_sensors and is_sensor) or not is_sensor: + self.visited_coordinates.append([Grid.x_pos, Grid.y_pos]) Grid.movement_stack.append([Grid.x_pos, Grid.y_pos, Grid.orientation]) def calculate_grid_dimensions(self): diff --git a/pyrevolve/revolve_bot/render/render.py b/pyrevolve/revolve_bot/render/render.py index a2994cdd01..149c607969 100644 --- a/pyrevolve/revolve_bot/render/render.py +++ b/pyrevolve/revolve_bot/render/render.py @@ -53,9 +53,7 @@ def traverse_path_of_robot(self, module, slot, include_sensors=True): """ if isinstance(module, ActiveHingeModule) or isinstance(module, BrickModule) or isinstance(module, TouchSensorModule) or isinstance(module, BrickSensorModule): self.grid.move_by_slot(slot) - if include_sensors or (isinstance(module, ActiveHingeModule) or isinstance(module, BrickModule)): - self.grid.add_to_visited() - + self.grid.add_to_visited(include_sensors, isinstance(module, TouchSensorModule)) if module.has_children(): # Traverse path of children of module for core_slot, child_module in module.iter_children(): diff --git a/pyrevolve/revolve_bot/revolve_bot.py b/pyrevolve/revolve_bot/revolve_bot.py index 44dba25344..77f4b55cb5 100644 --- a/pyrevolve/revolve_bot/revolve_bot.py +++ b/pyrevolve/revolve_bot/revolve_bot.py @@ -16,6 +16,7 @@ from .brain_nn import BrainNN from .render.render import Render +from .render.brain_graph import BrainGraph from .measure import Measure import xml.etree.ElementTree @@ -118,8 +119,7 @@ def load_yaml(self, text): brain_type = yaml_brain['type'] if brain_type == 'neural-network': - self._brain = BrainNN() - self._brain.FromYaml(yaml_brain) + self._brain = BrainNN.FromYaml(yaml_brain) else: self._brain = None @@ -252,6 +252,21 @@ def _update_substrate(self, new_direction, substrate_coordinates_map) + def render_brain(self, img_path): + """ + Render image of brain + @param img_path: path to where to store image + """ + if self._brain == None: + raise RuntimeError('Brain not initialized') + else: + try: + brain_graph = BrainGraph(self._brain, img_path) + brain_graph.brain_to_graph() + brain_graph.save_graph() + except: + print('Failed rendering brain') + def render2d(self, img_path): """ Render 2d representation of robot and store as png diff --git a/requirements.txt b/requirements.txt index a719f76b9f..7305a47fbe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,5 +6,6 @@ PyYAML>=3.11 protobuf>=3.0.0 psutil==3.4.2 pycairo>=1.18.0 +graphviz>=0.10.1 -e git+https://github.com/ci-group/pygazebo.git@py2to3#egg=pygazebo