Install developer version.
# install.packages('devtools')
library(devtools)
install_github('sergiocostafh/fractalforest')
Load fractalforest package.
library(fractalforest)
Set L-system rules.
binary_tree_rules <- data.frame(inp = c("0", "1"),
out = c("1[-0]+0", "1"), stringsAsFactors = FALSE)
Use the iterate_lsystem function to build the string based on the rules (rules), the axiom (init) and the number of iterations (n).
tree_string <- iterate_lsystem(init = "0", rules = binary_tree_rules, n = 5)
tree_string
## [1] "1[-1[-1[-1[-1[-0]+0]+1[-0]+0]+1[-1[-0]+0]+1[-0]+0]+1[-1[-1[-0]+0]+1[-0]+0]+1[-1[-0]+0]+1[-0]+0]+1[-1[-1[-1[-0]+0]+1[-0]+0]+1[-1[-0]+0]+1[-0]+0]+1[-1[-1[-0]+0]+1[-0]+0]+1[-1[-0]+0]+1[-0]+0"
Build the tree data.frame from the string, and visualize the output.
tree <- build_tree(string = tree_string, angle = 15)
head(tree)
## # A tibble: 6 x 5
## from_x to_x from_y to_y type
## <dbl> <dbl> <dbl> <dbl> <chr>
## 1 0 1.04e-17 0 0.170 branch
## 2 1.04e-17 4.39e- 2 0.170 0.333 branch
## 3 4.39e- 2 1.29e- 1 0.333 0.480 branch
## 4 1.29e- 1 2.49e- 1 0.480 0.600 branch
## 5 2.49e- 1 3.95e- 1 0.600 0.685 branch
## 6 3.95e- 1 5.59e- 1 0.685 0.729 leaf
Visualize the tree.
draw_tree(tree)
To set the rules, the following characters are recognized:
“+” Turn by positive angle.
“-” Turn by negative angle.
“[" Save current position and heading.
"]” Restore saved position and heading (allows one to go back).
“)” Reduces length and diameters of branches.
“(” Increment length and diameters of branches.
Any other characters in the rule string will be recognized as “a move forward, drawing as you go” instruction. This means that they must be declared in the inp column of the rules data frame, and also its corresponding substitution rule (out column).
The following example demonstrates the use of parentheses to reduce/increase branch lengths.
binary_tree_rules <- data.frame(inp = c("0", "1"),
out = c("1[-(0)]+(0)", "1"), stringsAsFactors = FALSE)
tree_string <- iterate_lsystem(init = "0", rules = binary_tree_rules, n = 5)
tree <- build_tree(string = tree_string, angle = 15)
draw_tree(tree)
iterate_lsystem functionThe n argument controls the number of iterations do build the plant. A large n value can be time and memory consuming, so be careful.
library(ggplot2)
library(dplyr)
trees_n <- lapply(1:8,
function(x) {
iterate_lsystem(init = '0', rules = binary_tree_rules, n = x) %>%
build_tree(string = ., angle = 15) %>%
mutate(n = paste('n = ', x))
})
bind_rows(trees_n) %>%
draw_tree()+
facet_wrap(~n, nrow = 2)
Below are some other rules for simulating plants with different morphologies.
alternate_bush_rules <- data.frame(inp = c("0", "1"),
out = c("1[+1][--(0)]+1-1[+++(0)]-(0)", "1"), stringsAsFactors = FALSE)
tree_string <- iterate_lsystem(init = "0", rules = alternate_bush_rules, n = 6)
tree1 <- build_tree(string = tree_string, angle = 10) %>%
mutate(tree = 'alternate tree')
arrow_weed_rules <- data.frame(inp = c("0", "1"),
out = c("1[+(0)][-(0)](10)", "1"), stringsAsFactors = FALSE)
tree_string <- iterate_lsystem(init = "0", rules = arrow_weed_rules, n = 6)
tree2 <- build_tree(string = tree_string, angle = 30) %>%
mutate(tree = 'arrow weed')
twiggy_weed_rules <- data.frame(inp = c("0", "1"),
out = c("1[-(0)]1[-(0)]+(0)", "1"), stringsAsFactors = FALSE)
tree_string <- iterate_lsystem(init = "0", rules = twiggy_weed_rules, n = 6)
tree3 <- build_tree(string = tree_string, angle = 25) %>%
mutate(tree = 'twiggy weed')
bind_rows(tree1, tree2, tree3) %>%
draw_tree()+
facet_wrap(~tree)
build_tree functionThe purpose of this function is to build a data frame that contains the coordinate matrix and some other information about the plant, such as the differentiation between branches and leaves and the diameters along the branches.
The h_reduction parameter in the build_tree function, controls the reduction factor of the length.
tree1 <- build_tree(string = tree_string, angle = 15, h_reduction = .7) %>%
mutate(hred = 'h_reduction = .7')
tree2 <- build_tree(string = tree_string, angle = 15) %>%
mutate(hred = 'h_reduction = .61803 (default)')
tree3 <- build_tree(string = tree_string, angle = 15, h_reduction = .5) %>%
mutate(hred = 'h_reduction = .5')
bind_rows(tree1, tree2, tree3) %>%
draw_tree() +
facet_wrap(~hred)
The randomness argument, as the name suggests, allows one to apply randomness to the angles and lengths of the plant. It must be set to TRUE if its effects are desired The amount of randomness can be controlled by the angle_cv and length_cv arguments, which define the angle and length variation coefficients, respectively. By default, these two arguments are set to 0.1,
tree1 <- build_tree(string = tree_string, angle = 15, randomness = TRUE) %>%
mutate(rand = 'length_cv = .1 (default)')
tree2 <- build_tree(string = tree_string, angle = 15, randomness = TRUE, length_cv = .5) %>%
mutate(rand = 'length_cv = .5')
tree3 <- build_tree(string = tree_string, angle = 15, randomness = TRUE) %>%
mutate(rand = 'angle_cv = .1 (default)')
tree4 <- build_tree(string = tree_string, angle = 15, randomness = TRUE, angle_cv = .5) %>%
mutate(rand = 'angle_cv = .5')
bind_rows(tree1, tree2, tree3, tree4) %>%
draw_tree() +
facet_wrap(~rand)
It’s possible to set the plant height setting the height parameter in the build_tree function. It will affect the resulting coordinates in the output.
To visualize the y axis with the heigth measure, ggplot2 functions can be used together with draw_tree function.
tree <- build_tree(string = tree_string, angle = 15, height = 10)
draw_tree(tree)+
labs(x = '', y = 'Height (m)')+
theme_bw()
As well as the height, in the build_tree function it is possible to define the diameter of branches, whose reduction is defined by the parentheses in the rules declared in the iterate_lsystem function, in the same way as lengths. The argument d_reduction controls the reduction factor for the diameters. This does not affect the coordinate matrix but only the plant visualization. As will be shown below.
draw_tree functionThis function was implemented on top of ggplot2 and for this reason it can be used together with several functions of the package.
To visualize the diameters along the plant, the diameter column must be declared in the d_col argument.
tree1 <- build_tree(string = tree_string, angle = 15, height = 10, diameter = 5, d_reduction = .7) %>%
mutate(dred = 'd_reduction = .7')
tree2 <- build_tree(string = tree_string, angle = 15, height = 10, diameter = 5) %>%
mutate(dred = 'd_reduction = .61803 (default)')
tree3 <- build_tree(string = tree_string, angle = 15, height = 10, diameter = 5, d_reduction = .5) %>%
mutate(dred = 'd_reduction = .5')
bind_rows(tree1, tree2, tree3) %>%
draw_tree(d_col = 'diameter')+
facet_wrap(~dred)
When the diameters are too large, the visualization may be compromised. The argument d_factor in the draw_tree function can solve this.
library(patchwork)
tree <- build_tree(string = tree_string, angle = 15, height = 10, diameter = 20)
p1 <- draw_tree(tree, d_col = 'diameter')+
labs(title = 'd_factor = NULL')
p2 <- draw_tree(tree, d_col = 'diameter', d_factor = .3)+
labs(title = 'd_factor = .3')
p1 + p2
Colors can be used to differentiate leaves and branches, by declaring the leaf_color and branch_color arguments. The build_tree function identifies as leaves all the branches positioned at the ends of the plant. This information is stored in the “type” column, which can be used to assign colors.
library(patchwork)
tree_string <- iterate_lsystem(init = "0", rules = binary_tree_rules, n = 10)
tree <- build_tree(string = tree_string, angle = 15, height = 10, diameter = 20)
draw_tree(tree, d_col = 'diameter', d_factor = .3, branch_color = 'lightsalmon4', leaf_color = 'darkgreen')
To get the matrix of coordinates of the plant, the get_tree_matrix can be used. Each pair of values corresponds to the coordinates of the bifurcation points and the ends of the created plant.
tree_matrix <- get_tree_matrix(tree)
head(tree_matrix)
## [,1] [,2]
## [1,] 0.000000e+00 0.000000
## [2,] 2.381501e-16 3.889414
## [3,] 6.221467e-01 6.211297
## [4,] 1.364959e+00 7.497885
## [5,] 2.014200e+00 8.147127
## [6,] 2.505633e+00 8.430856
The following code block presents procedures for calculating the fractal dimension of three trees of different morphological complexities.
After generating the trees, the fractal dimension can be calculated by the est.boxcount function of the Rdimtools package.
library(Rdimtools)
tree1_string <- iterate_lsystem(init = "0", rules = binary_tree_rules, n = 3)
tree1 <- build_tree(string = tree1_string, angle = 15, height = 5, diameter = 7, randomness = T)
tree2_string <- iterate_lsystem(init = "0", rules = binary_tree_rules, n = 5)
tree2 <- build_tree(string = tree2_string, angle = 15, height = 10, diameter = 14, randomness = T)
tree3_string <- iterate_lsystem(init = "0", rules = binary_tree_rules, n = 10)
tree3 <- build_tree(string = tree3_string, angle = 15, height = 15, diameter = 21, randomness = T)
# Bind the trees in one data.frame, establishing distances between them on the x axis
trees <- tree1 %>%
bind_rows(
tree2 %>%
mutate(from_x = from_x+5,
to_x = to_x+5)
) %>%
bind_rows(
tree3 %>%
mutate(from_x = from_x+13,
to_x = to_x+13)
)
tree1_matrix <- get_tree_matrix(tree1)
tree2_matrix <- get_tree_matrix(tree2)
tree3_matrix <- get_tree_matrix(tree3)
df1 <- est.boxcount(tree1_matrix)$estdim %>% round(2)
df2 <- est.boxcount(tree2_matrix)$estdim %>% round(2)
df3 <- est.boxcount(tree3_matrix)$estdim %>% round(2)
# Estimated fractal dimension for tree 1
df1
## [1] 1.06
# Estimated fractal dimension for tree 2
df2
## [1] 1.15
# Estimated fractal dimension for tree 3
df3
## [1] 1.19
The trees can be visualized simultaneously using the already presented draw_tree function.
draw_tree(trees, d_col = 'diameter', d_factor = .3)+
geom_text(aes(x=-2,y=-1,label='Fractal dimension:'), hjust = 1)+
geom_text(aes(x=0,y=-1,label=df1))+
geom_text(aes(x=5,y=-1,label=df2))+
geom_text(aes(x=13,y=-1,label=df3))+
scale_x_continuous(limits = c(-10,NA))