Source code for openalea.plantscan3d.mtgmanip

from openalea.plantgl.all import *


[docs] def initialize_mtg(root, nodelabel="N"): """ Initialize a new MTG with a single node at the given position. Parameters ~~~~~~~~~~ root: Vector3 Position of the root node. nodelabel: str Label for the nodes (default "N"). Returns ~~~~~~~ openalea.mtg.MTG New MTG with one node. """ from openalea.mtg import MTG mtg = MTG() plantroot = mtg.root branchroot = mtg.add_component(plantroot, label="P") noderoot = mtg.add_component(branchroot, label=nodelabel) mtg.property("position")[noderoot] = root mtg.property("radius")[noderoot] = None assert len(mtg.property("position")) == 1 return mtg
[docs] def mtg2pgltree(mtg): """ Convert an MTG to a PlantGL tree representation. Parameters ~~~~~~~~~~ mtg: openalea.mtg.MTG MTG object to convert. Returns ~~~~~~~ tuple (nodes, parents, vertex2node) where nodes is a list of positions, parents is a list of parent indices, and vertex2node maps MTG vertex ids to list indices. """ vertices = mtg.vertices(scale=mtg.max_scale()) vertex2node = dict([(vid, i) for i, vid in enumerate(vertices)]) positions = mtg.property("position") nodes = [positions[vid] for vid in vertices] parents = [ vertex2node[mtg.parent(vid)] if mtg.parent(vid) else vertex2node[vid] for vid in vertices ] return nodes, parents, vertex2node
[docs] def pgltree2mtg( mtg, startfrom, parents, positions, radii=None, filter_short_branch=False, angle_between_trunk_and_lateral=60, nodelabel="N", ): """ Convert a PlantGL tree representation back into an MTG. Parameters ~~~~~~~~~~ mtg: openalea.mtg.MTG MTG object to add nodes to. startfrom: int Starting node id in the MTG. parents: list of int Parent indices for each node in the tree. positions: list of Vector3 Positions of each node. radii: list of float or None Radii of each node. If None, no radius property is set. filter_short_branch: bool If True, remove branches with no children. angle_between_trunk_and_lateral: float Angle threshold in degrees to determine edge type between trunk and lateral branches. nodelabel: str Label for the nodes (default "N"). Returns ~~~~~~~ openalea.mtg.MTG MTG with the tree added. """ from math import acos, degrees rootpos = Vector3(mtg.property("position")[startfrom]) if norm(positions[0] - rootpos) > 1e-3: if len(mtg.children(startfrom)) > 0: edge_type = "+" else: edge_type = "<" startfrom = mtg.add_child( parent=startfrom, position=positions[0], label=nodelabel, edge_type=edge_type, ) children, root = determine_children(parents) clength = subtrees_size(children, root) mchildren = list(children[root]) npositions = mtg.property("position") removed = [] if len(mchildren) >= 2 and filter_short_branch: mchildren = [c for c in mchildren if len(children[c]) > 0] if len(mchildren) != len(children[root]): removed = list(set(children[root]) - set(mchildren)) mchildren.sort(key=lambda x: -clength[x]) toprocess = [ (c, startfrom, "<" if i == 0 else "+") for i, c in enumerate(mchildren) ] while len(toprocess) > 0: nid, parent, edge_type = toprocess.pop(0) pos = positions[nid] parameters = dict( parent=parent, label=nodelabel, edge_type=edge_type, position=pos ) if radii: parameters["radius"] = radii[nid] mtgnode = mtg.add_child(**parameters) mchildren = list(children[nid]) if len(mchildren) > 0: if len(mchildren) >= 2 and filter_short_branch: mchildren = [c for c in mchildren if len(children[c]) > 0] if len(mchildren) != len(children[nid]): removed = list(set(children[nid]) - set(mchildren)) if len(mchildren) > 0: mchildren.sort(key=lambda x: -clength[x]) first_edge_type = "<" langle = degrees( acos( dot( direction(pos - npositions[parent]), direction(positions[mchildren[0]] - pos), ) ) ) if langle > angle_between_trunk_and_lateral: first_edge_type = "+" edges_types = [first_edge_type] + [ "+" for i in range(len(mchildren) - 1) ] toprocess += [(c, mtgnode, e) for c, e in zip(mchildren, edges_types)] print("Remove short nodes ", ",".join(map(str, removed))) return mtg
[docs] def gaussian_weight(x, var): """ Compute a Gaussian weight at a given value. Parameters ~~~~~~~~~~ x: float Input value. var: float Variance of the Gaussian distribution. Returns ~~~~~~~ float Gaussian weight exp(-x²/(2*var)) / sqrt(2*pi*var²). """ from math import exp, pi, sqrt return exp(-(x**2) / (2 * var)) / sqrt(2 * pi * var * var)
[docs] def gaussian_filter(mtg, propname, considerapicalonly=True): """ Apply a Gaussian filter to smooth a property along the MTG. Parameters ~~~~~~~~~~ mtg: openalea.mtg.MTG MTG object. propname: str Name of the property to filter. considerapicalonly: bool If True, only consider apical (edge_type "<") children. Default to True. Returns ~~~~~~~ None. The property is updated in-place. """ prop = mtg.property(propname) nprop = dict() gw0 = gaussian_weight(0, 1) gw1 = gaussian_weight(1, 1) for vid, value in list(prop.items()): nvalues = [value * gw0] parent = mtg.parent(vid) if parent and parent in prop: nvalues.append(prop[parent] * gw1) children = mtg.children(vid) if considerapicalonly: children = [child for child in children if mtg.edge_type(child) == "<"] for child in children: if child in prop: nvalues.append(prop[child] * gw1) nvalue = sum(nvalues[1:], nvalues[0]) / sum([gw0 + (len(nvalues) - 1) * gw1]) nprop[vid] = nvalue prop.update(nprop)
[docs] def threshold_filter(mtg, propname): """ Apply a threshold filter to a property, preventing values from increasing along the traversal from root to leaves. Parameters ~~~~~~~~~~ mtg: openalea.mtg.MTG MTG object. propname: str Name of the property to filter. Returns ~~~~~~~ None. The property is updated in-place. """ from openalea.mtg.traversal import iter_mtg2 prop = mtg.property(propname) nprop = dict() for vid in iter_mtg2(mtg, mtg.root): if vid in prop: parent = mtg.parent(vid) if parent and parent in prop: pvalue = nprop.get(parent, prop[parent]) if pvalue < prop[vid]: nprop[vid] = pvalue prop.update(nprop)
[docs] def get_first_param_value(mtg, propname): """ Get the first non-None property value at the maximum scale. Parameters ~~~~~~~~~~ mtg: openalea.mtg.MTG MTG object. propname: str Name of the property. Returns ~~~~~~~ object or None First non-None property value found at the maximum scale, or None if no such value exists. """ from openalea.mtg.traversal import iter_mtg2 scale = mtg.max_scale() prop = mtg.property(propname) for vid in iter_mtg2(mtg, mtg.root): if vid in prop and mtg.scale(vid) == scale and prop[vid] is not None: return prop[vid]
[docs] def pipemodel(mtg, rootradius=None, leafradius=None, root=None): """ Uses the pipemodel algorithm to estimate the radius of each node of the MTG. Parameters ~~~~~~~~~~ mtg: openalea.mtg.MTG MTG object to be processed. rootradius: float | None Radius of the root node. If None, the radius of the first node is used. Default to None. leafradius: float | None Radius of the leaf nodes. If None, it is estimated as 1/100 of the root radius. Default to None. root: int | None Id of the root node. If None, the root node is selected automatically. Returns ~~~~~~~ MTG with a new or updated radius property computed using the pipe model algorithm. """ from math import log from openalea.mtg.traversal import post_order2 if root is None: roots = mtg.roots(scale=mtg.max_scale()) assert len(roots) == 1 root = roots[0] if rootradius is None: if "radius" not in mtg.properties(): raise KeyError("Property 'radius' is not defined in the MTG.") rootradius = mtg.property("radius")[root] if leafradius is None: leafradius = rootradius / 100.0 vertices = list(post_order2(mtg, root)) leaves = [vid for vid in vertices if len(mtg.children(vid)) == 0] # pipeexponent = log(len(leaves)) / (log(rootradius) - log(leafradius)) # print pipeexponent # invpipeexponent = 1./ pipeexponent radiusprop = dict() for vid in leaves: radiusprop[vid] = leafradius nbelems = dict() for vid in leaves: nbelems[vid] = 1 for vid in vertices: if vid not in nbelems: nbelems[vid] = sum([nbelems[child] for child in mtg.children(vid)]) + 1 print(root, nbelems[root]) # pipeexponent = log(nbelems[root]) / (log(rootradius) - log(leafradius)) pipeexponent = (log(rootradius) - log(leafradius)) / log(nbelems[root]) print(pipeexponent) # invpipeexponent = 1.0 / pipeexponent for vid in vertices: if vid not in radiusprop: radiusprop[vid] = leafradius * (nbelems[vid] ** pipeexponent) # for vid in post_order2(mtg, root): # if not vid in radiusprop: # rad = pow(sum([pow(radiusprop[child], pipeexponent) for child in mtg.children(vid)]), invpipeexponent) # radiusprop[vid] = rad return radiusprop