MultiScaleTreeGraph.jl functions

Here is a list of all exported functions from MultiScaleTreeGraph.jl. For more details, click on the link and you'll be directed to the function help.

DataFrames.DataFrameMethod
DataFrame(mtg::Node)
DataFrame(mtg::Node, key)

Convert an MTG into a DataFrame.

Arguments

  • mtg::Node: An mtg node (usually the root node).
  • key: The attribute(s) name(s). Select a list of variables given either as a Symbol

(faster), a String, or an Array of (or a Tuple).

Examples

# Importing an mtg from the package:
file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)

# Full DataFrame:
DataFrame(mtg)

# Select just :Length:
DataFrame(mtg, :Length)

# Select just :Length and :Width:
DataFrame(mtg, [:Length, :Width])
source
MetaGraphsNext.MetaGraphMethod
MetaGraph(g::Node)

Convert an MTG into a MetaGraph.

Examples

# Importing an mtg from the package:
file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)

MetaGraph(mtg)
source
MultiScaleTreeGraph.MutableNodeMTGType
NodeMTG(link, symbol, index, scale)
MutableNodeMTG(link, symbol, index, scale)

NodeMTG structure

Builds an MTG node to hold data about the link to the previous node, the symbol of the node, and its index.

Note

  • The symbol should match the possible values listed in the SYMBOL column of the CLASSES section

in the mtg file if read from a file.

  • The index is totaly free, and can be used as a way to e.g. keep track of the branching order.
NodeMTG("<", "Leaf", 2, 0)
source
MultiScaleTreeGraph.NodeType
Node(MTG<:AbstractNodeMTG)
Node(parent::Node, MTG<:AbstractNodeMTG)
Node(id::Int, MTG<:AbstractNodeMTG, attributes)
Node(id::Int, parent::Node, MTG<:AbstractNodeMTG, attributes)
Node(id::Int, parent::Node, children::Vector{Node}, MTG<:AbstractNodeMTG, attributes)
Node(
    id::Int,
    parent::Node,
    children::Vector{Node},
    MTG<:AbstractNodeMTG,
    attributes;
    traversal_cache
)

Type that defines an MTG node (i.e. an element) with:

  • id: The unique id of node (unique in the whole MTG)
  • parent: the parent node (if not the root node)
  • children: an optional array of children nodes
  • MTG: the MTG description, or encoding (see NodeMTG or

MutableNodeMTG)

  • attributes: the node attributes, that can be anything but

usually a Dict{String,Any}

  • traversal_cache: a cache for the traversal, used by e.g. traverse to traverse more efficiently particular nodes in the MTG

The node is an entry point to a Mutli-Scale Tree Graph, meaning we can move through the MTG from any of its node. The root node is the node without parent. A leaf node is a node without any children. Root and leaf nodes are used with their computer science meaning throughout the package, not in the biological sense.

Note that it is possible to create a whole MTG using only the Node type, because it has methods to create a node as a child of another node (see example below).

Examples

mtg = Node(NodeMTG("/", "Plant", 1, 1))
internode = Node(mtg, NodeMTG("/", "Internode", 1, 2))
# Note that the node is created with a parent, so it is not necessary to add it as a child of the `mtg ` Node

mtg
source
MultiScaleTreeGraph.NodeMTGType
NodeMTG(link, symbol, index, scale)
MutableNodeMTG(link, symbol, index, scale)

NodeMTG structure

Builds an MTG node to hold data about the link to the previous node, the symbol of the node, and its index.

Note

  • The symbol should match the possible values listed in the SYMBOL column of the CLASSES section

in the mtg file if read from a file.

  • The index is totaly free, and can be used as a way to e.g. keep track of the branching order.
NodeMTG("<", "Leaf", 2, 0)
source
AbstractTrees.parentMethod
AbstractTrees.parent(node::Node{T,A})

Get the parent of a MultiScaleTreeGraph node. If the node is the root, it returns nothing.

See also reparent! to update the parent of a node.

source
Base.:==Method
==(a::Node, b::Node)

Test AbstractNodeMTG equality.

source
Base.:==Method
==(a::Node, b::Node)

Test Node equality. The parent, children and siblings are not tested, only their id is.

source
Base.append!Method
append!(node::Node{M<:AbstractNodeMTG, <:MutableNamedTuple, GenericNode}, attr)
append!(node::Node{M<:AbstractNodeMTG, <:Dict, GenericNode}, attr)

Append new attributes to a node attributes.

source
Base.getindexMethod

Indexing Node attributes from node, e.g. node[:length] or node["length"]

source
Base.lengthMethod

Returns the length of the subtree below the node (including it)

source
Base.parentMethod
Base.parent(node::Node{T,A})

Get the parent of a MultiScaleTreeGraph node. If the node is the root, it returns nothing.

See also reparent! to update the parent of a node.

source
Base.printMethod

Print a node to io using an UTF-8 formatted representation of the tree. Most of the code from DataTrees.jl

Examples

file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)
mtg
# / 1: $
# └─ / 2: Individual
#    └─ / 3: Axis
#       └─ / 4: Internode
#          ├─ + 5: Leaf
#          └─ < 6: Internode
#             └─ + 7: Leaf
source
DataFrames.select!Method
select!(node::Node, args..., <keyword arguments>)
select(node::Node, args..., <keyword arguments>)

Delete all attributes not selected in args..., and optionally apply transformations on the fly on the selected variables. This function works similarly to transform! except it keeps only the selected variables, while transform! add new variables.

See the documentation of transform! for more details on the format of args and on how to use the arguments.

This function adds one more form to args...: a variable name to just select a variable.

Examples

file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)

select!(mtg, :Length => (x -> x / 10) => :length_m, :Width, ignore_nothing = true)
source
DataFrames.transformFunction
transform!(node::Node, args..., <keyword arguments>)
transform(node::Node, args..., <keyword arguments>)

Transform (mutate) an MTG (node) in place (transform!) or on a copy (transform) to add attributes specified by args....

Arguments

  • node::Node: An MTG node (e.g. the whole mtg returned by read_mtg()).

  • args::Any: the transformations (see details)

  • <keyword arguments>:

    • scale = nothing: The scale to filter-in (i.e. to keep). Usually a Tuple-alike of integers.
    • symbol = nothing: The symbol to filter-in. Usually a Tuple-alike of Strings.
    • link = nothing: The link with the previous node to filter-in. Usually a Tuple-alike of Char.
    • filter_fun = nothing: Any filtering function taking a node as input, e.g. isleaf.
    • ignore_nothing = false: filter-out the nodes with nothing values for the given

    attributes used as inputs (apply only to the form :var_name => ...)

Returns

transform!: Nothing, mutates the (sub-)tree in-place. transform: A mutated copy of node.

Notes

Carefull, transform is much slower than transform! because it makes a copy of the whole MTG each time.

Details

The interface of the function is inspired from the one used in DataFrames.jl, but adapted to an MTG.

The args... provided can be of the following forms:

  1. a :var_name => :new_var_name pair. This form is used to rename an attribute name
  2. a :var_name => function => :new_var_name or

[:var_name1, :var_name2...] => function => :new_var_name pair. The variables are declared as a Symbol or a String (or a vector of), and they are passed as positional arguments to the function. The new attribute name is optional and is automatically generated if not provided by concatenating the source column name(s) and the function name if any.

  1. a function => :new_var_name form that applies a function to a node and puts the results

in a new attribute. This form is usually applied when searching ancestors or descendants values.

  1. a function form that applies a mutating function to a node, without expecting any output.

This form is adapted when using a function that already mutates the node, without the need to return anything, e.g. branching_order!.

Carefull to the form you use! Form 2 expect a function that takes one or more node attributes (== variables) as inputs, while form 3 and 4 expect a function that takes a node.

Examples

file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)

# We can use transform to apply a function over all nodes (same as using [`traverse!`](@ref))
transform!(mtg,  node -> isleaf(node) ? println(node_id(x)," is a leaf") : nothing)
node_5 is a leaf
node_7 is a leaf

# We can compute a new variable based on another. For example to know if the value of the
# `:Length` attribute is provided or not, we can do:
transform!(mtg, :Length => isnothing)
# To check the values we first call [`get_attributes`](@ref) to know the new variable name:
get_attributes(mtg)
# And then we get the values using [`descendants`](@ref)
descendants(mtg, :Length_isnothing, self = true)
# Or DataFrame:
DataFrame(mtg, :Length_isnothing)

# We can also set the attribute name ourselves like so:
transform!(mtg, :Length => isnothing => :no_length)
descendants(mtg, :no_length, self = true)

# We can provide anonymous functions if we want to:
transform!(mtg, :Length => (x -> isnothing(x)) => :no_length)
descendants(mtg, :no_length, self = true)

# When a node does not have an attribute, it returns `nothing`. Most basic functions do not
# handle those very well, e.g.:
transform!(mtg, :Length => log)
# It does not work because some nodes have no value for `:Length`.
# To remove automatically the nodes with `nothing` values, use `ignore_nothing`:
transform!(mtg, :Length => log => :log_length, ignore_nothing = true)
descendants(mtg, :log_length, self = true)

# Or you could handle these manually in your function if you prefer:
transform!(mtg, :Length => (x -> x === nothing ? nothing : log(x)) => :log_length2)
descendants(mtg, :log_length2, self = true)

# Another way is to give a filtering function as an argument:
transform!(mtg, :Length => log => :log_length, filter_fun = x -> x[:Length] !== nothing)

# We can use more than one attribute as input to our function like so:
transform!(
    mtg,
    [:Width, :Length] => ((x, y) -> (x/2)^2 * π * y) => :volume,
    filter_fun = x -> x[:Length] !== nothing && x[:Width] !== nothing
)
descendants(mtg, :volume, self = true)

# Note that `filter_fun` filter the node, so we use the node[:attribute] notation here.

# We can also chain operations, and they will be executed sequentially so we can use variables
# computed on the instruction just before:
density = 0.6
transform!(
    mtg,
    [:Width, :Length] => ((x, y) -> (x/2)^2 * π * y) => :vol,
    :vol => (x -> x * density) => :biomass,
    filter_fun = x -> x[:Length] !== nothing && x[:Width] !== nothing
)
DataFrame(mtg, [:vol, :biomass])

# We can also rename a variable like so:
transform!(
    mtg,
    :biomass => :mass,
    filter_fun = x -> x[:Length] !== nothing && x[:Width] !== nothing
)
DataFrame(mtg, [:vol, :mass])

# Finally, we can use variables from ancestors/descendants using the `function => :new_var` form:
function get_mass_descendants(x)
    masses = descendants(x, :mass, ignore_nothing = true)
    if length(masses) == 0
        nothing
    else
        sum(masses)
    end
end

transform!(
    mtg,
    get_mass_descendants => :mass_beared
)
DataFrame(mtg, [:mass, :mass_beared])
source
DataFrames.transform!Function
transform!(node::Node, args..., <keyword arguments>)
transform(node::Node, args..., <keyword arguments>)

Transform (mutate) an MTG (node) in place (transform!) or on a copy (transform) to add attributes specified by args....

Arguments

  • node::Node: An MTG node (e.g. the whole mtg returned by read_mtg()).

  • args::Any: the transformations (see details)

  • <keyword arguments>:

    • scale = nothing: The scale to filter-in (i.e. to keep). Usually a Tuple-alike of integers.
    • symbol = nothing: The symbol to filter-in. Usually a Tuple-alike of Strings.
    • link = nothing: The link with the previous node to filter-in. Usually a Tuple-alike of Char.
    • filter_fun = nothing: Any filtering function taking a node as input, e.g. isleaf.
    • ignore_nothing = false: filter-out the nodes with nothing values for the given

    attributes used as inputs (apply only to the form :var_name => ...)

Returns

transform!: Nothing, mutates the (sub-)tree in-place. transform: A mutated copy of node.

Notes

Carefull, transform is much slower than transform! because it makes a copy of the whole MTG each time.

Details

The interface of the function is inspired from the one used in DataFrames.jl, but adapted to an MTG.

The args... provided can be of the following forms:

  1. a :var_name => :new_var_name pair. This form is used to rename an attribute name
  2. a :var_name => function => :new_var_name or

[:var_name1, :var_name2...] => function => :new_var_name pair. The variables are declared as a Symbol or a String (or a vector of), and they are passed as positional arguments to the function. The new attribute name is optional and is automatically generated if not provided by concatenating the source column name(s) and the function name if any.

  1. a function => :new_var_name form that applies a function to a node and puts the results

in a new attribute. This form is usually applied when searching ancestors or descendants values.

  1. a function form that applies a mutating function to a node, without expecting any output.

This form is adapted when using a function that already mutates the node, without the need to return anything, e.g. branching_order!.

Carefull to the form you use! Form 2 expect a function that takes one or more node attributes (== variables) as inputs, while form 3 and 4 expect a function that takes a node.

Examples

file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)

# We can use transform to apply a function over all nodes (same as using [`traverse!`](@ref))
transform!(mtg,  node -> isleaf(node) ? println(node_id(x)," is a leaf") : nothing)
node_5 is a leaf
node_7 is a leaf

# We can compute a new variable based on another. For example to know if the value of the
# `:Length` attribute is provided or not, we can do:
transform!(mtg, :Length => isnothing)
# To check the values we first call [`get_attributes`](@ref) to know the new variable name:
get_attributes(mtg)
# And then we get the values using [`descendants`](@ref)
descendants(mtg, :Length_isnothing, self = true)
# Or DataFrame:
DataFrame(mtg, :Length_isnothing)

# We can also set the attribute name ourselves like so:
transform!(mtg, :Length => isnothing => :no_length)
descendants(mtg, :no_length, self = true)

# We can provide anonymous functions if we want to:
transform!(mtg, :Length => (x -> isnothing(x)) => :no_length)
descendants(mtg, :no_length, self = true)

# When a node does not have an attribute, it returns `nothing`. Most basic functions do not
# handle those very well, e.g.:
transform!(mtg, :Length => log)
# It does not work because some nodes have no value for `:Length`.
# To remove automatically the nodes with `nothing` values, use `ignore_nothing`:
transform!(mtg, :Length => log => :log_length, ignore_nothing = true)
descendants(mtg, :log_length, self = true)

# Or you could handle these manually in your function if you prefer:
transform!(mtg, :Length => (x -> x === nothing ? nothing : log(x)) => :log_length2)
descendants(mtg, :log_length2, self = true)

# Another way is to give a filtering function as an argument:
transform!(mtg, :Length => log => :log_length, filter_fun = x -> x[:Length] !== nothing)

# We can use more than one attribute as input to our function like so:
transform!(
    mtg,
    [:Width, :Length] => ((x, y) -> (x/2)^2 * π * y) => :volume,
    filter_fun = x -> x[:Length] !== nothing && x[:Width] !== nothing
)
descendants(mtg, :volume, self = true)

# Note that `filter_fun` filter the node, so we use the node[:attribute] notation here.

# We can also chain operations, and they will be executed sequentially so we can use variables
# computed on the instruction just before:
density = 0.6
transform!(
    mtg,
    [:Width, :Length] => ((x, y) -> (x/2)^2 * π * y) => :vol,
    :vol => (x -> x * density) => :biomass,
    filter_fun = x -> x[:Length] !== nothing && x[:Width] !== nothing
)
DataFrame(mtg, [:vol, :biomass])

# We can also rename a variable like so:
transform!(
    mtg,
    :biomass => :mass,
    filter_fun = x -> x[:Length] !== nothing && x[:Width] !== nothing
)
DataFrame(mtg, [:vol, :mass])

# Finally, we can use variables from ancestors/descendants using the `function => :new_var` form:
function get_mass_descendants(x)
    masses = descendants(x, :mass, ignore_nothing = true)
    if length(masses) == 0
        nothing
    else
        sum(masses)
    end
end

transform!(
    mtg,
    get_mass_descendants => :mass_beared
)
DataFrame(mtg, [:mass, :mass_beared])
source
MultiScaleTreeGraph.addchild!Method
addchild!(p::Node, id::Int, MTG<:AbstractNodeMTG, attributes)
addchild!(p::Node, MTG<:AbstractNodeMTG, attributes)
addchild!(p::Node, MTG<:AbstractNodeMTG)
addchild!(p::Node, child::Node; force=false)

Add a new child to a parent node (p), and add the parent node as the parent. Returns the child node.

See also insert_child!, or directly Node where we can pass the parent, and it uses addchild! under the hood.

Examples

# Create a root node:
mtg = MultiScaleTreeGraph.Node(
    NodeMTG("/", "Plant", 1, 1),
    Dict{Symbol,Any}()
)

roots = addchild!(
    mtg, 
    NodeMTG("+", "RootSystem", 1, 2)
)

stem = addchild!(
    mtg, 
    NodeMTG("+", "Stem", 1, 2)
)

phyto = addchild!(
    stem, 
    NodeMTG("/", "Phytomer", 1, 3)
)

mtg
source
MultiScaleTreeGraph.ancestorsMethod
ancestors(node::Node,key,<keyword arguments>)
ancestors(node::Node,<keyword arguments>)

Get attribute values from the ancestors (basipetal), or the ancestor nodes that are not filtered-out.

Arguments

Mandatory arguments

  • node::Node: The node to start at.
  • key: The key, or attribute name. It is only mandatory for the first method that search for attributes values. The second method returns the node directly.

Make it a Symbol for faster computation time.

Keyword Arguments

  • scale = nothing: The scale to filter-in (i.e. to keep). Usually a Tuple-alike of integers.
  • symbol = nothing: The symbol to filter-in. Usually a Tuple-alike of Strings.
  • link = nothing: The link with the previous node to filter-in. Usually a Tuple-alike of Char.
  • all::Bool = true: Return all filtered-in nodes (true), or stop at the first node that

is filtered out (false).

  • self = false: is the value for the current node needed ?
  • filter_fun = nothing: Any filtering function taking a node as input, e.g. isleaf.
  • recursivity_level = -1: The maximum number of recursions allowed (considering filters).

E.g. to get the parent only: recursivity_level = 1, for parent + grand-parent: recursivity_level = 2. If a negative value is provided (the default), the function returns all valid values from the node to the root.

  • ignore_nothing = false: filter-out the nodes with nothing values for the given key
  • type::Union{Union,DataType}: The type of the attribute. Makes the function run much

faster if provided (≈4x faster).

Note

In most cases, the type argument should be given as a union of Nothing and the data type of the attribute to manage missing or inexistant data, e.g. measurements made at one scale only. See examples for more details.

Examples

# Importing an example mtg from the package:
file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)

# Using a leaf node from the mtg:
leaf_node = get_node(mtg, 5)

ancestors(leaf_node, :Length) # Short to write, but slower to execute

# Fast version, note that we pass a union of Nothing and Float64 because there are some nodes
# without a `Length` attribute:
ancestors(leaf_node, :Length, type = Union{Nothing,Float64})

# Filter by scale:
ancestors(leaf_node, :XX, scale = 1, type = Float64)
ancestors(leaf_node, :Length, scale = 3, type = Float64)

# Filter by symbol:
ancestors(leaf_node, :Length, symbol = "Internode")
ancestors(leaf_node, :Length, symbol = ("Axis","Internode"))
source
MultiScaleTreeGraph.branching_order!Method
branching_order!(mtg; ascend = true)

Compute the topological branching order of the nodes in an mtg.

Arguments

  • mtg: the mtg, e.g. output from read_mtg()
  • ascend: If true, the order is computed from the base (acropetal), if false,

it is computed from the tip (basipetal).

Notes

The order of a node is computed from the maximum order of their children when using the basipetal computation.

Examples

file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)
branching_order!(mtg)
DataFrame(mtg, :branching_order)
# 7×2 DataFrame
#  Row │ tree                        branching_order
#      │ String                      Int64
# ─────┼───────────────────────────────────────────────
#    1 │ / 1: $                                      1
#    2 │ └─ / 2: Individual                          1
#    3 │    └─ / 3: Axis                             1
#    4 │       └─ / 4: Internode                     1
#    5 │          ├─ + 5: Leaf                       2
#    6 │          └─ < 6: Internode                  1
#    7 │             └─ + 7: Leaf                    2

branching_order!(mtg, ascend = false)
DataFrame(mtg, :branching_order)
# 7×2 DataFrame
#  Row │ tree                        branching_order
#      │ String                      Int64
# ─────┼───────────────────────────────────────────────
#    1 │ / 1: $                                      2
#    2 │ └─ / 2: Individual                          2
#    3 │    └─ / 3: Axis                             2
#    4 │       └─ / 4: Internode                     2
#    5 │          ├─ + 5: Leaf                       1
#    6 │          └─ < 6: Internode                  2
#    7 │             └─ + 7: Leaf                    1
source
MultiScaleTreeGraph.cache_nodes!Method
cache_nodes!(node; scale=nothing, symbol=nothing, link=nothing, filter_fun=nothing, overwrite=false)

Cache the nodes of the mtg based on the filters that would be applied to a traversal. This is automatically usually for traversal then when using traverse! or transform!.

Examples

file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))), "test", "files", "simple_plant.mtg")
mtg = read_mtg(file, Dict)

# Cache all leaf nodes:
cache_nodes!(mtg, symbol="Leaf")

# Cached nodes are stored in the traversal_cache field of the mtg (here, the two leaves):
@test MultiScaleTreeGraph.node_traversal_cache(mtg)["_cache_c0bffb8cc8a9b075e40d26be9c2cac6349f2a790"] == [get_node(mtg, 5), get_node(mtg, 7)]

# Then you can use the cached nodes in a traversal:
traverse(mtg, x -> symbol(x), symbol="Leaf") == ["Leaf", "Leaf"]
source
MultiScaleTreeGraph.check_filtersMethod
check_filters(node; scale = nothing, symbol = nothing, link = nothing)

Check if the filters are consistant with the mtg onto which they are applied

Examples

check_filters(mtg, scale = 1)
check_filters(mtg, scale = (1,2))
check_filters(mtg, scale = (1,2), symbol = "Leaf", link = "<")
source
MultiScaleTreeGraph.delete_node!Method

deletenode!(node; childlinkfun = newchild_link)

Delete a node and re-parent the children to its own parent.

If the node is a root and it has only one child, the child becomes the root, if it has several children, it returns an error.

child_link_fun is a function that takes the child node of a deleted node as input and returns its new link. The default function is new_child_link, which tries to be clever considering the parent and child links. See its help page for more information. If the link shouldn't be modified, use the link function instead.

The function returns the parent node (or the new root if the node is a root)

source
MultiScaleTreeGraph.delete_nodes!Method

delete_nodes!(mtg::Node,<keyword arguments>)

Delete nodes in mtg following filters rules.

Arguments

Mandatory arguments

  • node::Node: The node to start at.

Keyword Arguments (filters)

  • scale = nothing: The scale to delete. Usually a Tuple-alike of integers.
  • symbol = nothing: The symbol to delete. Usually a Tuple-alike of Strings.
  • link = nothing: The link with the previous node to delete. Usually a Tuple-alike of Char.
  • all::Bool = true: Continue after the first deletion (true), or stop?
  • filter_fun = nothing: Any filtering function taking a node as input, e.g. isleaf

to decide whether to delete a node or not.

  • child_link_fun = new_child_link: a function that takes the child node of a deleted node

as input and returns its new link (see details).

Notes

  1. The function is acropetal, meaning it will apply the deletion from leaves to the root to ensure

that one pass is enough and we don't repeat the process of visiting already visited children.

  1. The function does not do anything fancy, it let the user take care of its own rules when

deleting nodes. So if you delete a branching node, the whole subtree will be modified and take the link of the children. This process is left to the user becaue it highly depends on the mtg structure.

  1. The package provides some pre-made functions for filtering. See for example is_segment!

to re-compute the mtg at a given scale to have only nodes at branching points. This is often used to match automatic reconstructions from e.g. LiDAR point cloud with manual measurements.

  1. The default function used for child_link_fun is new_child_link, which tries to be

clever considering the parent and child links. See its help page for more information. If the link shouldn't be modified, use the link function instead.

Examples

file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)

delete_nodes!(mtg, scale = 2) # Will remove all nodes of scale 2

# Delete the leaves:
delete_nodes!(mtg, symbol = "Leaf")
# Delete the leaves and internodes:
delete_nodes!(mtg, symbol = ("Leaf","Internode"))
source
MultiScaleTreeGraph.descendantsFunction
descendants(node::Node,key,<keyword arguments>)
descendants(node::Node,<keyword arguments>)
descendants!(node::Node,key,<keyword arguments>)

Get attribute values from the descendants of the node (acropetal). The first method returns an array of values, the second an array of nodes that respect the filters, and the third the mutating version of the first one that caches the results in the mtg.

The mutating version (descendants!) cache the results in a cached variable named after the hash of the function call. This version is way faster when descendants is called repeateadly for the same computation on large trees, but require to clean the chache sometimes (see clean_cache!). It also only works for trees with attributes of subtype of AbstractDict.

Arguments

Mandatory arguments

  • node::Node: The node to start at.
  • key: The key, or attribute name (only mandatory for the first and third methods). Make it a Symbol for faster computation time.

Keyword Arguments

  • scale = nothing: The scale to filter-in (i.e. to keep). Usually a Tuple-alike of integers.
  • symbol = nothing: The symbol to filter-in. Usually a Tuple-alike of Strings.
  • link = nothing: The link with the previous node to filter-in. Usually a Tuple-alike of Char.
  • all::Bool = true: Return all filtered-in nodes (true), or stop at the first node that

is filtered out (false).

  • self = false: is the value for the current node needed ?
  • filter_fun = nothing: Any filtering function taking a node as input, e.g. isleaf.
  • recursivity_level = Inf: The maximum number of recursions allowed (considering filters).

E.g. to get the first level children only: recursivity_level = 1, for children + grand-children: recursivity_level = 2. If Inf (the default) or a negative value is provided, there is no recursion limitation.

  • ignore_nothing = false: filter-out the nodes with nothing values for the given key
  • type::Union{Union,DataType}: The type of the attribute. Can make the function run much

faster if provided (e.g. ≈4x faster).

Tips

To get the values of the leaves use isleaf as the filtering function, e.g.: descendants(mtg, :Width; filter_fun = isleaf).

Note

In most cases, the type argument should be given as a union of Nothing and the data type of the attribute to manage missing or inexistant data, e.g. measurements made at one scale only. See examples for more details.

Examples

# Importing the mtg from the github repo:
file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)

descendants(mtg, :Length) # Short to write, but slower to execute

# Fast version, note that we pass a union of Nothing and Float64 because there are some nodes
# without a `Length` attribute:
descendants(mtg, :Length, type = Union{Nothing,Float64})

# Filter by scale:
descendants(mtg, :XEuler, scale = 3, type = Union{Nothing, Float64})
descendants(mtg, :Length, scale = 3, type = Float64) # No `nothing` value here, no need of a union type

# Filter by symbol:
descendants(mtg, :Length, symbol = "Leaf")
descendants(mtg, :Length, symbol = ("Leaf","Internode"))

# Filter by function, e.g. get the values for the leaves only:
descendants(mtg, :Width; filter_fun = isleaf)

# You can also ask for different attributes by passing them as a vector:
descendants(mtg, [:Width, :Length]; filter_fun = isleaf)
# The output is an array of arrays of length of the attributes you asked for.

# It is possible to cache the results in the mtg using the mutating version `descendants!` (note the `!` 
# at the end of the function name):
transform!(mtg, node -> sum(descendants!(node, :Length)) => :subtree_length, symbol = "Internode")

# Or using `@mutate_mtg!` instead of `transform!`:
@mutate_mtg!(mtg, subtree_length = sum(descendants!(node, :Length)), symbol = "Internode")

# The cache is stored in a temporary variable with a name that starts with `_cache_` followed by the SHA
# of the function call, *e.g.*: `:_cache_5c1e97a3af343ce623cbe83befc851092ca61c8d`:
node_attributes(mtg[1][1][1])

# You can then clean the cache to avoid using too much memory:
clean_cache!(mtg)
node_attributes(mtg[1][1][1])
source
MultiScaleTreeGraph.descendants!Function
descendants(node::Node,key,<keyword arguments>)
descendants(node::Node,<keyword arguments>)
descendants!(node::Node,key,<keyword arguments>)

Get attribute values from the descendants of the node (acropetal). The first method returns an array of values, the second an array of nodes that respect the filters, and the third the mutating version of the first one that caches the results in the mtg.

The mutating version (descendants!) cache the results in a cached variable named after the hash of the function call. This version is way faster when descendants is called repeateadly for the same computation on large trees, but require to clean the chache sometimes (see clean_cache!). It also only works for trees with attributes of subtype of AbstractDict.

Arguments

Mandatory arguments

  • node::Node: The node to start at.
  • key: The key, or attribute name (only mandatory for the first and third methods). Make it a Symbol for faster computation time.

Keyword Arguments

  • scale = nothing: The scale to filter-in (i.e. to keep). Usually a Tuple-alike of integers.
  • symbol = nothing: The symbol to filter-in. Usually a Tuple-alike of Strings.
  • link = nothing: The link with the previous node to filter-in. Usually a Tuple-alike of Char.
  • all::Bool = true: Return all filtered-in nodes (true), or stop at the first node that

is filtered out (false).

  • self = false: is the value for the current node needed ?
  • filter_fun = nothing: Any filtering function taking a node as input, e.g. isleaf.
  • recursivity_level = Inf: The maximum number of recursions allowed (considering filters).

E.g. to get the first level children only: recursivity_level = 1, for children + grand-children: recursivity_level = 2. If Inf (the default) or a negative value is provided, there is no recursion limitation.

  • ignore_nothing = false: filter-out the nodes with nothing values for the given key
  • type::Union{Union,DataType}: The type of the attribute. Can make the function run much

faster if provided (e.g. ≈4x faster).

Tips

To get the values of the leaves use isleaf as the filtering function, e.g.: descendants(mtg, :Width; filter_fun = isleaf).

Note

In most cases, the type argument should be given as a union of Nothing and the data type of the attribute to manage missing or inexistant data, e.g. measurements made at one scale only. See examples for more details.

Examples

# Importing the mtg from the github repo:
file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)

descendants(mtg, :Length) # Short to write, but slower to execute

# Fast version, note that we pass a union of Nothing and Float64 because there are some nodes
# without a `Length` attribute:
descendants(mtg, :Length, type = Union{Nothing,Float64})

# Filter by scale:
descendants(mtg, :XEuler, scale = 3, type = Union{Nothing, Float64})
descendants(mtg, :Length, scale = 3, type = Float64) # No `nothing` value here, no need of a union type

# Filter by symbol:
descendants(mtg, :Length, symbol = "Leaf")
descendants(mtg, :Length, symbol = ("Leaf","Internode"))

# Filter by function, e.g. get the values for the leaves only:
descendants(mtg, :Width; filter_fun = isleaf)

# You can also ask for different attributes by passing them as a vector:
descendants(mtg, [:Width, :Length]; filter_fun = isleaf)
# The output is an array of arrays of length of the attributes you asked for.

# It is possible to cache the results in the mtg using the mutating version `descendants!` (note the `!` 
# at the end of the function name):
transform!(mtg, node -> sum(descendants!(node, :Length)) => :subtree_length, symbol = "Internode")

# Or using `@mutate_mtg!` instead of `transform!`:
@mutate_mtg!(mtg, subtree_length = sum(descendants!(node, :Length)), symbol = "Internode")

# The cache is stored in a temporary variable with a name that starts with `_cache_` followed by the SHA
# of the function call, *e.g.*: `:_cache_5c1e97a3af343ce623cbe83befc851092ca61c8d`:
node_attributes(mtg[1][1][1])

# You can then clean the cache to avoid using too much memory:
clean_cache!(mtg)
node_attributes(mtg[1][1][1])
source
MultiScaleTreeGraph.expand_node!Method

Expand MTG line

Expand the elements denoted by the syntactic sugar "<<", "<.<", "++" or "+.+"

Arguments

  • x::Array{String}: A split MTG line (e.g. c("/P1","/A1"))
  • line::Array{Int64,1}: The current line index (mutated) in the file. Only

used as information when erroring.

Returns

A Tuple of:

  • the split MTG line with all nodes explicitly
  • the nodes with common attributes (when using <.< or +.+)

Examples

x = split("/A1+U85/U86<U87<.<U93<U94<.<U96<U97+.+U100",r"(?<=.)(?=[</+])");
nodes, shared = MultiScaleTreeGraph.expand_node!(x,1)
(AbstractString["/A1", "+U85", "/U86", "<U87", "<U88", "<U89", "<U90", "<U91", "<U92", "<U93", "<U94", "<U95", "<U96", "<U97", "+U98", "+U99", "+U100"], Any[87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100])
source
MultiScaleTreeGraph.get_nodeMethod
get_node(node::Node, id::Int)

Get a node in an mtg by id.

Examples

file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)
node_6_2 = get_node(mtg, 6)
source
MultiScaleTreeGraph.get_node_printing!Function
get_node_printing!(node, lead, ref, print_node, node_lead=0, node_ref="")

Get the number of tabulation (in lead) and the "^" (in ref) used as a prefix for the node when writting it to a file, based on the topology of its parent. Also get the node printing (e.g. "/Axis0") in print_node.

The function modifies the lead, ref and print_node vectors in place.

Examples

file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)
lead = Int[]
ref = String[]
get_node_printing!(mtg, lead, ref)

lead
ref
source
MultiScaleTreeGraph.insert_child!Function
insert_parent!(node, template, attr_fun = node -> typeof(node_attributes(node))(), max_id = [max_id(node)])
insert_generation!(node, template, attr_fun = node -> typeof(node_attributes(node))(), max_id = [max_id(node)])
insert_child!(node, template, attr_fun = node -> typeof(node_attributes(node))(), max_id = [max_id(node)])
insert_sibling!(node, template, attr_fun = node -> typeof(node_attributes(node))(), max_id = [max_id(node)])

Insert a node in an MTG as:

  • a new parent of node: insert_parent!
  • a new child of node: insert_child!
  • a new sibling of node: insert_sibling!
  • a new child of node, but the children of node become the children of the inserted node:

insert_generation!

Arguments

  • node::Node: The node from which to insert a node (as its parent, child or sibling).
  • template:
    • A template NodeMTG or MutableNodeMTG used for the inserted node,
    • A NamedTuple with values for link, symbol, index, and scale
    • Or a function taking the node as input and returning said template
  • attr_fun: A function to compute new attributes based on the filtered node. Must return

attribute values of the same type as the one used in other nodes from the MTG (e.g. Dict or NamedTuple). If you just need to pass attributes values to a node use x -> your_values.

  • max_id::Vector{Int64}: The maximum id of the nodes in the MTG as a vector of length one. It is incremented in the function,

and use by default the value from max_id.

Examples

file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)

template = MultiScaleTreeGraph.MutableNodeMTG("/", "Shoot", 0, 1)
insert_parent!(mtg[1][1], template)
mtg

# The template can be a function that returns the template. For example a dummy example would
# be a function that uses the NodeMTG of the first child of the node:

insert_parent!(
    mtg[1][1],
    node -> (
        link = link(node[1]),
        symbol = symbol(node[1]),
        index = index(node[1]),
        scale = scale(node[1]))
    )
)
source
MultiScaleTreeGraph.insert_children!Function
insert_parents!(node::Node, template, <keyword arguments>)
insert_generations!(node::Node, template, <keyword arguments>)
insert_children!(node::Node, template, <keyword arguments>)
insert_siblings!(node::Node, template, <keyword arguments>)

Insert new nodes in the mtg following filters rules. It is important to note the function always return the root node, whether it is the old one or a new inserted one, so the user is encouraged to assign the results to an object.

Insert nodes programmatically in an MTG as:

  • new parents of the filtered nodes: insert_parents!
  • new children of the filtered nodes: insert_children!
  • new siblings of the filtered node: insert_siblings!
  • new children of the filtered nodes, but the previous children of the filtered node become

the children of the inserted node: insert_generations!

Arguments

Mandatory arguments

  • node::Node: The node to start at.
  • template:
    • A template NodeMTG or MutableNodeMTG used for the inserted node,
    • A NamedTuple with values for link, symbol, index, and scale
    • Or a function taking the node as input and returning said template
  • attr: Attributes for the node. Similarly to template, can be:
    • An attribute of the same type as of node attributes (e.g. a Dict or a NamedTuple)
    • A function to compute new attributes (should also return same type for the attributes)

Keyword Arguments (filters)

  • scale = nothing: The scale at which to insert. Usually a Tuple-alike of integers.
  • symbol = nothing: The symbol at which to insert. Usually a Tuple-alike of Strings.
  • link = nothing: The link with at which to insert. Usually a Tuple-alike of Char.
  • all::Bool = true: Continue after the first insertion (true), or stop.
  • filter_fun = nothing: Any function taking a node as input, e.g. isleaf to decide

on which node the insertion will be based on.

Examples

file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)

# Insert new Shoot nodes before all scale 2 nodes:
mtg = insert_parents!(mtg, MultiScaleTreeGraph.MutableNodeMTG("/", "Shoot", 0, 1), scale = 2)

mtg
source
MultiScaleTreeGraph.insert_generation!Function
insert_parent!(node, template, attr_fun = node -> typeof(node_attributes(node))(), max_id = [max_id(node)])
insert_generation!(node, template, attr_fun = node -> typeof(node_attributes(node))(), max_id = [max_id(node)])
insert_child!(node, template, attr_fun = node -> typeof(node_attributes(node))(), max_id = [max_id(node)])
insert_sibling!(node, template, attr_fun = node -> typeof(node_attributes(node))(), max_id = [max_id(node)])

Insert a node in an MTG as:

  • a new parent of node: insert_parent!
  • a new child of node: insert_child!
  • a new sibling of node: insert_sibling!
  • a new child of node, but the children of node become the children of the inserted node:

insert_generation!

Arguments

  • node::Node: The node from which to insert a node (as its parent, child or sibling).
  • template:
    • A template NodeMTG or MutableNodeMTG used for the inserted node,
    • A NamedTuple with values for link, symbol, index, and scale
    • Or a function taking the node as input and returning said template
  • attr_fun: A function to compute new attributes based on the filtered node. Must return

attribute values of the same type as the one used in other nodes from the MTG (e.g. Dict or NamedTuple). If you just need to pass attributes values to a node use x -> your_values.

  • max_id::Vector{Int64}: The maximum id of the nodes in the MTG as a vector of length one. It is incremented in the function,

and use by default the value from max_id.

Examples

file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)

template = MultiScaleTreeGraph.MutableNodeMTG("/", "Shoot", 0, 1)
insert_parent!(mtg[1][1], template)
mtg

# The template can be a function that returns the template. For example a dummy example would
# be a function that uses the NodeMTG of the first child of the node:

insert_parent!(
    mtg[1][1],
    node -> (
        link = link(node[1]),
        symbol = symbol(node[1]),
        index = index(node[1]),
        scale = scale(node[1]))
    )
)
source
MultiScaleTreeGraph.insert_generations!Function
insert_parents!(node::Node, template, <keyword arguments>)
insert_generations!(node::Node, template, <keyword arguments>)
insert_children!(node::Node, template, <keyword arguments>)
insert_siblings!(node::Node, template, <keyword arguments>)

Insert new nodes in the mtg following filters rules. It is important to note the function always return the root node, whether it is the old one or a new inserted one, so the user is encouraged to assign the results to an object.

Insert nodes programmatically in an MTG as:

  • new parents of the filtered nodes: insert_parents!
  • new children of the filtered nodes: insert_children!
  • new siblings of the filtered node: insert_siblings!
  • new children of the filtered nodes, but the previous children of the filtered node become

the children of the inserted node: insert_generations!

Arguments

Mandatory arguments

  • node::Node: The node to start at.
  • template:
    • A template NodeMTG or MutableNodeMTG used for the inserted node,
    • A NamedTuple with values for link, symbol, index, and scale
    • Or a function taking the node as input and returning said template
  • attr: Attributes for the node. Similarly to template, can be:
    • An attribute of the same type as of node attributes (e.g. a Dict or a NamedTuple)
    • A function to compute new attributes (should also return same type for the attributes)

Keyword Arguments (filters)

  • scale = nothing: The scale at which to insert. Usually a Tuple-alike of integers.
  • symbol = nothing: The symbol at which to insert. Usually a Tuple-alike of Strings.
  • link = nothing: The link with at which to insert. Usually a Tuple-alike of Char.
  • all::Bool = true: Continue after the first insertion (true), or stop.
  • filter_fun = nothing: Any function taking a node as input, e.g. isleaf to decide

on which node the insertion will be based on.

Examples

file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)

# Insert new Shoot nodes before all scale 2 nodes:
mtg = insert_parents!(mtg, MultiScaleTreeGraph.MutableNodeMTG("/", "Shoot", 0, 1), scale = 2)

mtg
source
MultiScaleTreeGraph.insert_parent!Function
insert_parent!(node, template, attr_fun = node -> typeof(node_attributes(node))(), max_id = [max_id(node)])
insert_generation!(node, template, attr_fun = node -> typeof(node_attributes(node))(), max_id = [max_id(node)])
insert_child!(node, template, attr_fun = node -> typeof(node_attributes(node))(), max_id = [max_id(node)])
insert_sibling!(node, template, attr_fun = node -> typeof(node_attributes(node))(), max_id = [max_id(node)])

Insert a node in an MTG as:

  • a new parent of node: insert_parent!
  • a new child of node: insert_child!
  • a new sibling of node: insert_sibling!
  • a new child of node, but the children of node become the children of the inserted node:

insert_generation!

Arguments

  • node::Node: The node from which to insert a node (as its parent, child or sibling).
  • template:
    • A template NodeMTG or MutableNodeMTG used for the inserted node,
    • A NamedTuple with values for link, symbol, index, and scale
    • Or a function taking the node as input and returning said template
  • attr_fun: A function to compute new attributes based on the filtered node. Must return

attribute values of the same type as the one used in other nodes from the MTG (e.g. Dict or NamedTuple). If you just need to pass attributes values to a node use x -> your_values.

  • max_id::Vector{Int64}: The maximum id of the nodes in the MTG as a vector of length one. It is incremented in the function,

and use by default the value from max_id.

Examples

file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)

template = MultiScaleTreeGraph.MutableNodeMTG("/", "Shoot", 0, 1)
insert_parent!(mtg[1][1], template)
mtg

# The template can be a function that returns the template. For example a dummy example would
# be a function that uses the NodeMTG of the first child of the node:

insert_parent!(
    mtg[1][1],
    node -> (
        link = link(node[1]),
        symbol = symbol(node[1]),
        index = index(node[1]),
        scale = scale(node[1]))
    )
)
source
MultiScaleTreeGraph.insert_parents!Function
insert_parents!(node::Node, template, <keyword arguments>)
insert_generations!(node::Node, template, <keyword arguments>)
insert_children!(node::Node, template, <keyword arguments>)
insert_siblings!(node::Node, template, <keyword arguments>)

Insert new nodes in the mtg following filters rules. It is important to note the function always return the root node, whether it is the old one or a new inserted one, so the user is encouraged to assign the results to an object.

Insert nodes programmatically in an MTG as:

  • new parents of the filtered nodes: insert_parents!
  • new children of the filtered nodes: insert_children!
  • new siblings of the filtered node: insert_siblings!
  • new children of the filtered nodes, but the previous children of the filtered node become

the children of the inserted node: insert_generations!

Arguments

Mandatory arguments

  • node::Node: The node to start at.
  • template:
    • A template NodeMTG or MutableNodeMTG used for the inserted node,
    • A NamedTuple with values for link, symbol, index, and scale
    • Or a function taking the node as input and returning said template
  • attr: Attributes for the node. Similarly to template, can be:
    • An attribute of the same type as of node attributes (e.g. a Dict or a NamedTuple)
    • A function to compute new attributes (should also return same type for the attributes)

Keyword Arguments (filters)

  • scale = nothing: The scale at which to insert. Usually a Tuple-alike of integers.
  • symbol = nothing: The symbol at which to insert. Usually a Tuple-alike of Strings.
  • link = nothing: The link with at which to insert. Usually a Tuple-alike of Char.
  • all::Bool = true: Continue after the first insertion (true), or stop.
  • filter_fun = nothing: Any function taking a node as input, e.g. isleaf to decide

on which node the insertion will be based on.

Examples

file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)

# Insert new Shoot nodes before all scale 2 nodes:
mtg = insert_parents!(mtg, MultiScaleTreeGraph.MutableNodeMTG("/", "Shoot", 0, 1), scale = 2)

mtg
source
MultiScaleTreeGraph.insert_sibling!Function
insert_parent!(node, template, attr_fun = node -> typeof(node_attributes(node))(), max_id = [max_id(node)])
insert_generation!(node, template, attr_fun = node -> typeof(node_attributes(node))(), max_id = [max_id(node)])
insert_child!(node, template, attr_fun = node -> typeof(node_attributes(node))(), max_id = [max_id(node)])
insert_sibling!(node, template, attr_fun = node -> typeof(node_attributes(node))(), max_id = [max_id(node)])

Insert a node in an MTG as:

  • a new parent of node: insert_parent!
  • a new child of node: insert_child!
  • a new sibling of node: insert_sibling!
  • a new child of node, but the children of node become the children of the inserted node:

insert_generation!

Arguments

  • node::Node: The node from which to insert a node (as its parent, child or sibling).
  • template:
    • A template NodeMTG or MutableNodeMTG used for the inserted node,
    • A NamedTuple with values for link, symbol, index, and scale
    • Or a function taking the node as input and returning said template
  • attr_fun: A function to compute new attributes based on the filtered node. Must return

attribute values of the same type as the one used in other nodes from the MTG (e.g. Dict or NamedTuple). If you just need to pass attributes values to a node use x -> your_values.

  • max_id::Vector{Int64}: The maximum id of the nodes in the MTG as a vector of length one. It is incremented in the function,

and use by default the value from max_id.

Examples

file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)

template = MultiScaleTreeGraph.MutableNodeMTG("/", "Shoot", 0, 1)
insert_parent!(mtg[1][1], template)
mtg

# The template can be a function that returns the template. For example a dummy example would
# be a function that uses the NodeMTG of the first child of the node:

insert_parent!(
    mtg[1][1],
    node -> (
        link = link(node[1]),
        symbol = symbol(node[1]),
        index = index(node[1]),
        scale = scale(node[1]))
    )
)
source
MultiScaleTreeGraph.insert_siblings!Function
insert_parents!(node::Node, template, <keyword arguments>)
insert_generations!(node::Node, template, <keyword arguments>)
insert_children!(node::Node, template, <keyword arguments>)
insert_siblings!(node::Node, template, <keyword arguments>)

Insert new nodes in the mtg following filters rules. It is important to note the function always return the root node, whether it is the old one or a new inserted one, so the user is encouraged to assign the results to an object.

Insert nodes programmatically in an MTG as:

  • new parents of the filtered nodes: insert_parents!
  • new children of the filtered nodes: insert_children!
  • new siblings of the filtered node: insert_siblings!
  • new children of the filtered nodes, but the previous children of the filtered node become

the children of the inserted node: insert_generations!

Arguments

Mandatory arguments

  • node::Node: The node to start at.
  • template:
    • A template NodeMTG or MutableNodeMTG used for the inserted node,
    • A NamedTuple with values for link, symbol, index, and scale
    • Or a function taking the node as input and returning said template
  • attr: Attributes for the node. Similarly to template, can be:
    • An attribute of the same type as of node attributes (e.g. a Dict or a NamedTuple)
    • A function to compute new attributes (should also return same type for the attributes)

Keyword Arguments (filters)

  • scale = nothing: The scale at which to insert. Usually a Tuple-alike of integers.
  • symbol = nothing: The symbol at which to insert. Usually a Tuple-alike of Strings.
  • link = nothing: The link with at which to insert. Usually a Tuple-alike of Char.
  • all::Bool = true: Continue after the first insertion (true), or stop.
  • filter_fun = nothing: Any function taking a node as input, e.g. isleaf to decide

on which node the insertion will be based on.

Examples

file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)

# Insert new Shoot nodes before all scale 2 nodes:
mtg = insert_parents!(mtg, MultiScaleTreeGraph.MutableNodeMTG("/", "Shoot", 0, 1), scale = 2)

mtg
source
MultiScaleTreeGraph.is_segment!Method
is_segment(node)

Checks if a node (n) has only one child (n+1). This is usefull to simplify a complex mtg to become an mtg with nodes only at the branching points, has it is often measured on the field.

The function also takes care of passing the link of the node (n) to its child (n+1) if the node (n) branches or decompose its parent (n-1). This allows a conservation of the relationships as they previously were in the mtg.

See delete_nodes! for an example of application.

source
MultiScaleTreeGraph.issectionMethod
issection(string,section)

Is a section

Is a string part of an MTG section ? Returns true if it does, false otherwise.

Arguments

  • string::String: The string to test.
  • section::String: The section to test.
issection("CODE :", "CODE")
source
MultiScaleTreeGraph.new_child_linkMethod
new_child_link(node)

Compute the new link of the child node when deleting a parent node. The rule is to give the child node link of its parent node that is deleted, except when the parent was following its own parent.

The node given as input is the child node here.

The rule is summarized in the following table:

Deleted node linkChild node linkNew child node linkwarning
///
/++yes (1)
/</
+//yes (2)
+++
+<+
<//
<++
<<<

The warnings happens when there is no satisfactory way to handle the new link, i.e. when mixing branching and change in scale.

Note that in the case (1) of the warning the first child only takes the "/" link, the others keep their links.

source
MultiScaleTreeGraph.new_idMethod
new_id(mtg)
new_id(mtg, max_id)

Make a new unique identifier by incrementing on the maximum node id. Hint: prefer using max_id = max_id(mtg) and then new_id(mtg, max_is) for performance if you do it repeatidely.

source
MultiScaleTreeGraph.new_node_MTGMethod
new_node_MTG(node, template<:Union{NodeMTG,MutableNodeMTG,NamedTuple,MutableNamedTuple})
new_node_MTG(node, fn)

Returns a new NodeMTG matching the one used in node (either NodeMTG or MutableNodeMTG) based on a template, or on a function that takes a node as input and return said template.

Examples

file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)

# using a NodeMTG as a template:
MultiScaleTreeGraph.new_node_MTG(mtg, NodeMTG("/", "Leaf", 1, 2))
# Note that it returns a MutableNodeMTG because `mtg` is using this type instead of a `NodeMTG`

# using a NamedTuple as a template:
MultiScaleTreeGraph.new_node_MTG(mtg, (link = "/", symbol = "Leaf", index = 1, scale = 2))

# using a function that returns a template based on the first child of the node:
MultiScaleTreeGraph.new_node_MTG(
    mtg,
    x -> (
            link = link(x[1]),
            symbol = symbol(x[1]),
            index = index(x[1]),
            scale = scale(x[1]))
        )
source
MultiScaleTreeGraph.next_line!Method
next_line!(f,line)

Read line

Read the next line in the IO stream, strip the comments, the missing values and increment the line index.

Arguments

  • f::IOStream: A buffered IO stream to the mtg file, e.g. f = open(file, "r").
  • line::Array{Int64,1}: The line number at which f is at the start of the funtion (mutated).
  • whitespace::Bool: remove leading whitespaces.
source
MultiScaleTreeGraph.nleavesFunction
nleaves(node)
nleaves!(node)

Get the total number of leaves a node is bearing, i.e. the number of terminal nodes. nleaves! is faster than nleaves but cache the results in a variable so it uses more memory. Please use clean_cache! after calling nleaves! to clean the temporary variables.

Examples

# Importing the mtg from the github repo:
file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)

nleaves!(mtg)

clean_cache!(mtg)
source
MultiScaleTreeGraph.nleaves!Function
nleaves(node)
nleaves!(node)

Get the total number of leaves a node is bearing, i.e. the number of terminal nodes. nleaves! is faster than nleaves but cache the results in a variable so it uses more memory. Please use clean_cache! after calling nleaves! to clean the temporary variables.

Examples

# Importing the mtg from the github repo:
file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)

nleaves!(mtg)

clean_cache!(mtg)
source
MultiScaleTreeGraph.node_attributes!Method
node_attributes!(node::Node)

Set the attributes of a node, i.e. replace the whole structure by another. This function is internal, and should not be used directly. Use e.g. node.key = value to set a single attribute of the node.

source
MultiScaleTreeGraph.node_attributesMethod

Instantiate a attr_type struct with node_attr keys and values

Arguments

  • attr_type::DataType: the type of the structure used to hold the attributes
  • node_attr::String: The node attributes as a Dict
source
MultiScaleTreeGraph.node_mtgMethod
node_mtg(node::Node)

Get the MTG encoding of the node, i.e. the MTG description (see NodeMTG or MutableNodeMTG):

  • scale: the scale of the node (e.g. 1)
  • symbol: the symbol of the node (e.g. "Axis")
  • index: the index of the node (e.g. 1, this is free)
  • link: the link of the node ("/", "+" or "<")
source
MultiScaleTreeGraph.parse_MTG_nodeMethod

Parse MTG node

Parse MTG nodes (called from parse_mtg!())

Arguments

  • l::String: An MTG node (e.g. "/Individual0")

Return

A parsed node in the form of a Dict of three:

  • the link
  • the symbol
  • and the index
source
MultiScaleTreeGraph.parse_MTG_node_attrMethod

Parse MTG node attributes names, values and type

Arguments

  • node_data::String: A splitted mtg node data (attributes)
  • attr_type::DataType: the type of the structure used to hold the attributes
  • features::DataFrame: The features data.frame
  • attr_column_start::Integer: The index of the column of the first attribute
  • line::Integer: The current line of the mtg file
  • force::Bool: force data reading even if errors are met during conversion ?

Return

A list of attributes

source
MultiScaleTreeGraph.parse_line_to_node!Method
parse_line_to_node!(tree_dict, l, line, attr_column_start, node_id, attr_type, mtg_type, features,classes)

Parse a line of the MTG file to a node and add it to the tree dictionary. It may also add several nodes if the line contains several MTG elements.

source
MultiScaleTreeGraph.parse_macro_argsMethod
parse_macro_args(args)

Parse filters and arguments given as a collection of expressions. This function is used to get the filters as keyword arguments in macros.

Examples

args = (:(x = node_id(node)), :(y = node.x + 2), :(scale = 2))
MultiScaleTreeGraph.parse_macro_args(args)
source
MultiScaleTreeGraph.parse_mtg!Method

Parse MTG section

Arguments

  • f::IOStream: A buffered IO stream to the mtg file, e.g. f = open(file, "r")
  • classes::Array: The class section data as returned by parse_section!
  • description::Array: The description section data as returned by parse_section!
  • features::Array: The features section data as returned by parse_section!
  • line::Array{Int64,1}: The current line index (mutated). Must be given as line of MTG:
  • l::Array{String,1}: the current line
  • attr_type::DataType: the type of the structure used to hold the attributes

Note

The buffered IO stream (f) should start at the line of the section.

Returns

The parsed MTG section

source
MultiScaleTreeGraph.parse_section!Method

Parse MTG section

Arguments

  • f::IOStream: A buffered IO stream to the mtg file, e.g. f = open(file, "r").
  • header::Array{String,1}: A string defining the expected header for the class.
  • section::String: The section name.
  • line::Array{Int64,1}: The line number at which f is at the start of the funtion (mutated).
  • l::Array{String,1}: the current line

Note

The buffered IO stream (f) should start at the line of the section.

Returns

The parsed section of the MTG

Examples

file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
f = open(file, "r")
line = [0] ; l = [""]; l[1] = MultiScaleTreeGraph.next_line!(f,line)

while MultiScaleTreeGraph.issection(l[1]) || MultiScaleTreeGraph.issection(l[1],"CLASSES")
    l[1] = MultiScaleTreeGraph.next_line!(f,line)
end

classes = MultiScaleTreeGraph.parse_section!(f,["SYMBOL","SCALE","DECOMPOSITION","INDEXATION","DEFINITION"],"CLASSES",line,l)

close(f)
source
MultiScaleTreeGraph.pipe_model!Method
pipe_model!(node, var_name, threshold_value; allow_missing = false)

Same than pipe_model! but uses another variable as the reference down until a threshold value. This is used for example in the case of LiDAR measurements, where we know the cross-section (:var_name) is well measured down to e.g. 2-3cm of diameter, but should be computed below.

This function allows to compute the cross-section using the pipe model only for some sub-trees with values of :var_name <= threshold_value.

Arguments

  • node: the mtg, or a specific node at which to start from.
  • var_name: the name of the cross-section attribute name in the nodes
  • threshold_value: the threshold defining the value below which the cross-section will be

re-computed using the pipe model instead of using var_name.

  • allow_missing=false: Allow missing values for var_name, in which case the cross-section is

recomputed using the pipe model. Please use this option only if you know why.

Details

The node cross-section is partitioned from parent to children according to the number of leaves (i.e. terminal nodes) each child subtree has, unless one or more children has a :var_name > threshold_value. In this case the shared cross-section is the one from the parent minus the one of these nodes for which we simply use the measured value. The cross-section of the siblings with :var_name <= threshold_value will be shared as usual using their number of leaves. If :var_name of the siblings are higher than the parent value, the cross-section of the node is computed only using the number of leaves as it should not be bigger.

Word of caution

Some tips when using this function:

  • User must ensure that :var_name has a value for all nodes in the mtg before calling this

version of pipe_model!, unless allow_missing=true.

  • Nodes with untrusted values should be

set to a value below the threshold value to make pipe_model! recompute them.

source
MultiScaleTreeGraph.pipe_model!Method
pipe_model!(node, root_value; name=:_cache_a7118a60b2a624134bf9eac2d64f2bb32798626a)

Computes the cross-section of node considering its topological environment and the cross-section at the root node (root_value).

The pipe model helps compute the cross-section of the nodes in an mtg by following the rule that the sum of the cross-sections of the children of a node is equal to the node cross-section.

The implementation uses the following algorithm:

First, check how many children a node has.

If it has one child only, the child cross-section is equal to the node cross-section.

If more children, the node cross-section is shared between the children according to the number of leaf nodes their subtree has, i.e. the total number of terminal nodes of their subtree.

Please call clean_cache! after using pipe_model! because it creates temporary variables.

source
MultiScaleTreeGraph.prune!Method
prune!(node)

Prune a tree at node, i.e. delete the entire sub-tree starting at node (including it).

Returns an error if the node is a root, or the parent node of the (deleted) node.

Examples

using MultiScaleTreeGraph
file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)

prune!(get_node(mtg, 6))

mtg
source
MultiScaleTreeGraph.read_mtgFunction
read_mtg(file, attr_type = Dict, mtg_type = MutableNodeMTG; sheet_name = nothing)

Read an MTG file

Arguments

  • file::String: The path to the MTG file.
  • attr_type::DataType = Dict: the type used to hold the attribute values for each node.
  • mtg_type = MutableNodeMTG: the type used to hold the mtg encoding for each node (i.e.

link, symbol, index, scale). See details section below.

  • sheet_name = nothing: the sheet name in case you're reading an xlsx or xlsm file. It

reads the first sheet if nothing (default behavior).

Details

attr_type should be:

  • NamedTuple if you don't plan to modify the attributes of the mtg, e.g. to use them for

plotting or computing statistics...

  • MutableNamedTuple if you plan to modify the attributes values but not adding new attributes

very often, e.g. recompute an attribute value...

  • Dict or similar (e.g. OrderedDict) if you plan to heavily modify the attributes, e.g.

adding/removing attributes a lot

The MultiScaleTreeGraph package provides two types for mtg_type, one immutable (NodeMTG), and one mutable (MutableNodeMTG). If you're planning on modifying the mtg encoding of some of your nodes, you should use MutableNodeMTG, and if you don't want to modify anything, use NodeMTG instead as it should be faster.

Note

See the documentation of the MTG format from the package documentation for further details, e.g. The MTG concept.

Returns

The MTG root node.

Examples

file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)

# Or using another `MutableNamedTuple` for the attributes to be able to add one if needed:
mtg = read_mtg(file,Dict);

# We can also read an mtg directly from an excel file from the field:
file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","tree3h.xlsx")
mtg = read_mtg(file)
source
MultiScaleTreeGraph.rewrite_expr!Method
rewrite_expr!(arguments)

Re-write the call to the variables of a node in an expression to match their location: leave it as it is if the variable is a node field, or add attributes after the node if it is an attribute.

Examples

test = :(x = node.var)
MultiScaleTreeGraph.rewrite_expr!(:mtg,test)
test
# :(mtg[:x] = mtg[:var])

test = :(x = node.foo)
MultiScaleTreeGraph.rewrite_expr!(:mtg,test)
test
# :(mtg[:x] = mtg[:foo])

test = :(x = symbol(node))
MultiScaleTreeGraph.rewrite_expr!(:mtg,test)
test
# :(mtg[:x] = symbol(mtg))

test = :(x = node_mtg(node) |> symbol)
MultiScaleTreeGraph.rewrite_expr!(:mtg,test)
test
# :(mtg[:x] = node_mtg(mtg) |> symbol)
source
MultiScaleTreeGraph.split_MTG_elementsMethod
split_MTG_elements(l)

Split MTG line

Split the elements (e.g. inter-node, growth unit...) in an MTG line

Arguments

  • l::String: A string for an MTG line (e.g. "/P1/A1").

Return

A vector of elements (keeping their link, e.g. + or <)

split_MTG_elements("/A1+U85/U86<U87<.<U93<U94<.<U96<U97+.+U100")
source
MultiScaleTreeGraph.strip_commentsFunction

Strip comments from a string

striplinecomment{T<:String,U<:String}(a::T, cchars::U="#;")

Arguments

  • a::String: the string from which the comments has to be stripped
  • cchars::String: the characters that defines comments

From https://rosettacode.org/wiki/Stripcommentsfromastring#Julia

  • whitespace::Bool: remove leading whitespaces.
strip_comments("test1")
strip_comments("test2 # with a comment")
strip_comments("# just a comment")
""
source
MultiScaleTreeGraph.traverseFunction
traverse!(node::Node, f::Function[, args...], <keyword arguments>)
traverse(node::Node, f::Function[, args...], <keyword arguments>)

Traverse the nodes of a (sub-)tree, given any starting node in the tree, and apply a function which is either mutating (use traverse!) or not (use traverse).

Arguments

  • node::Node: An MTG node (e.g. the whole mtg returned by read_mtg()).

  • f::Function: a function to apply over each node

  • args::Any: any argument to pass to the function

  • <keyword arguments>:

    • scale = nothing: The scale to filter-in (i.e. to keep). Usually a Tuple-alike of integers.
    • symbol = nothing: The symbol to filter-in. Usually a Tuple-alike of Strings.
    • link = nothing: The link with the previous node to filter-in. Usually a Tuple-alike of Char.
    • filter_fun = nothing: Any filtering function taking a node as input, e.g. isleaf.
    • all::Bool = true: Return all filtered-in nodes (true), or stop at the first node that is filtered out (false).
    • type::Type = Any: The elements type of the returned array. This can speed-up things. Only available for the non-mutating version.
    • recursivity_level::Int = Inf: The maximum depth of the traversal. Default is Inf (i.e. no limit).

Returns

nothing for traverse! because it mutates the (sub-)tree in-place, or an Array{type} (or Array{Any} if type is not given) for traverse.

Examples

file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)
traverse!(mtg, node -> isleaf(node) ? println(node_id(node)," is a leaf") : nothing)
node_5 is a leaf
node_7 is a leaf

# We can also use the `do...end` block notation when we have a complex set of instructions:
traverse!(mtg) do node
    if isleaf(node)
         println(node_id(x)," is a leaf")
    end
end
source
MultiScaleTreeGraph.traverse!Function
traverse!(node::Node, f::Function[, args...], <keyword arguments>)
traverse(node::Node, f::Function[, args...], <keyword arguments>)

Traverse the nodes of a (sub-)tree, given any starting node in the tree, and apply a function which is either mutating (use traverse!) or not (use traverse).

Arguments

  • node::Node: An MTG node (e.g. the whole mtg returned by read_mtg()).

  • f::Function: a function to apply over each node

  • args::Any: any argument to pass to the function

  • <keyword arguments>:

    • scale = nothing: The scale to filter-in (i.e. to keep). Usually a Tuple-alike of integers.
    • symbol = nothing: The symbol to filter-in. Usually a Tuple-alike of Strings.
    • link = nothing: The link with the previous node to filter-in. Usually a Tuple-alike of Char.
    • filter_fun = nothing: Any filtering function taking a node as input, e.g. isleaf.
    • all::Bool = true: Return all filtered-in nodes (true), or stop at the first node that is filtered out (false).
    • type::Type = Any: The elements type of the returned array. This can speed-up things. Only available for the non-mutating version.
    • recursivity_level::Int = Inf: The maximum depth of the traversal. Default is Inf (i.e. no limit).

Returns

nothing for traverse! because it mutates the (sub-)tree in-place, or an Array{type} (or Array{Any} if type is not given) for traverse.

Examples

file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)
traverse!(mtg, node -> isleaf(node) ? println(node_id(node)," is a leaf") : nothing)
node_5 is a leaf
node_7 is a leaf

# We can also use the `do...end` block notation when we have a complex set of instructions:
traverse!(mtg) do node
    if isleaf(node)
         println(node_id(x)," is a leaf")
    end
end
source
MultiScaleTreeGraph.unsafe_getindexMethod

Indexing Node attributes from node, e.g. node[:length] or node["length"], but in an unsafe way, meaning it returns nothing when the key is not found instead of returning an error. It is primarily used when traversing the tree, so if a node does not have a field, it does not return an error.

source
MultiScaleTreeGraph.write_mtgMethod
write_mtg(file, mtg; kwargs...)
write_mtg(file, mtg, classes, description, features)

Write an mtg file to disk.

Arguments

  • file::String: The path to the MTG file to write.
  • mtg: the mtg
  • classes: the classes section
  • description: the description section
  • features: the features section

Note

kwargs can be used to give zero, one or two of the classes, description and features instead of all. In this case the missing ones are recomputed using get_classes, get_features or get_description.

Examples

file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)
write_mtg("test.mtg",mtg)
source
MultiScaleTreeGraph.@mutate_mtg!Macro
@mutate_mtg!(node, args...,kwargs...)

Mutate the mtg nodes in place.

Arguments

  • mtg: the mtg to mutate
  • args...: The computations to apply to the nodes (see examples)
  • kwargs...: Optional keyword arguments for traversing and filtering (see details)

Details

As for descendants and ancestors, kwargs can be any filter from:

  • scale = nothing: The scale to filter-in (i.e. to keep). Usually a Tuple-alike of integers.
  • symbol = nothing: The symbol to filter-in. Usually a Tuple-alike of Strings.
  • link = nothing: The link with the previous node to filter-in. Usually a Tuple-alike of Char.
  • all::Bool = true: Return all filtered-in nodes (true), or stop at the first node that

is filtered out (false).

  • filter_fun = nothing: Any filtering function taking a node as input, e.g. isleaf.
  • traversal: The type of tree traversal. By default it is using AbstractTrees.PreOrderDFS.

Examples

# Importing an mtg from the package:
file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)

# Compute a new attribute with the scales and add 2 to its values:
@mutate_mtg!(mtg, scaling = node.scales .+ 2, filter_fun = node -> node.scales !== nothing)

# Compute several new attributes, some based on others:
@mutate_mtg!(mtg, x = length(node_id(node)), y = node.x + 2, z = sum(node.y))

# We can also use it without parenthesis:

@mutate_mtg! mtg x = length(node_id(node))
source
MultiScaleTreeGraph.@mutate_node!Macro
@mutate_node!(node, args...)

Mutate a single node in place.

Arguments

  • node: the node to mutate
  • args...: The computations to apply to the node (see examples)

See also

@mutate_mtg! to mutate all nodes of an mtg.

Examples

# Importing an mtg from the package:
file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)

# Compute a new attribute with the scales and add 2 to its values:
@mutate_node!(mtg, scaling = node.scales .+ 2)

# The computation is only applied to the root node. To apply it to all nodes,
# see @mutate_mtg!

# Compute several new attributes, some based on others:
@mutate_node!(mtg, x = length(node_id(node)), y = node.x + 2, z = sum(node.y))

# We can also use it without parenthesis:

@mutate_node! mtg x = length(node_id(node))
source