Source code for openalea.hydroroot.generator.measured_root

import random
import numpy as np

from openalea.mtg import MTG, fat_mtg
from openalea.mtg.traversal import post_order2
from openalea.mtg import algo

SUPERIOR_ORDER = True

[docs] def mtg_builder( primary_length, primary_length_data, lateral_length_data, segment_length=1e-4, branching_variability=None, branching_delay=None, length_law=None, nude_tip_length=20, order_max=5, seed=None): """ 'Deprecated' Create a MTG from length laws. The first law is the length of the primary root. The second law is the length of the ramification. The primary root is of length 'total_length'. It is discretized following the law length_base. All the variables are expressed in meters. :param primary_length: :param primary_length_data: :param lateral_length_data: :param segment_length: (Default value = 1e-4) :param branching_variability: (Default value = None) :param branching_delay: (Default value = None) :param length_law: (Default value = None) :param nude_tip_length: (Default value = 20) :param order_max: (Default value = 5) :param seed: (Default value = None) """ length_base = primary_length_data length_lateral = lateral_length_data g = MTG() rid = vid = g.add_component(g.root) # Primary root # g.node(vid).tip_length = total_length prev_len = 0. ramifs = [] # Create the primary axis for i, len_base in enumerate(length_base): while len_base-prev_len > 0: prev_len += segment_length vid = g.add_child(vid, edge_type='<', label='S', base_length=prev_len, length=segment_length, order=0) vid = g.add_child(vid, edge_type='<', label='S', base_length=len_base, length=segment_length, order=0) prev_len = len_base if length_lateral[i] > 0.: ramifs.append((length_lateral[i], vid)) # Create lateral axis for length_ramif, cid in ramifs: len_tip = length_ramif # print 'length ', len_tip _root_id = cid parent_base = g.node(cid).base_length prev_len = 0. count = 0 while len_tip - prev_len >0.: #print count count+= 1 prev_len += segment_length edget = '+' if cid == _root_id else '<' cid = g.add_child(cid, edge_type=edget, label='S', base_length=prev_len+parent_base, length=segment_length, order=1) ######################################################## # Compute position_index: distance to tip in nb vertices position_index = g.properties()['position_index'] = {} for v in post_order2(g, rid): pi = position_index.setdefault(v,0) parent = g.parent(v) if parent is not None: if g.edge_type(v) == '<': position_index[parent]= pi+1 ################## # Compute higher order of ramification anchors = [] # list of branching points where lateral roots will be "grafted" if not seed is None: random.seed(seed) np.random.seed(seed) def delayed_markov(timer): """markov chain with a delay between ramification :param timer: """ if (timer <= 0) : return (1,branching_delay) else : timer -= 1 return 0,timer def create_randomized_delayed_axis(nid, n, anchors=anchors): """create an axis of length n using the delayed markov and randomized the id of the branching points in anchors around the theoretical branching positions :Parameters: - nid: root node for the axis - n : number of vertices for this axis - anchors: future ramification points on this axis :param nid: :param n: :param anchors: (Default value = anchors) """ n = int(n) axis = [] shuffled_axis = [] branch, time = delayed_markov(0) for i in range(n-1): branch, time = delayed_markov(time) axis.append((branch, n-i)) shuffled_axis.append((branch,n-i)) for i in range(n-1): # shift (1-branching_stability) branching points # at max (1-branching_stability)*branching delay away from # theoretical branching position if (axis[i][0] == 1): # read 'axis' only to avoid treating the same branching point after each shift if 1 : #random.random() < branching_variability : var = int(round(branching_variability*branching_delay)) shift = random.randint(-var,var) print('shift ', shift, i) # shift occurs only if the target is not branched already or outside the axis if ((i+shift)>0) and ((i+shift)<n-1) and (shuffled_axis[i+shift][0]==0) : b, p = shuffled_axis[i] shuffled_axis[i] = (0, p) shuffled_axis[i+shift] = (1, p) for ramif, position in shuffled_axis: order = nid.order nid = nid.add_child(order=order, edge_type='<') nid.position_index = position if ramif : anchors.append(nid) def update_randomized_delayed_axis(vid, anchors=anchors): """create an axis of length n using the delayed markov and randomized the id of the branching points in anchors around the theoretical branching positions :Parameters: - nid: root node for the axis - n : number of vertices for this axis - anchors: future ramification points on this axis :param vid: :param anchors: (Default value = anchors) """ _axis = list(algo.descendants(g,vid,RestrictedTo='SameAxis')) n = len(_axis) print('AXIS ', _axis) nid = g.node(vid) assert nid.order == 1 axis = [] shuffled_axis = [] branch, time = delayed_markov(0) for i in range(n-1): branch, time = delayed_markov(time) axis.append((branch, n-i)) shuffled_axis.append((branch,n-i)) for i in range(n-1): # shift (1-branching_stability) branching points # at max (1-branching_stability)*branching delay away from # theoretical branching position if (axis[i][0] == 1): # read 'axis' only to avoid treating the same branching point after each shift if 1 : #random.random() < branching_variability : var = int(round(branching_variability*branching_delay)) shift = random.randint(-var,var) # shift occurs only if the target is not branched already or outside the axis if ((i+shift)>0) and ((i+shift)<n-1) and (shuffled_axis[i+shift][0]==0) : b, p = shuffled_axis[i] shuffled_axis[i] = (0, p) shuffled_axis[i+shift] = (1, p) for index, (ramif, position) in enumerate(shuffled_axis): if ramif : aid = _axis[index+1] nid = g.node(aid) anchors.append(nid) if SUPERIOR_ORDER: # Update the ramification of order 1 for v in g: if g.edge_type(v) == '+': print('ORDER: ', v, g.order(v)) update_randomized_delayed_axis(v) print('ANCHORS ', anchors) while anchors: # while they are branching point left nid = anchors.pop(0) # take next branching point if nid.order == 2: print(nid, nid.position_index) position_index = nid.position_index # distance to the tip #print position_index if nid.order < order_max: # check if maximal branching order was reached # if there is a length law, use it to compute lateral root length at this position if length_law: lateral_length = int(length_law(position_index)) else : # standard lateral root length - can't be longer than the bearing axis remaining branching length (remaining length - nude tip length) n = len(list(algo.descendants(g,nid._vid,RestrictedTo='SameAxis'))) #n = random.randint(1, max(n-nude_tip_length,1)) n = max(n-nude_tip_length,1) lateral_length = n-1 # create axis of appropriate length if lateral_length > 0: # branching_variability also apply to the length of LR var = int(lateral_length*branching_variability) lateral_length = random.randint(max(1,lateral_length-var), lateral_length+var) # Create the first node of the branching point and the corresponding axis cid = nid.add_child(order=nid.order+1, edge_type='+') #print "pid length", nid, lateral_length create_randomized_delayed_axis(cid, lateral_length) g = fat_mtg(g) print('branching_delay ', branching_delay) print('max_order', max(g.property('order').values())) return g
[docs] def mtg_from_aqua_data(df, segment_length=1e-4): # Added F. Bauget 2019-12-19 """ Reconstruct MTG from pandas DataFrame with the following columns: * 1st col: 'db' (float) distance from base in m on the parent root where starts the lateral root * 2nd col: 'lr' (float) length in m of the corresponding lateral root * 3d col: 'order' (string) = '1' if parent root is the primary root, = '1-n' if the parent root is a lateral root that starts at the node n on the parent root :param df: pandas (DataFrame) :param segment_length: (float) length of the vertices in hydroroot should be in m (Default value = 1e-4) :returns: - g: MTG with the following properties set: edge_type, label, base_length, length .. note:: At this stage 2022-08-23 tested with a maximum order of 2 .. seealso:: :func:`hydroroot.read_file.read_archi_data` """ g = MTG() rid = vid = g.add_component(g.root) rnid = g.node(rid) rnid.base_length = 0. rnid.length = segment_length rnid.label = 'S' rnid.order = 0 # Primary root prev_len = 0. ramifs = {} # variable (nb racine (1 first), index entry) [(index vertice node, lenght lat )] df_order = df[df.order == '1'] # array with 1st root length_base = df_order.index if 'radius' in df_order: # F. Bauget 2020-11-02 : added the possibility to set real radii PR_radius = df_order.iloc[-1].radius # F. Bauget 2022-06-28: error was df instead of df_order rnid.radius = PR_radius # path = [1] count = 0 for i in length_base: len_base = df_order.iloc[i].db code = '1' while len_base - prev_len > 0: # we add segment of segment_length till the next vertice => no lateral root yet so the edge_type is '<' prev_len += segment_length if 'radius' in df_order: # F. Bauget 2020-11-02 : added the possibility to set real radii vid = g.add_child(vid, edge_type = '<', label = 'S', base_length = prev_len, length = segment_length, order = 0, code = code, radius = PR_radius) else: vid = g.add_child(vid, edge_type = '<', label = 'S', base_length = prev_len, length = segment_length, order = 0, code = code) # Modification Decamber 2019by Fabrice: problem was that two following vertice may have the same base_length causing wrong g.property('position') recalculation in the conductance calculation # did not pass test_reconstruct_from_aqua_data in test_archi_data.py # vid = g.add_child(vid, edge_type='<', label='S', base_length=len_base, length=segment_length, order=0) # prev_len = len_base # the last added vertice may be > than the first node so we change len_base which is to big len_lateral = df_order.iloc[i].lr if len_lateral > 0.: count += 1 if 'radius' in df_order: # F. Bauget 2020-11-02 : added the possibility to set real radii r = df_order.iloc[i].radius p = tuple([1, count, 0.]) # 1: PR, count: countieme RL ramifs.setdefault(p, []).append((vid, len_lateral, r)) # randomly added, to sort it sorted(ramifs) else: p = tuple([1, count]) # 1: PR, count: countieme RL ramifs.setdefault(p, []).append((vid, len_lateral)) # randomly added, to sort it sorted(ramifs) # F. Bauget 2020-06-11 : error in order which is the lateral order or the number of edges + between root and the # vertices on the lateral of this order _order = 1 while ramifs: ramifs = add_branching(g, df, ramifs = ramifs, Order = _order, segment_length = segment_length) _order += 1 # ramifs = add_branching(g, df, ramifs = ramifs, Order = 1, segment_length = segment_length) # # ramifs = add_branching(g, df, ramifs = ramifs, Order = 3, segment_length = segment_length) return g
[docs] def add_branching(g, df, ramifs = None, Order = 0, segment_length = 1e-4): """add branching of a given order on the previous order called by :func:`mtg_from_aqua_data` :param g: MTG :param df: (DataFrame) :param ramifs: (dict) - dict of a list with tuple (vid, lr) vid is the vertex index on the parent root from which the lateral of length lr starts (Default value = None) :param Order: (int) - the order of the new branching (Default value = 0) :param segment_length: (float) - length in m of the vertices (Default value = 1e-4) :returns: - new_ramifs: dict to used as the ramifs parameter for a new call of add_branching """ new_ramifs = {} len_base = 0 # count = 0 # F. Bauget 2020-06-11 : comment same date below for path in ramifs: if 'radius' in df: # F. Bauget 2020-11-02 vid, lr, r = ramifs[path][0] else: vid, lr = ramifs[path][0] # vid is the vertice index on the parent root from which the lateral of length lr starts # order = '-'.join(map(str, path)) # F. Bauget 2022-06-27 order = '-'.join(map(str, path[:2])) df_order = df[df.order == order] length_base = df_order.index code = order len_base = lr prev_len = 0. _root_id = vid parent_base = g.node(vid).base_length if df_order.empty: while len_base - prev_len > 0: prev_len += segment_length edge_type = '+' if _root_id == vid else '<' if 'radius' in df_order: # F. Bauget 2020-11-02 : added the possibility to set real radii vid = g.add_child(vid, edge_type = edge_type, label = 'S', base_length = parent_base + prev_len, length = segment_length, order = Order, code = code, radius = r) else: vid = g.add_child(vid, edge_type = edge_type, label = 'S', base_length = parent_base + prev_len, length = segment_length, order = Order, code = code) else: count = 0 # F. Bauget 2020-06-11 : this is the count of laterals on a root so at each new root with lateral should be reset to 0 for i in length_base: len_base = df_order.db[i] edge_type = '+' if _root_id == vid else '<' while len_base - prev_len > 0: prev_len += segment_length if 'radius' in df_order: # F. Bauget 2020-11-02 : added the possibility to set real radii vid = g.add_child(vid, edge_type = edge_type, label = 'S', base_length = parent_base + prev_len, length = segment_length, order = Order, code = code, radius = r) else: vid = g.add_child(vid, edge_type = edge_type, label = 'S', base_length = parent_base + prev_len, length = segment_length, order = Order, code = code) edge_type = '<' #Modification December 2019 by Fabrice: problem was that two following vertice may have the same base_length causing wrong g.property('position') recalculation in the conductance calculation # did not pass test_reconstruct_from_aqua_data in test_archi_data.py # vid = g.add_child(vid, edge_type='<', label='S', base_length=parent_base+len_base+segment_length, length=segment_length, order=1, code=code) # prev_len = len_base len_lateral = df_order.lr[i] if len_lateral > 0.: count += 1 if 'radius' in df_order: # F. Bauget 2020-11-02 : added the possibility to set real radii # r = df_order.iloc[i].radius # F. Bauget 2022-06-27 r_lrII = df_order.radius[i] # F. Bauget 2022-06-27 p = tuple(['-'.join(map(str, path)), count, 0.]) # 1: PR, count: countieme RL # ramifs.setdefault(p, []).append((vid, len_lateral, r)) # randomly added, to sort it sorted(ramifs) # F. Bauget 2022-06-27 new_ramifs.setdefault(p, []).append((vid, len_lateral, r_lrII)) # randomly added, to sort it sorted(ramifs) else: p = tuple(['-'.join(map(str, path)), count]) new_ramifs.setdefault(p, []).append((vid, len_lateral)) return new_ramifs