MTG implementation
Introduction
In this package, the MTG is represented as a tree data structure.
The tree is built from a series of nodes with different fields that describe the topology (i.e. how nodes are connected together) and the attributes of the node.
The package use terms from computer science rather than plant biology. So we use words such as "root" in an MTG, which is not the plant root, but the first node in the tree, i.e. the one without any parent. Similarly a leaf node is not a leaf from a plant but a node without any children.
Data types
The nodes have their own data type called Node
. A Node
has several fields:
fieldnames(Node)
(:id, :parent, :children, :MTG, :attributes, :traversal_cache)
Here is a little description of each field:
id
: The unique integer identifier of the node. It can be set by the user but is usually set automatically.parent
: The parent node of the curent node. If the curent node is the root node, it will returnnothing
. You can test whether a node is a root node sing theisroot
function.- children: a dictionary of the children nodes with their
id
as key, ornothing
if none; MTG
: The MTG encoding of the node (see below, orNodeMTG
)attributes
: the node attributes. Usually aNamedTuple
, aMutableNamedTuple
or aDict
or similar (e.g.OrderedDict
), but the type is optional. The choice of the data structure depends mainly on how much you plan to change the attributes and their values. Attributes include for example the length or diameter of a node, its colour, 3d position...traversal_cache
: a cache for the traversal, used by e.g.traverse
to traverse more efficiently particular nodes in the MTG
The value of tee fields are accessed using accessor functions: node_id
, parent
, children
, node_mtg
, node_attributes
, and the last one get_traversal_cache
which is not exported because users shouldn't use it directly.
The MTG field of a node describes the topology encoding of the node: its type of link with its parent (decompose: /
, follow: <
, and branch: +
), its symbol, index, and scale (see Node MTG and attributes and The MTG section for more details). The MTG field must be encoded in a data structure called NodeMTG
or in a MutableNodeMTG
. They have four fields corresponding to the topology encoding:
fieldnames(NodeMTG)
(:link, :symbol, :index, :scale)
Creating a NodeMTG
is very simple, just pass the arguments by position. For example if we have an Axis that decomposes its parent node ("/"), with an index 0 and a scale of 1, we would declare it as follows:
axis_mtg_encoding = NodeMTG("/", "Axis", 0, 1)
NodeMTG("/", "Axis", 0, 1)
The we can access the data using the dot syntax:
axis_mtg_encoding.symbol
"Axis"
NodeMTG
is the immutable data type, meaning that information cannot be changed once read. By default the package the mutable equivalent called MutableNodeMTG
. Accessing the information of a mutable data structure is slower, but it is more convenient if we need to change its values.
Learning by example
Let's print again the example MTG from the previous section:
file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
println(read(file, String))
CODE: FORM-A
CLASSES:
SYMBOL SCALE DECOMPOSITION INDEXATION DEFINITION
$ 0 FREE FREE IMPLICIT
Individual 1 FREE FREE IMPLICIT
Axis 2 FREE FREE IMPLICIT # you can add comments anywhere
Internode 3 FREE FREE IMPLICIT
Leaf 3 FREE FREE IMPLICIT
DESCRIPTION:
LEFT RIGHT RELTYPE MAX
Internode Internode,Leaf + ?
Internode Internode,Leaf < ?
FEATURES:
NAME TYPE
Length ALPHA
Width ALPHA
XEuler REAL
isAlive BOOLEAN
dateDeath DD/MM/YY
MTG:
ENTITY-CODE Length Width XEuler isAlive dateDeath
/Scene0
^/Individual0
^/Axis0
^/Internode0 0.1 0.02 1
+Leaf0 0.2 0.1 0 24/08/2022
^<Internode1 0.1 0.02 180.0 1
+Leaf0 0.2 0.1 1
We can use read_mtg
from MultiScaleTreeGraph.jl
to read this MTG:
file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)
/ 1: Scene
└─ / 2: Individual
└─ / 3: Axis
└─ / 4: Internode
├─ + 5: Leaf
└─ < 6: Internode
└─ + 7: Leaf
read_mtg
returns the first node of the MTG, of type Node
:
typeof(mtg)
Node{MutableNodeMTG, Dict{Symbol, Any}}
The Node
is a parametric type, that's why typeof(mtg)
also returns the type used for the MTG data in the node (MutableNodeMTG
) and the type used for the attributes (Dict{Symbol, Any}
). But this is not important here.
We can access the fields of the node using the accessor functions:
node_id(mtg)
1
parent(mtg)
This one returns nothing
because the node is the root node, it has no parent, but we could use it on its child, and it would return the root again:
mtg_child = mtg[1]
parent(mtg_child) == mtg
true
children(mtg)
1-element Vector{Node{MutableNodeMTG, Dict{Symbol, Any}}}:
/ 2: Individual
└─ / 3: Axis
└─ / 4: Internode
├─ + 5: Leaf
└─ < 6: Internode
└─ + 7: Leaf
node_mtg(mtg)
MutableNodeMTG("/", "Scene", 0, 0)
node_attributes(mtg)
Dict{Symbol, Any} with 3 entries:
:scales => [0, 1, 2, 3, 3]
:description => 2×4 DataFrame…
:symbols => SubString{String}["Scene", "Individual", "Axis", "Internode",…
The package also provide helper functions to access the MTG encoding of the node directly:
symbol(mtg)
"Scene"
index(mtg)
0
scale(mtg)
0