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
Note

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 return nothing. You can test whether a node is a root node sing the isroot function.
  • children: the child nodes.
  • MTG: The MTG encoding of the node (see below, or NodeMTG)
  • 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
:Axis
Note

NodeMTG 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		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)
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}
Note

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)
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, 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)
:Scene
index(mtg)
0
scale(mtg)
0