Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/model_treenode.py
diff options
context:
space:
mode:
authorThomas Jourdan <b.vehikel@googlemail.com>2009-12-06 12:37:03 (GMT)
committer Thomas Jourdan <b.vehikel@googlemail.com>2009-12-06 12:37:03 (GMT)
commitbcde11455168a07de8a3b17f2a4d77ce8931e75d (patch)
treea5ba40242065b6c68940311f0d5335ab4656baa0 /model_treenode.py
parent572b826a4446ce3d44a57f4d29a9edeca086df42 (diff)
Layers are now arranged as a tree data structure.
Diffstat (limited to 'model_treenode.py')
-rw-r--r--model_treenode.py369
1 files changed, 369 insertions, 0 deletions
diff --git a/model_treenode.py b/model_treenode.py
new file mode 100644
index 0000000..9a51cd6
--- /dev/null
+++ b/model_treenode.py
@@ -0,0 +1,369 @@
+# coding: UTF8
+# Copyright 2009 Thomas Jourdan
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import cairo
+import random
+import ka_debug
+import ka_factory
+import model_random
+import model_constraintpool
+import model_locus
+import model_allele
+import exon_color
+
+LAYERTYPE_CONSTRAINT = 'layertypeconstraint'
+NUMBER_OF_LAYERS_CONSTRAINT = 'layernumberofconstraint'
+MERGERTYPE_CONSTRAINT = 'mergertypeconstraint'
+MODIFIERTYPE_CONSTRAINT = 'modifiertypeconstraint'
+
+class TreeNode(model_allele.Allele):
+ """
+ inv: (self.left_treenode is None) or isinstance(self.left_treenode, TreeNode)
+ inv: (self.right_treenode is None) or isinstance(self.right_treenode, TreeNode)
+ inv: self.layer is not None
+ inv: self.merger is not None
+ inv: self.modifier is not None
+ """
+
+ cdef = [{'bind' : LAYERTYPE_CONSTRAINT,
+ 'name' : 'Permitted layer types',
+ 'domain': model_constraintpool.STRING_M_OF_N,
+ 'enum' : ka_factory.get_factory('layer').keys()
+ },
+ {'bind' : NUMBER_OF_LAYERS_CONSTRAINT,
+ 'name' : 'Number of layers',
+ 'domain': model_constraintpool.INT_RANGE,
+ 'min' : 1, 'max': 4,
+ },
+ {'bind' : MERGERTYPE_CONSTRAINT,
+ 'name' : 'Permitted merging strategies for layers',
+ 'domain': model_constraintpool.STRING_M_OF_N,
+ 'enum' : ka_factory.get_factory('merger').keys()
+ },
+ {'bind' : MODIFIERTYPE_CONSTRAINT,
+ 'name' : 'Permitted merging strategies for layers',
+ 'domain': model_constraintpool.STRING_M_OF_N,
+ 'enum' : ka_factory.get_factory('modifier').keys()
+ },
+ ]
+
+ def __init__(self, trunk):
+ super(TreeNode, self).__init__(trunk)
+ self.left_treenode = None
+ self.right_treenode = None
+ self.left_background = exon_color.Color(self.path, 0, 0, 0, 1)
+ self.right_background = exon_color.Color(self.path, 0, 0, 0, 1)
+ layer_factory = ka_factory.get_factory('layer')
+ self.layer = layer_factory.create(layer_factory.keys()[0], self.path)
+ merger_factory = ka_factory.get_factory('merger')
+ self.merger = merger_factory.create(merger_factory.keys()[0], self.path)
+ modifier_factory = ka_factory.get_factory('modifier')
+ self.modifier = modifier_factory.create(modifier_factory.keys()[0], self.path)
+
+ def dot(self):
+ result = ""
+ anchor = ka_debug.dot_id(self) + ' -> '
+ for ref in [self.left_treenode, self.right_treenode,
+ self.layer, self.merger, self.modifier,
+ self.left_background, self.right_background]:
+ if ref is not None:
+ result += ka_debug.dot_ref(anchor, ref)
+ return result
+
+ @staticmethod
+ def _paint_checker_board(ctx):
+ """Paint a checker board background"""
+ steps = 16
+ delta = (1.0) / (1.0 * steps)
+ ctx.set_operator(cairo.OPERATOR_SOURCE)
+ for row in range(steps):
+ for col in range(steps):
+ ctx.rectangle(col * delta - 0.5, row * delta - 0.5,
+ delta, delta)
+ if (col + row) % 2 == 0:
+ ctx.set_source_rgb(0.4, 0.4, 0.4)
+ else:
+ ctx.set_source_rgb(0.6, 0.6, 0.6)
+ ctx.fill()
+
+ def __eq__(self, other):
+ """Equality based on layers quantity and content."""
+ equal = isinstance(other, TreeNode) \
+ and self.left_treenode == other.left_treenode \
+ and self.right_treenode == other.right_treenode \
+ and self.left_background == other.left_background \
+ and self.right_background == other.right_background \
+ and self.layer == other.layer \
+ and self.merger == other.merger \
+ and self.modifier == other.modifier
+ return equal
+
+ def randomize(self):
+ """Randomize the tree nodes components.
+ post: self.layer is not None
+ """
+ cpool = model_constraintpool.ConstraintPool.get_pool()
+
+ self.left_background.randomize()
+ self.right_background.randomize()
+
+ # create layer
+ layer_factory = ka_factory.get_factory('layer')
+ layertype_constraint = cpool.get(self, LAYERTYPE_CONSTRAINT)
+ self.layer = layer_factory.create_random(layertype_constraint,
+ self.path)
+ self.layer.randomize()
+
+ # create strategy for merging 'left' and 'right' layer
+ merger_factory = ka_factory.get_factory('merger')
+ mergertype_constraint = cpool.get(self, MERGERTYPE_CONSTRAINT)
+ self.merger = merger_factory.create_random(mergertype_constraint,
+ self.path)
+ self.merger.randomize()
+
+ # create strategy for modifying stacked layers
+ modifier_factory = ka_factory.get_factory('modifier')
+ modifiertype_constraint = cpool.get(self, MODIFIERTYPE_CONSTRAINT)
+ self.modifier = modifier_factory.create_random(modifiertype_constraint,
+ self.path)
+ self.modifier.randomize()
+
+ number_of_constraint = cpool.get(self, NUMBER_OF_LAYERS_CONSTRAINT)
+ depth = _count_slash(self.path)
+ if (depth <= number_of_constraint[0] or \
+ (depth > number_of_constraint[0] and random.choice([False, True]))) \
+ and depth <= number_of_constraint[1]:
+ self.left_treenode = TreeNode(self.path)
+ self.left_treenode.path += 'Left'
+ self.left_treenode.randomize()
+ if (depth <= number_of_constraint[0] or \
+ (depth > number_of_constraint[0] and random.choice([False, True]))) \
+ and depth <= number_of_constraint[1]:
+ self.right_treenode = TreeNode(self.path)
+ self.right_treenode.path += 'Right'
+ self.right_treenode.randomize()
+
+
+ def mutate(self):
+ """Make random changes to the tree node.
+ """
+ cpool = model_constraintpool.ConstraintPool.get_pool()
+
+ # delegate mutating to the nodes child components
+ if self.left_treenode is not None:
+ self.left_treenode.mutate()
+ if self.right_treenode is not None:
+ self.right_treenode.mutate()
+ # mutating my details
+ self.left_background.mutate()
+ self.right_background.mutate()
+ if model_random.is_mutating():
+ self.layer.mutate()
+ if model_random.is_mutating():
+ if model_random.is_mutating():
+ merger_factory = ka_factory.get_factory('merger')
+ mergertype_constraint = cpool.get(self, MERGERTYPE_CONSTRAINT)
+ self.merger = merger_factory.create_random(mergertype_constraint,
+ self.path)
+ self.merger.randomize()
+ if model_random.is_mutating():
+ if model_random.is_mutating():
+ modifier_factory = ka_factory.get_factory('modifier')
+ modifiertype_constraint = cpool.get(self, MODIFIERTYPE_CONSTRAINT)
+ self.modifier = modifier_factory.create_random(modifiertype_constraint,
+ self.path)
+ self.modifier.randomize()
+
+ def swap_places(self):
+ """Swap 'left' and 'right' tree node delegate swapping to the nodes components."""
+ # shuffle tree node
+ self.left_treenode, self.right_treenode = \
+ model_random.swap_parameters(self.left_treenode,
+ self.right_treenode)
+ self.left_background, self.right_background = \
+ model_random.swap_parameters(self.left_background,
+ self.right_background)
+ # delegate swapping to the nodes child components
+ if self.left_treenode is not None:
+ self.left_treenode.swap_places()
+ if self.right_treenode is not None:
+ self.right_treenode.swap_places()
+ self.layer.swap_places()
+ self.merger.swap_places()
+ self.modifier.swap_places()
+
+ def crossingover(self, other):
+ """Returns a deep copy mixed from my tree node and the other tree node.
+ pre: isinstance(other, TreeNode)
+ # check for distinct references, needs to copy content, not references
+ post: __return__ is not self
+ post: __return__ is not other
+ post: model_locus.unique_check(__return__, self, other) == ''
+ post: __return__.layer is not None
+ """
+ # deep copy
+ new_one = TreeNode(self.get_trunk())
+ # crossing over the layers
+ new_one.left_treenode = other.left_treenode \
+ if model_random.is_crossing() \
+ else self.left_treenode
+ if new_one.left_treenode is not None:
+ new_one.left_treenode = new_one.left_treenode.copy()
+ new_one.right_treenode = other.right_treenode \
+ if model_random.is_crossing() \
+ else self.right_treenode
+ if new_one.right_treenode is not None:
+ new_one.right_treenode = new_one.right_treenode.copy()
+ new_one.left_background = self.left_background.crossingover(other.left_background)
+ new_one.right_background = self.right_background.crossingover(other.right_background)
+ new_one.layer = other.layer.copy() \
+ if model_random.is_crossing() \
+ else self.layer.copy()
+ new_one.merger = other.merger.copy() \
+ if model_random.is_crossing() \
+ else self.merger.copy()
+ new_one.modifier = other.modifier.copy() \
+ if model_random.is_crossing() \
+ else self.modifier.copy()
+ return new_one
+
+ def render(self, ctx, width, height):
+ """
+ pre: ctx is not None
+ pre: width > 0
+ pre: height > 0
+ pre: width == height
+ """
+ ctx.save()
+
+ if (self.left_treenode is None) and (self.right_treenode is None):
+ # I am a leaf, use my own layer painting strategy
+ self.layer.render(ctx, width, height)
+ elif (self.left_treenode is not None) and (self.right_treenode is not None):
+ # merge 'left' and 'right' tree node
+ left_surface, left_ctx = self._prepare_surface(ctx, width, height, \
+ self.left_background)
+ self.left_treenode.render(left_ctx, width, height)
+# left_surface.write_to_png('/dev/shm/left_' + self.left_treenode.get_unique_id() + '.png')
+ right_surface, right_ctx = self._prepare_surface(ctx, width, height, \
+ self.right_background)
+ right_ctx.set_operator(cairo.OPERATOR_SOURCE)
+ self.right_treenode.render(right_ctx, width, height)
+# right_surface.write_to_png('/dev/shm/right_' + self.right_treenode.get_unique_id() + '.png')
+
+ self.merger.merge_layers(left_surface, right_surface, \
+ ctx, width, height)
+ elif (self.left_treenode is not None) and (self.right_treenode is None):
+ self.modifier.render_single_layer(self.layer, self.left_treenode,
+ ctx, width, height)
+# self.left_treenode.render(ctx, width, height)
+ elif (self.left_treenode is None) and (self.right_treenode is not None):
+ self.modifier.render_single_layer(self.layer, self.right_treenode,
+ ctx, width, height)
+# self.right_treenode.render(ctx, width, height)
+ ctx.restore()
+
+ def _prepare_surface(self, ctx, width, height, background):
+ new_surface = ctx.get_target().create_similar(cairo.CONTENT_COLOR_ALPHA,
+ width, height)
+ new_ctx = cairo.Context(new_surface)
+ new_ctx.scale(width, height)
+ new_ctx.translate(0.5, 0.5)
+ new_ctx.set_operator(cairo.OPERATOR_SOURCE)
+ rgba = background.rgba
+ new_ctx.set_source_rgba(rgba[0], rgba[1], rgba[2], rgba[3])
+ new_ctx.paint()
+ return new_surface, new_ctx
+
+ def explain(self, formater):
+ """Explain all layers, modifier and mergers."""
+
+ width = height = 256
+
+ # explain layers for current node and its child nodes
+ depth = _count_slash(self.path)
+ leaf = (self.left_treenode is None) and (self.right_treenode is None)
+ # preview
+ title = 'Combined layers of subtree ' + self.path if depth > 1 \
+ else 'Final image, all layers are combined'
+ title = 'Leaf ' + self.path if leaf else title
+ formater.begin_list(title)
+ # preview this node
+ self._preview(self, formater, width, height)
+
+ # explain details
+ formater.begin_list('Details for ' + title)
+ if leaf:
+ # explain the leafs layer
+ self.layer.explain(formater)
+ elif (self.left_treenode is not None) and (self.right_treenode is not None):
+ # explain how layers are combined
+ formater.color_item(self.left_background, 'left background color:', alpha=True)
+ self.merger.explain_left(formater)
+ self.left_treenode.explain(formater)
+ formater.color_item(self.right_background, 'right background color:', alpha=True)
+ self.merger.explain_right(formater)
+ self.right_treenode.explain(formater)
+ else:
+ # preview this node
+ self._preview(self.layer, formater, width, height)
+ text, surface, descr = self.modifier.explain()
+ if surface is not None:
+ formater.surface_item(surface, 'modifier : ' + text, descr)
+ else:
+ formater.text_item(text)
+ # explain child node
+ if self.left_treenode is not None:
+ self.left_treenode.explain(formater)
+ if self.right_treenode is not None:
+ self.right_treenode.explain(formater)
+ formater.end_list()
+
+ formater.end_list()
+
+ def _preview(self, source, formater, width, height):
+ surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
+ ctx = cairo.Context(surface)
+ ctx.scale(width, height)
+ ctx.translate(0.5, 0.5)
+ TreeNode._paint_checker_board(ctx)
+ ctx.save()
+ source.render(ctx, width, height)
+ formater.surface_item(surface, self.__class__.__name__, self.__class__.__name__)
+ ctx.restore()
+
+ def copy(self):
+ """ The tree nodes copy constructor.
+ post: __return__.layer is not None
+ """
+ new_one = TreeNode(self.get_trunk())
+ new_one.left_treenode = self.left_treenode.copy() \
+ if self.left_treenode is not None \
+ else None
+ new_one.right_treenode = self.right_treenode.copy() \
+ if self.right_treenode is not None \
+ else None
+ new_one.layer = self.layer.copy()
+ new_one.merger = self.merger.copy()
+ new_one.modifier = self.modifier.copy()
+ new_one.left_background = self.left_background.copy()
+ new_one.right_background = self.right_background.copy()
+ return new_one
+
+
+def _count_slash(path):
+ return len([char for char in path if char == '/'])