# -*- coding: utf-8 -*-
"""
Methods on the mtg for adel programs.
Author: Christophe Pradal
License: CeCILL
Date: 08/07/2008
"""
import csv
from openalea.mtg import MTG, fat_mtg
try:
from openalea.mtg.traversal import *
except:
pass
# from openalea.mtg.io import read_lsystem_string
from openalea.mtg.algo import union
import numpy
try:
from openalea.plantgl.all import (
Scene,
Translation,
Vector3,
Geometry,
AxisRotation,
AxisRotated,
Transform4,
BaseOrientation,
Shape,
Material,
Color3,
PglTurtle,
Mesh,
Translated,
)
except:
Material = tuple
Color3 = tuple
pass
import random
from math import pi
# import read_lsystem string & deps from newmtg/io
try:
from openalea.core.logger import get_logger, logging
logger = get_logger("openalea.mtg")
_ch = logging.StreamHandler()
logger.addHandler(_ch)
except:
logger = None
debug = 0
[docs]
def log(*args):
if debug:
if logger:
logger.debug(" ".join(map(str, args)))
else:
print(" ".join(map(str, args)))
import re
[docs]
def get_expr(s, expr):
res = re.search(expr, s)
_str = ""
if res:
_str = s[res.start() : res.end()]
return _str
[docs]
def get_label(s):
name = r"[a-zA-Z0-9]+"
return get_expr(s, name)
[docs]
def get_name(s):
name = r"[a-zA-Z]+"
return get_expr(s, name)
[docs]
def get_index(s):
name = r"[0-9]+"
return get_expr(s, name)
[docs]
def get_args(s):
args = r"\([0-9,-\.\+]+\)"
return get_expr(s, args)
[docs]
def get_float(s):
args = r"[0-9-\+]+"
num = get_expr(s, args)
return float(num)
[docs]
def read_lsystem_string(string, symbol_at_scale, functional_symbol={}, mtg=None):
"""Read a string generated by a lsystem.
:Parameters:
- `string`: The lsystem string representing the axial tree.
- `symbol_at_scale`: A dict containing the scale for each symbol name.
:Optional parameters:
- `functional_symbol`: A dict containing a function for specific symbols.
The args of the function have to be coherent with those in the string.
The return type of the functions have to be a dictionary of properties: dict(name, value)
:Return:
MTG object
"""
import openalea.plantgl.all as pgl
s = string
def transform(turtle, mesh):
x = turtle.getUp()
z = turtle.getHeading()
bo = pgl.BaseOrientation(x, z ^ x)
matrix = pgl.Transform4(bo.getMatrix())
matrix.translate(turtle.getPosition())
mesh = mesh.transform(matrix)
return mesh
# 1. Create the mtg structure.
if mtg is None:
mtg = MTG()
# 2. add some properties to the MTG
mtg.add_property("index")
mtg.add_property("can_label")
mtg.add_property("geometry")
mtg.add_property("tissue_type")
vid = mtg.root # vid of the support tree, i.e. at the finest scale
current_vertex = mtg.root
branching_stack = []
pending_edge = "" # edge type for the next edge to be created
scale = 0
lsys_symbols = ["[", "]", "/", "+", "^", "f"]
modules = list(symbol_at_scale.keys())
symbols = lsys_symbols + modules
index = dict(list(zip(list(symbol_at_scale.keys()), [0] * len(symbol_at_scale))))
is_ramif = False
# 2. Create a PlantGL Turtle...
turtle = pgl.Turtle()
max_scale = max(symbol_at_scale.values())
for edge_type in symbols:
if edge_type != "f":
s = s.replace(edge_type, "\n%s" % edge_type)
else:
s = s.replace("f(", "\nf(")
l = s.split()
try:
plant_name = [s for s in list(symbol_at_scale.keys()) if "plant" in s.lower()][
0
]
except:
ValueError("""Incorrect plant name (should be plant)""")
for node in l:
# Check if node is a module
tag = node[0]
if tag == "[":
branching_stack.append(vid)
turtle.push()
is_ramif = True
elif tag == "]":
vid = branching_stack.pop()
current_vertex = vid
scale = mtg.scale(vid)
turtle.pop()
is_ramif = False
elif tag == "/":
args = get_args(node[1:])
if args:
angle = get_float(args[1:-1])
turtle.rollR(angle)
else:
turtle.rollR()
elif tag == "+":
args = get_args(node[1:])
if args:
angle = get_float(args[1:-1])
turtle.left(angle)
else:
turtle.left()
elif tag == "^":
args = get_args(node[1:])
if args:
angle = get_float(args[1:-1])
turtle.up(angle)
else:
turtle.up()
elif tag == "f" and node[1] == "(":
args = get_args(node[1:])
if args:
length = get_float(args[1:-1])
if length > 0:
turtle.f(length)
else:
turtle.f()
else:
# add new modules to the mtg (i.e. add nodes)
name = get_name(node)
if name not in modules:
print("Unknow element %s" % name)
continue
module_scale = symbol_at_scale[name]
if is_ramif:
edge_type = "+"
else:
edge_type = "<"
log(node, module_scale, edge_type)
if module_scale == scale:
if mtg.scale(vid) == scale:
vid = mtg.add_child(vid, edge_type=edge_type, label=name)
current_vertex = vid
pending_edge = ""
log(
"",
"Cas 1.1",
scale,
"mtg.scale(vid)",
mtg.scale(vid),
"generated vertex",
vid,
)
assert mtg.scale(vid) == module_scale
else:
# add the edge to the current vertex
current_vertex = mtg.add_child(
current_vertex, edge_type=edge_type, label=name
)
log(
"",
"Cas 1.2",
scale,
"mtg.scale(vid)",
mtg.scale(vid),
"generated vertex",
current_vertex,
)
assert mtg.scale(current_vertex) == module_scale
is_ramif = False
elif module_scale > scale:
log("", "Cas 2", scale, "mtg.scale(vid)", mtg.scale(vid))
old_current_vertex = current_vertex
while module_scale > scale:
if mtg.scale(vid) == scale:
assert vid == current_vertex
vid = mtg.add_component(vid)
current_vertex = vid
log(
"",
"",
"Cas 2.1",
scale,
"generate new component",
current_vertex,
)
scale += 1
if module_scale == scale:
assert mtg.scale(current_vertex) == module_scale
mtg.property("label")[current_vertex] = name
break
else:
scale += 1
current_vertex = mtg.add_component(current_vertex)
else:
log(
node,
"add_child(%d, child=%d)"
% (old_current_vertex, current_vertex),
)
mtg.property("label")[current_vertex] = name
if mtg.scale(vid) == scale:
vid = mtg.add_child(
vid, child=current_vertex, edge_type=edge_type
)
is_ramif = False
else:
assert module_scale < scale
while module_scale < scale:
scale -= 1
current_vertex = mtg.complex(current_vertex)
else:
current_vertex = mtg.add_child(
current_vertex, edge_type=edge_type, label=name
)
assert mtg.scale(current_vertex) == module_scale
# MANAGE the properties, the geometry and the indices!!!
index[name] += 1
if name == plant_name:
for k in list(index.keys()):
if k != name:
index[k] = 0
mtg.property("index")[current_vertex] = index[name]
if name in functional_symbol:
features = eval(node, functional_symbol)
geom = features.get("geometry")
canlabel = features.get("label")
ttype = features.get("tissue_type")
if geom:
# get the transformation from the turtle
geom = transform(turtle, geom)
mtg.property("geometry")[current_vertex] = geom
if name == "StemElement":
# parse args to know how the turtle has to move .
args = get_args(node)[1:-1]
list_args = args.split(",")
length = float(list_args[1]) # 2nd arg
if length > 0:
turtle.f(length)
if canlabel:
canlabel.elt_id = index[name]
plant_id = mtg.complex_at_scale(current_vertex, scale=1)
canlabel.plant_id = mtg.property("index")[plant_id]
mtg.property("can_label")[current_vertex] = canlabel
if ttype:
mtg.property("tissue_type")[current_vertex] = ttype
mtg = fat_mtg(mtg)
return mtg
[docs]
def to_aggregation_table(g):
"""
Convert a mtg `g` to an aggregation table.
Returns a multi-line string with one line per triangle.
"""
# 1. classe at each scale
label = g.property("label")
index = g.property("index")
geometry = g.property("geometry")
can_label = g.property("can_label")
tissue_type = g.property("tissue_type")
symbols = dict(list(zip(iter(label.values()), iter(label.keys()))))
for k, v in symbols.items():
symbols[k] = g.scale(v)
l = list(symbols.items())
l.sort(key=lambda x: x[1])
# scales
header = "Plant Axe Metamer StemElement LeafElement Type"
header = header.split()
# 2. iterate on the geometry at the last scale
assert g.max_scale() == 4
root_axe = next(g.roots_iter(scale=2))
root_metamer = next(g.roots_iter(scale=3))
root_elt = next(g.roots_iter(scale=4))
# compute the number of triangles
nb_lines = sum(mesh.indexListSize() for mesh in geometry.values() if mesh)
lines = numpy.zeros((nb_lines, 6), dtype=int)
# compute relative index for metamer in axe and element in metamer
local_index = {}
# determine the number of element
nb_stem_elements = {}
for root_axe in g.roots_iter(scale=2):
for axe_id in pre_order(g, root_axe):
for i, mid in enumerate(g.components_iter(axe_id)):
local_index[mid] = i + 1
for root_metamer in g.roots_iter(scale=3):
for mid in pre_order(g, root_metamer):
stem_index = 0
leaf_index = 0
for eid in g.components_iter(mid):
if "stem" in label[eid].lower():
stem_index += 1
local_index[eid] = stem_index
else:
leaf_index += 1
local_index[eid] = leaf_index
nb_stem_elements[mid] = stem_index
i = 0
for root_elt in g.roots_iter(scale=4):
for vid in pre_order(g, root_elt):
metamer_id = g.complex(vid)
axe_id = g.complex(metamer_id)
plant_id = g.complex(axe_id)
plant_index = index[plant_id]
axe_index = index[axe_id]
metamer_index = local_index[metamer_id]
element_index = local_index[vid]
stem_index = 0
leaf_index = 0
# element
is_stem = "stem" in label[vid].lower()
if is_stem:
stem_index = element_index
else:
leaf_index = element_index
# determine tissue type
ttype = tissue_type[vid]
##lab = can_label[vid]
## if lab.is_soil():
## ttype = 0
## elif lab.is_leaf():
## ttype = 1#lamina
## elif lab.is_stem():
## if nb_stem_elements[metamer_id] == 1:
## ttype = 2 if lab.optical_id <= 2 else 5#2 = sheath, 5 = ear
## else:
## if element_index == 1:
## ttype = 3 if lab.optical_id <= 2 else 4#internode(3) or peduncle(4)
## else:
## ttype = 2 if lab.optical_id <= 2 else 5#sheath or ear
## else:
## ttype = -1 #unknown
indices = (
plant_index,
axe_index,
metamer_index,
stem_index,
leaf_index,
ttype,
)
# nid = g.node(vid)
# nid.tissue_type = ttype
geom = geometry.get(vid)
if geom:
n = geom.indexListSize()
for j in range(n):
lines[i] = indices
i += 1
lines = lines.transpose()
cols = [c.transpose() for c in lines]
table = dict(list(zip(header, cols)))
return table
[docs]
def to_plantgl(
g,
leaf_material=None,
stem_material=None,
soil_material=None,
colors=None,
ambient_only=False,
):
"""
Returns a plantgl scene from an mtg.
"""
if colors is None:
if leaf_material is None:
leaf_material = Material(Color3(0, 180, 0))
if stem_material is None:
stem_material = Material(Color3(0, 130, 0))
if soil_material is None:
soil_material = Material(Color3(170, 85, 0))
colors = g.property("color")
geometries = g.property("geometry")
labels = g.property("can_label")
scene = Scene()
def geom2shape(vid, mesh, scene):
shape = None
if isinstance(mesh, list):
for m in mesh:
geom2shape(vid, m, scene)
return
if mesh is None:
return
if isinstance(mesh, Shape):
shape = mesh
mesh = mesh.geometry
label = labels.get(vid)
if colors:
if ambient_only:
shape = Shape(
mesh,
Material(
ambient=Color3(*colors.get(vid, [0, 0, 0])),
diffuse=0.0,
specular=Color3(0, 0, 0),
),
)
else:
shape = Shape(mesh, Material(Color3(*colors.get(vid, [0, 0, 0]))))
elif not label:
if not shape:
shape = Shape(mesh)
elif label.is_soil():
shape = Shape(mesh, soil_material)
elif label.is_stem() and label.optical_id <= 1:
shape = Shape(mesh, stem_material)
elif label.is_stem() and label.optical_id > 1:
shape = Shape(mesh, soil_material)
elif label.is_leaf() and label.optical_id <= 1:
shape = Shape(mesh, leaf_material)
elif label.is_leaf() and label.optical_id > 1:
shape = Shape(mesh, soil_material)
shape.id = vid
scene.add(shape)
for vid, mesh in geometries.items():
geom2shape(vid, mesh, scene)
return (scene,)
def _line(ind, pts, label):
s = "p 1 %s 3 %s" % (str(label), " ".join("%.6f" % x for i in ind for x in pts[i]))
return s
[docs]
def to_canestra(g):
"""
Return a string representing a canestra file.
"""
geometry = g.property("geometry")
can_label = g.property("can_label")
begin = "# File generated by OpenAlea.Adel program"
lines = [begin]
max_scale = g.max_scale()
for root_elt in g.roots_iter(scale=max_scale):
for vid in pre_order(g, root_elt):
mesh = geometry.get(vid)
if not mesh:
continue
pts = numpy.array(mesh.pointList, ndmin=2)
indices = numpy.array(mesh.indexList, ndmin=2)
label = can_label[vid]
lines.extend([_line(ind, pts, label) for ind in indices])
lines.append("")
return "\n".join(lines)
[docs]
def planter(g, distribution, random_seed=0, azimuths=None):
"""
Transform a set of plants with given point distributions.
:Parameters:
- g: MTG
- distribution: a list of 2D positions
- random_seed: add a random rotation to each plant if the value is positive.In this case, random_seed is used as a seed.
- azimuth : a list defining plant azimuths (in radians). The list is recycled to achieve positioning of all the plants
"""
# assert g.nb_vertices(scale=1) == len(distribution)
geometry = g.property("geometry")
# store the previous plant translation to not copy each time all the geometry.
if "_plant_translation" not in g.properties():
g.add_property("_plant_translation")
translations = g.property("_plant_translation")
if random_seed > 0:
random.seed(random_seed)
def pt2transfo(pt, previous_pt, rotation):
matrix = Translation(pt).getMatrix()
if rotation != 0:
r = AxisRotation((0, 0, 1), rotation).getMatrix()
matrix = matrix * r * Translation(previous_pt).getMatrix()
return Transform4(matrix), pt
def transform_geom(geom, transfo, translation, rotation):
if isinstance(geom, list):
geom = [transform_geom(g, transfo, translation, rotation) for g in geom]
elif isinstance(geom, Mesh):
geom = geom.transform(transfo) if geom else geom
elif isinstance(geom, Geometry):
geom = Translated(translation, AxisRotated((0, 0, 1), rotation, geom))
elif isinstance(geom, Shape):
geom = Shape(
Translated(translation, AxisRotated((0, 0, 1), rotation, geom.geometry))
)
return geom
plants = g.vertices(scale=1)
plants = plants[: len(distribution)]
max_scale = g.max_scale()
for i, root_elt in enumerate(plants):
previous_translation = translations.get(root_elt, (0, 0, 0))
translations[root_elt] = distribution[i]
# displacement = Vector3(distribution[i])-Vector3(previous_translation)
rotation = 0
if azimuths is not None:
rotation = azimuths[i]
elif random_seed > 0:
rotation = random.random() * pi
transfo, translation = pt2transfo(
Vector3(distribution[i]), -Vector3(previous_translation), rotation
)
l = (
vid
for vid in g.vertices_iter(scale=max_scale)
if g.complex_at_scale(vid, scale=1) == root_elt
)
# for vid in g.components_at_scale(root_elt, 4):
for vid in l:
geom = geometry.get(vid)
geom = transform_geom(geom, transfo, translation, rotation)
if geom:
geometry[vid] = geom
can_label = g.property("can_label").get(vid)
if can_label:
can_label.plant_id = i
[docs]
def duplicate(g, n=1):
if g.nb_vertices(scale=1) == n:
return g
g1 = g.sub_mtg(g.root)
for i in range(n - 1):
g1 = union(g1, g)
return g1
[docs]
class CanMTG(MTG):
def __init__(self, functions, axial_string):
MTG.__init__(self)
symbols = {
"newPlant": 1,
"newAxe": 2,
"newMetamer": 3,
"StemElement": 4,
"LeafElement": 4,
}
g = read_lsystem_string(axial_string, symbols, functions, self)
self.symbols = symbols
[docs]
def apply_property(g, pname, function):
"""Apply a function to each values of a MTG property.
Returns this values as a dict (vid, new value).
"""
prop = g.property(pname)
return dict((k, function(v)) for k, v in prop.items())
CanMTG.planter = planter
CanMTG.to_plantgl = to_plantgl
CanMTG.to_canestra = to_canestra
CanMTG.to_aggregation_table = to_aggregation_table
MTG.planter = planter
MTG.to_plantgl = to_plantgl
MTG.to_canestra = to_canestra
MTG.to_aggregation_table = to_aggregation_table
[docs]
def convert(v, undef="NA"):
res = v
try:
res = int(res)
except ValueError:
try:
res = float(res)
except ValueError:
if res == undef:
res = None
return res
[docs]
def properties(d, exclude=[]):
res = {}
for k, v in d.items():
if k in exclude:
continue
v = convert(v)
if v is not None:
res[k] = v
return res
[docs]
def properties_from_dict(d, index, exclude=None):
if exclude is None:
exclude = []
res = {}
for k in d:
if k in exclude:
continue
res[k] = d[k][index]
return res
[docs]
def topological_table_to_mtg(csv_file, epsilon=1e-6):
f = open(csv_file)
l = f.readline()
sniffer = csv.Sniffer()
dialect = sniffer.sniff(l)
# Go from start of file
f.seek(0)
reader = csv.DictReader(f, dialect=dialect)
# Build the MTG
g = MTG()
topology = ["plant", "axe", "numphy"]
# buffers
prev_plant = 0
prev_axe = -1
prev_metamer = -1
vid_plant = -1
vid_axe = -1
vid_metamer = -1
vid_node = -1
# store the metamer vids of the axis 0
metamers = []
nodes = []
edge_type = "<"
for d in reader:
plant, axe, num_metamer = [int(convert(d.get(x), undef=None)) for x in topology]
# print 'adding vertices for plant: %d, axe:%d, metamer: %d'%(plant, axe, num_metamer)
# Add new plant
if plant != prev_plant:
label = "plant" + str(plant)
vid_plant = g.add_component(g.root, edge_type="/", label=label)
vid_axe = -1
vid_metamer = -1
vid_node = -1
prev_axe = -1
# ??
if num_metamer < prev_metamer:
prev_axe = -1
prev_metamer = -1
# Add an axis
if axe != prev_axe:
label = "axe" + str(axe)
if axe == 0:
vid_axe = g.add_component(vid_plant, edge_type="/", label=label)
vid_node = -1
else:
# args['edge_type'] = '+'
edge_type = "+"
vid_axe = g.add_child(vid_axe, edge_type=edge_type, label=label)
# vid, vid_axe = g.add_child_and_complex(metamers[axe], complex=vid_axe, **args)
# Add metamers
args = properties(d, exclude=topology)
assert num_metamer > 0
label = "metamer" + str(num_metamer)
new_axe = True
if axe == 0 and num_metamer == 1:
metamers = []
edge_type = "/"
vid_metamer = g.add_component(vid_axe, edge_type="/", label=label, **args)
elif num_metamer == 1:
# Add the first element connected to previous metamer on the previous axis
edge_type = "+"
vid_metamer, vid_axe = g.add_child_and_complex(
metamers[axe - 1], complex=vid_axe, edge_type="+", label=label, **args
)
vid_node = nodes[axe - 1]
else:
edge_type = "<"
vid_metamer = g.add_child(vid_metamer, label=label, edge_type="<", **args)
new_axe = False
if axe == 0:
metamers.append(vid_metamer)
# Add internode, sheath, and lamina
nb_internodes = 0
nb_sheath = 0
nb_leaf = 0
# Internode
if args["Ev"] > 0.0:
if args["Esen"] > args["Ev"]:
if vid_node == -1:
vid_node = g.add_component(
vid_metamer,
label="Esen",
edge_type="/",
length=args["Ev"],
po=args["Epos"],
diam=args["Ed"],
)
assert edge_type == "/"
else:
vid_node, vid_metamer = g.add_child_and_complex(
vid_node,
complex=vid_metamer,
label="Esen",
edge_type=edge_type,
length=args["Ev"],
po=args["Epos"],
diam=args["Ed"],
)
else:
if vid_node == -1:
vid_node = g.add_component(
vid_metamer,
label="Egreen",
edge_type="/",
length=args["Ev"] - args["Esen"],
po=args["Epo"],
diam=args["Ed"],
)
assert edge_type == "/"
else:
vid_node, vid_metamer = g.add_child_and_complex(
vid_node,
complex=vid_metamer,
label="Egreen",
edge_type=edge_type,
length=args["Ev"] - args["Esen"],
po=args["Epo"],
diam=args["Ed"],
)
vid_node = g.add_child(
vid_node,
label="Esen",
edge_type="<",
length=args["Esen"],
po=args["Epos"],
diam=args["Ed"],
)
else:
if new_axe and args["Gv"] == 0.0:
if vid_node == -1:
vid_node = g.add_component(
vid_metamer,
label="Egreen",
edge_type="/",
length=0.0,
po=args["Epo"],
diam=args["Ed"],
)
else:
vid_node, vid_metamer = g.add_child_and_complex(
vid_node,
complex=vid_metamer,
label="Egreen",
edge_type=edge_type,
length=0.0,
po=args["Epo"],
diam=args["Ed"],
)
# Sheath
if args["Gv"] > 0.0:
if args["Gsen"] > args["Gv"]:
vid_node = g.add_child(
vid_node,
label="Gsen",
edge_type="<",
length=args["Gv"],
po=args["Gpos"],
diam=args["Gd"],
)
else:
vid_node = g.add_child(
vid_node,
label="Ggreen",
edge_type="<",
length=args["Gv"] - args["Gsen"],
po=args["Gpo"],
diam=args["Gd"],
)
vid_node = g.add_child(
vid_node,
label="Gsen",
edge_type="<",
length=args["Gsen"],
po=args["Gpos"],
diam=args["Gd"],
)
if axe == 0:
nodes.append(vid_node)
# Laminae
if args["Lv"] > 0.0:
if args["Lsen"] > args["Lv"]:
l_node = g.add_child(
vid_node,
label="Lsen",
edge_type="+",
length=args["Lv"],
po=args["Lpos"],
Ll=args["Ll"],
Lw=args["Lw"],
LcType=args["LcType"],
LcIndex=args["LcIndex"],
Linc=args["Linc"],
Laz=args["Laz"],
srb=0,
srt=1,
)
else:
l_node = g.add_child(
vid_node,
label="Lgreen",
edge_type="+",
length=args["Lv"] - args["Lsen"],
po=args["Lpo"],
Ll=args["Ll"],
Lw=args["Lw"],
LcType=args["LcType"],
LcIndex=args["LcIndex"],
Linc=args["Linc"],
Laz=args["Laz"],
srb=0,
srt=1 - (args["Lsen"] / args["Lv"]),
)
l_node = g.add_child(
l_node,
label="Lsen",
edge_type="<",
length=args["Lsen"],
po=args["Lpos"],
Ll=args["Ll"],
Lw=args["Lw"],
LcType=args["LcType"],
LcIndex=args["LcIndex"],
Linc=args["Linc"],
Laz=args["Laz"],
srt=1,
srb=1 - (args["Lsen"] / args["Lv"]),
)
prev_plant = plant
prev_axe = axe
prev_metamer = num_metamer
f.close()
return fat_mtg(g)
[docs]
def internode(g, vid_axe, vid_metamer, prev_node, props, epsilon):
if props["Ev"] < epsilon:
return prev_node
[docs]
def mtg_factory(params, sectors=1):
"""Construct a MTG from a dictionary.
The dictionary contains the parameters and a list of elements. Sector is an integer giving the number of LeafElements per Leaf
The keys of params are:
* plant: index of plant
* axe
* numphy
* Lv
* L_shape
* Lsen
* Lw_shape
* LcType
* LcIndex
* Linc
* Laz
* Lpo
* Lpos
* Gl
* Gv
* Gsen
* Gpo
* Gpos
* Gd
* Ginc
* El
* Ev
* Esen
* Ed
* Einc
* Epo
* Epos
:TODO:
* add length and final_length (DONE)
* fix naming convention for Linc: relative inclination (DONE)
* Add a scale to define the tissue types (ear, sheath, laminae, gain)
* diam and final_diam (resp. width)
* function reset length
* function phenology(g, table) -> dynamic parameters (start_thermaltime, end_thermaltime)
* function growthThermaltime(g, tt, dtt): tt=thermaltime du couvert
* function growthThermaltime(g, tt, dtt, stress factor)
* stress factor: offre/demande
* demand = :math:`D=\int_{tt}^{tt+dtt}{S(x)dx}\rho_s+\int_{tt}^{tt+dtt}{V(x)dx}\rho_v`
* offre = :math:`sum{E_abs}\eps_b`
=> ds = ds_predit* stress_factor
* give the area to the leaf model
* update properties
"""
if not _check_adel_parameters(params):
raise ValueError("Adel parameters are invalid")
g = MTG()
topology = ["plant", "axe", "numphy"]
# buffers
prev_plant = 0
prev_axe = -1
prev_metamer = -1
vid_plant = -1
vid_axe = -1
vid_metamer = -1
vid_node = -1
# store the metamer vids of the axis 0
metamers = []
nodes = []
edge_type = "<"
dp = params
nrow = len(params["plant"])
for i in range(nrow):
# plant, axe, num_metamer = [int(convert(d.get(x),undef=None)) for x in topology]
plant = int(dp["plant"][i])
try:
axe = int(dp["axe"][i])
except:
axe = int(dp["ms_insertion"][i])
num_metamer = int(dp["numphy"][i])
# plant, axe, num_metamer = [int(convert(d.get(x),undef=None)) for x in topology]
# print 'plant: %d, axe:%d, nb_metamers: %d'%(plant, axe, num_metamer)
# Add new plant
if plant != prev_plant:
label = "plant" + str(plant)
vid_plant = g.add_component(g.root, label=label, edge_type="/")
vid_axe = -1
vid_metamer = -1
vid_node = -1
prev_axe = -1
if num_metamer < prev_metamer:
prev_axe = -1
prev_metamer = -1
# Add an axis
if axe != prev_axe:
label = "axe" + str(axe)
if axe == 0:
vid_axe = g.add_component(vid_plant, edge_type="/", label=label)
vid_node = -1
else:
# args['edge_type'] = '+'
edge_type = "+"
assert g.parent(vid_plant) is None
new_axe = g.add_child(vid_axe, edge_type=edge_type, label=label)
print(vid_axe, new_axe, vid_plant)
assert g.parent(vid_plant) is None
# vid, vid_axe = g.add_child_and_complex(metamers[axe], complex=vid_axe, **args)
# Add metamers
args = properties_from_dict(dp, i, exclude=topology)
assert num_metamer > 0
label = "metamer" + str(num_metamer)
if axe == 0 and num_metamer == 1:
metamers = []
edge_type = "/"
vid_metamer = g.add_component(vid_axe, edge_type="/", label=label, **args)
elif num_metamer == 1:
# Add the first element connected to previous metamer on the previous axis
edge_type = "+"
vid_metamer = g.add_component(vid_axe, label=label, **args)
vid_metamer = g.add_child(
metamers[axe - 1], child=vid_metamer, edge_type="+"
)
vid_node = nodes[axe - 1]
else:
edge_type = "<"
vid_metamer = g.add_child(vid_metamer, label=label, edge_type="<", **args)
if axe == 0:
metamers.append(vid_metamer)
# Add internode, sheath, and lamina
# Visible Internode
# if args['Ev'] > 0.:
tissue = "internode"
# Several cases:
Egreen = args["Ev"] - args["Esen"] if args["Ev"] - args["Esen"] > 0.0 else 0.0
Esen = args["Ev"] if Egreen == 0.0 else args["Esen"]
# - Green element and perhaps a senescent one
if vid_node == -1:
# first metamer on the axis
vid_node = g.add_component(
vid_metamer,
label="StemElement",
edge_type="/",
length=Egreen,
final_length=args["El"],
po=args["Epo"],
diam=args["Ed"],
tissue=tissue,
inclination=args["Einc"],
)
assert edge_type == "/"
else:
# first element on the metamer which has a parent metamer on the axis
new_node = g.add_component(vid_metamer)
vid_node = g.add_child(
vid_node,
child=new_node,
label="StemElement",
edge_type=edge_type,
length=Egreen,
final_length=args["El"],
po=args["Epo"],
diam=args["Ed"],
tissue=tissue,
inclination=args["Einc"],
)
# Add a senescent element
# if args['Esen'] > 0.:
vid_node = g.add_child(
vid_node,
label="StemElement",
edge_type="<",
length=Esen,
final_length=args["El"],
po=args["Epos"],
diam=args["Ed"],
tissue=tissue,
sen=True,
inclination=0.0,
)
# Sheath
# Same logic that described previously.
Ggreen = args["Gv"] - args["Gsen"] if args["Gv"] - args["Gsen"] > 0.0 else 0.0
Gsen = args["Gv"] if Ggreen == 0.0 else args["Gsen"]
vid_node = g.add_child(
vid_node,
label="StemElement",
edge_type="<",
length=Ggreen,
final_length=args["Gl"],
po=args["Gpo"],
diam=args["Gd"],
tissue=tissue,
inclination=args["Ginc"],
)
# if args['Gsen'] > 0.:
vid_node = g.add_child(
vid_node,
label="StemElement",
edge_type="<",
length=Gsen,
final_length=args["Gl"],
po=args["Gpos"],
diam=args["Gd"],
sen=True,
tissue=tissue,
inclination=0.0,
)
if axe == 0:
nodes.append(vid_node)
# Lamina
# if args['Lv'] > 0.:
l_node = vid_node
tissue = "lamina"
ds = 1.0 / sectors
srlim = 1.0 - (args["Lsen"] / args["Lv"])
st = ds
edge_type = "+"
Laz = args["Laz"]
Lgreen = args["Lv"] - args["Lsen"] if args["Lv"] - args["Lsen"] > 0.0 else 0.0
Lsen = args["Lv"] if Lgreen == 0.0 else args["Lsen"]
for isect in range(sectors):
# create one green and/or one senescent sector
srb_green = st - ds
srt_green = min(st, max(srb_green, srlim))
srb_sen = srt_green
srt_sen = st
l_node = g.add_child(
l_node,
label="LeafElement",
edge_type=edge_type,
length=args["Lv"],
final_length=args["L_shape"],
po=args["Lpo"],
Ll=args["Ll"],
Lw=args["Lw_shape"],
LcType=args["LcType"],
LcIndex=args["LcIndex"],
Laz=Laz,
Linc=args["Linc"],
srb=srb_green,
srt=srt_green,
tissue=tissue,
)
l_node = g.add_child(
l_node,
label="LeafElement",
edge_type="<",
length=args["Lv"],
final_length=args["L_shape"],
po=args["Lpos"],
Ll=args["Ll"],
Lw=args["Lw_shape"],
LcType=args["LcType"],
LcIndex=args["LcIndex"],
Laz=Laz,
Linc=args["Linc"],
srb=srb_sen,
srt=srt_sen,
sen=True,
tissue=tissue,
)
st += ds
edge_type = "<"
prev_plant = plant
prev_axe = axe
prev_metamer = num_metamer
return fat_mtg(g)
def _check_adel_parameters(params):
return True
[docs]
def compute_element(n, symbols):
leaf = symbols.get("LeafElement")
stem = symbols.get("StemElement")
leaf_rank = int(n.complex().index())
optical_species = int(n.po)
final_length = n.final_length
length = n.length
s_base = n.srb
s_top = n.srt
seed = n.LcIndex
# leaf inclination
linc = n.Linc
if n.label.startswith("L"):
radius_max = n.Lw
element = leaf(
optical_species,
final_length,
length,
radius_max,
s_base,
s_top,
leaf_rank,
seed,
linc,
)
else:
diameter_base = (
n.parent().diam if (n.parent() and n.parent().diam > 0.0) else n.diam
)
diameter_top = n.diam
element = stem(optical_species, length, diameter_base, diameter_top)
can_label = element.get("label")
if can_label:
can_label.elt_id = leaf_rank
plant_node = n.complex_at_scale(scale=1)
can_label.plant_id = plant_node.index()
geom = element.get("geometry")
return geom, can_label
[docs]
def mtg_turtle(g, symbols):
"""Compute the geometry on each node of the MTG using Turtle geometry."""
from openalea.mtg import turtle
plants = g.component_roots_at_scale_iter(g.root, scale=1)
nplants = g.nb_vertices(scale=1)
gt = MTG()
def adel_visitor(g, v, turtle):
# 1. retriev the node
n = g.node(v)
angle = float(n.Laz) if n.Laz else 0.0
turtle.rollL(angle)
if g.edge_type(v) == "+":
if not n.label.startswith("L"):
angle = n.Ginc or n.Einc
angle = float(angle) if angle is not None else 0.0
turtle.up(angle)
# 2. Compute the geometric symbol
mesh, can_label = compute_element(n, symbols)
if mesh:
n.geometry = transform(turtle, mesh)
n.can_label = can_label
# 3. Update the turtle
turtle.setId(v)
if n.label.startswith("S"):
turtle.f(n.length)
# Get the azimuth angle
for plant in plants:
gplant = g.sub_mtg(plant)
scene = turtle.TurtleFrame(gplant, visitor=adel_visitor)
gt = union(gplant, gt)
return gt
[docs]
def mtg_turtle_time(g, symbols, time, update_visitor=None):
"""Compute the geometry on each node of the MTG using Turtle geometry.
Update_visitor is a function called on each node in a pre-order (parent before children).
This function allow to update the parameters and state variables of the vertices.
:Example:
>>> def grow(node, time):
"""
g.properties()["geometry"] = {}
g.properties()["_plant_translation"] = {}
max_scale = g.max_scale()
def compute_element(n, symbols, time):
leaf = symbols.get("LeafElement")
stem = symbols.get("StemElement")
leaf_rank = int(n.complex().index())
optical_species = int(n.po)
metamer = n.complex()
# Length computation
if update_visitor:
length = n.length
final_length = metamer.final_length
else:
final_length = n.final_length
try:
length = (
final_length
* (time - metamer.start_tt)
/ (metamer.end_tt - metamer.start_tt)
if metamer.end_tt and time < metamer.end_tt
else n.length
)
except:
length = n.length
if update_visitor and n.label.startswith("L"):
if metamer.final_length is None:
metamer.final_length = n.final_length
metamer.length = n.length
length = metamer.length
prev_length = (
metamer.final_length
* (n.start_tt - metamer.start_tt)
/ (metamer.end_tt - metamer.start_tt)
)
s_base = (metamer.length - prev_length - n.length) / metamer.length
else:
s_base = n.srb
s_top = n.srt
seed = n.LcIndex
# leaf inclination
if update_visitor and n.label.startswith("L"):
linc = metamer.insertion_angle
else:
linc = n.Linc
element = {}
if n.label.startswith("L"):
radius_max = n.Lw
element = leaf(
optical_species,
final_length,
length,
radius_max,
s_base,
s_top,
leaf_rank,
seed,
linc,
)
else:
diameter_base = (
n.parent().diam if (n.parent() and n.parent().diam > 0.0) else n.diam
)
diameter_top = n.diam
element = stem(optical_species, length, diameter_base, diameter_top)
can_label = element.get("label")
if can_label:
can_label.elt_id = leaf_rank
plant_node = n.complex_at_scale(scale=1)
can_label.plant_id = plant_node.index()
geom = element.get("geometry")
return geom, can_label
def adel_visitor(g, v, turtle, time):
# 1. retriev the node
n = g.node(v)
# Update visitor to compute or modified the node parameters
if update_visitor is not None:
update_visitor(n, time)
if "Leaf" in n.label:
metamer = n.complex()
if (n.start_tt <= time < n.end_tt) or (
(time >= metamer.end_tt) and n.edge_type() == "+"
):
angle = float(metamer.Laz) if metamer.Laz else 0.0
turtle.rollL(angle)
else:
if "Leaf" in n.label:
if n.edge_type() == "+":
angle = float(n.Laz) if n.Laz else 0.0
turtle.rollL(angle)
else:
angle = float(n.Laz) if n.Laz else 0.0
turtle.rollL(angle)
if g.edge_type(v) == "+":
angle = n.Ginc or n.Einc
angle = float(angle) if angle is not None else 0.0
# angle = n.inclination
# angle = float(angle) if angle is not None else 0.
turtle.up(angle)
# 2. Compute the geometric symbol
mesh, can_label = compute_element(n, symbols, time)
if mesh:
n.geometry = transform(turtle, mesh)
n.can_label = can_label
# 3. Update the turtle
turtle.setId(v)
m = n.complex()
if update_visitor:
length = n.length
else:
try:
length = (
n.length * (time - m.start_tt) / (m.end_tt - m.start_tt)
if time < m.end_tt
else n.length
)
except:
length = n.length
if ("Leaf" not in n.label) and (length > 0.0):
turtle.F(length)
# Get the azimuth angle
def traverse_with_turtle_time(g, vid, time, visitor=adel_visitor):
turtle = PglTurtle()
def push_turtle(v):
n = g.node(v)
# if 'Leaf' in n.label:
# return False
try:
start_tt = n.complex().start_tt
if start_tt > time:
return False
except:
pass
if g.edge_type(v) == "+":
turtle.push()
return True
def pop_turtle(v):
n = g.node(v)
try:
start_tt = n.complex().start_tt
if start_tt > time:
return False
except:
pass
if g.edge_type(v) == "+":
turtle.pop()
if g.node(vid).complex().start_tt <= time:
visitor(g, vid, turtle, time)
# turtle.push()
plant_id = g.complex_at_scale(vid, scale=1)
for v in pre_order2_with_filter(g, vid, None, push_turtle, pop_turtle):
if v == vid:
continue
# Done for the leaves
if g.node(v).complex().start_tt > time:
print("Do not consider ", v, time)
continue
visitor(g, v, turtle, time)
scene = turtle.getScene()
return g
for plant_id in g.component_roots_at_scale_iter(g.root, scale=max_scale):
g = traverse_with_turtle_time(g, plant_id, time)
return g
[docs]
def thermal_time(
g, phyllochron=110.0, leaf_duration=1.6, stem_duration=1.6, leaf_falling_rate=10
):
"""
Add dynamic properties on the mtg to simulate developpement
leaf_duration is the phyllochronic time for a leaf to develop from tip appearance to collar appearance
stem_duration is the phyllochronic time for a stem to develop
falling_rate (degrees / phyllochron) is the rate at which leaves fall after colar appearance
"""
plants = g.vertices(scale=1)
metamer_scale = g.max_scale() - 1
for plant in plants:
tt = 0
v = next(g.component_roots_at_scale_iter(plant, scale=metamer_scale))
for metamer in pre_order2(g, v):
end_leaf = tt + phyllochron * leaf_duration
nm = g.node(metamer)
nm.start_tt = tt
nm.end_tt = end_leaf
nm.frate = leaf_falling_rate / phyllochron
sectors = [node for node in nm.components() if "Leaf" in node.label]
stems = [node for node in nm.components() if "Stem" in node.label]
nb_stems = len(stems)
stem_tt = end_leaf
dtt = phyllochron * stem_duration / nb_stems
for stem in stems:
stem.start_tt = stem_tt
stem.end_tt = stem_tt + dtt
stem_tt += dtt
nb_sectors = max(1, len(sectors))
sector_tt = end_leaf
dtt = phyllochron * leaf_duration / nb_sectors
for sector in sectors:
sector.start_tt = sector_tt - dtt
sector.end_tt = sector_tt
sector_tt -= dtt
tt += phyllochron
return g