MTG implementation
Introduction
In this package, the MTG is represented as a tree (nodes linked by parent/children relationships).
The tree is built from nodes. Each node stores:
- how it is connected to other nodes (its topology)
- its own measured or computed attributes
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 simple 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 theisrootfunction.children: the child nodes.MTG: The MTG encoding of the node (see below, orNodeMTG)attributes: node values (for example length, diameter, color, 3D position).traversal_cache: saved traversal results used to speed up repeated operations.
The values of these fields are accessed with helper functions such as node_id, parent, children, node_mtg, attribute, and attributes.
The MTG field of a node describes how the node is positioned in the graph: link with parent (/, <, +), symbol, index, and scale (see Node MTG and attributes and The MTG section for more details). It is stored as NodeMTG or MutableNodeMTG. These types have four fields:
fieldnames(NodeMTG)(:link, :symbol, :index, :scale)Creating a NodeMTG is simple: pass the four values in order. For example, an Axis that decomposes its parent ("/"), with index 0 and scale 1:
axis_mtg_encoding = NodeMTG("/", "Axis", 0, 1)NodeMTG(:/, :Axis, 0, 1)Then we can access data using dot syntax:
axis_mtg_encoding.symbol:AxisNodeMTG is immutable (cannot be changed after creation). MutableNodeMTG can be changed. Use mutable if you plan to edit node topology fields.
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 1We 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)Symbols: Scene Individual Axis Internode Leaf
Scales: 0 1 2 3 3
/ 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, MultiScaleTreeGraph.ColumnarAttrs}typeof(mtg) shows extra type details (including MTG encoding type and attribute container type). You usually do not need to worry about these details to use the package.
We can access the fields of the node using the accessor functions:
node_id(mtg)1parent(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) == mtgtruechildren(mtg)1-element Vector{Node{MutableNodeMTG, MultiScaleTreeGraph.ColumnarAttrs}}:
/ 2: Individual
└─ / 3: Axis
└─ / 4: Internode
├─ + 5: Leaf
└─ < 6: Internode
└─ + 7: Leaf
node_mtg(mtg)MutableNodeMTG(:/, :Scene, 0, 0)attributes(mtg, format=:dict)Dict{Symbol, Any} with 3 entries:
:scales => [0, 1, 2, 3, 3]
:description => ColumnTable([:LEFT, :RIGHT, :RELTYPE, :MAX], Dict(:LEFT=>1, :…
:symbols => ["Scene", "Individual", "Axis", "Internode", "Leaf"]The package also provide helper functions to access the MTG encoding of the node directly:
symbol(mtg):Sceneindex(mtg)0scale(mtg)0