Skip to content

Commit c9efe30

Browse files
committed
Working tying and pruning
0 parents  commit c9efe30

33 files changed

+3629
-0
lines changed

README.rst

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
============
2+
TreeSim_Lpy
3+
============
4+
5+
6+
TreeSim_Lpy is a tree modelling tool which is built upon L-py with the added features of pruning
7+
and tying trees down to mimic different architectures. The tool uses python and prior knowledge of L-systems
8+
and L-Py is needed to work with this tool.
9+
10+
Python version 3.9
11+
12+
13+
The documentation is provided at https://treesim-lpy.readthedocs.io/en/latest/
14+
You can find the L-Py documentation at
15+
<https://lpy.readthedocs.io/en/latest>
16+
17+
========
18+
Gallery
19+
========
20+
.. figure:: media/envy.png
21+
:width: 500
22+
:height: 300
23+
24+
Example of a labelled, pruned and tied envy tree system using TreeSim_Lpy
25+
26+
27+
28+
.. figure:: media/ufo.png
29+
:width: 500
30+
:height: 300
31+
32+
Example of a labelled, pruned and tied UFO tree system using TreeSim_Lpy
33+
34+
35+
36+
37+
=============
38+
Documentation
39+
=============
40+
41+
Documentation is available at `<https://treesim-lpy.readthedocs.io/en/latest/>`_
42+
43+
Help and Support
44+
----------------
45+
46+
Please open an **Issue** if you need support or you run into any error (Installation, Runtime, etc.).
47+
We'll try to resolve it as soon as possible.
48+
49+
50+
==============
51+
Citations
52+
==============
53+
54+
- F. Boudon, T. Cokelaer, C. Pradal, P. Prusinkiewicz and C. Godin, L-Py: an L-system simulation framework for modeling plant architecture development based on a dynamic language, Frontiers in Plant Science, 30 May 2012.
55+

bug_test.lpy

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
"""
2+
Tying, Pruning, and Labelling Envy architecture tree
3+
"""
4+
import sys
5+
sys.path.append('../')
6+
from stochastic_tree import Support, BasicWood
7+
import numpy as np
8+
import random as rd
9+
import copy
10+
import gc
11+
import time
12+
13+
from helper import *
14+
15+
class Spur(BasicWood):
16+
count = 0
17+
def __init__(self, copy_from = None, max_buds_segment: int = 5, thickness: float = 0.1,\
18+
thickness_increment: float = 0.01, growth_length: float = 1., max_length: float = 7.,\
19+
tie_axis: list = [0,1,1], order: int = 1, prototype_dict: dict = {}, name = None, color = None):
20+
super().__init__(copy_from, max_buds_segment,thickness, thickness_increment, growth_length,\
21+
max_length, tie_axis, order, color)
22+
if copy_from:
23+
self.__copy_constructor__(copy_from)
24+
else:
25+
self.prototype_dict = prototype_dict
26+
if not name:
27+
self.name = str(self.__class__.__name__) +'_'+ str(self.__class__.count)
28+
Spur.count+=1
29+
30+
def is_bud_break(self, num_buds_segment):
31+
return (rd.random() < 0.1)
32+
33+
def create_branch(self):
34+
return None
35+
36+
def grow(self):
37+
pass
38+
39+
class Branch(BasicWood):
40+
count = 0
41+
def __init__(self, copy_from = None, max_buds_segment: int = 5, thickness: float = 0.1,\
42+
thickness_increment: float = 0.01, growth_length: float = 1., max_length: float = 7.,\
43+
tie_axis: tuple = (0,1,1), order: int = 1, prototype_dict: dict = {}, name = None, color = None):
44+
45+
super().__init__(copy_from, max_buds_segment,thickness, thickness_increment, growth_length,\
46+
max_length, tie_axis, order, color)
47+
if copy_from:
48+
self.__copy_constructor__(copy_from)
49+
else:
50+
self.prototype_dict = prototype_dict
51+
52+
if not name:
53+
self.name = str(self.__class__.__name__) +'_'+ str(self.__class__.count)
54+
Branch.count+=1
55+
56+
def is_bud_break(self, num_buds_segment):
57+
return (rd.random() < 0.02*(1 - num_buds_segment/self.max_buds_segment))
58+
59+
def create_branch(self):
60+
new_ob = Spur(copy_from = self.prototype_dict['spur'])
61+
return new_ob
62+
63+
def grow(self):
64+
pass
65+
66+
class NonTrunk(BasicWood):
67+
count = 0
68+
def __init__(self, copy_from = None, max_buds_segment: int = 5, thickness: float = 0.1,\
69+
thickness_increment: float = 0.01, growth_length: float = 1., max_length: float = 7.,\
70+
tie_axis: tuple = (0,1,1), order: int = 1, prototype_dict: dict = {}, name = None, color = None):
71+
72+
super().__init__(copy_from, max_buds_segment,thickness, thickness_increment, growth_length,\
73+
max_length, tie_axis, order, color)
74+
if copy_from:
75+
self.__copy_constructor__(copy_from)
76+
else:
77+
self.prototype_dict = prototype_dict
78+
79+
if not name:
80+
self.name = str(self.__class__.__name__) +'_'+ str(self.__class__.count)
81+
Branch.count+=1
82+
83+
def is_bud_break(self, num_buds_segment):
84+
return (rd.random() < 0.02*(1 - num_buds_segment/self.max_buds_segment))
85+
86+
def create_branch(self):
87+
new_ob = Spur(copy_from = self.prototype_dict['spur'])
88+
return new_ob
89+
90+
def grow(self):
91+
pass
92+
93+
94+
class Trunk(BasicWood):
95+
count = 0
96+
""" Details of the trunk while growing a tree, length, thickness, where to attach them etc """
97+
def __init__(self, copy_from = None, max_buds_segment: int = 5, thickness: float = 0.1,\
98+
thickness_increment: float = 0.01, growth_length: float = 1., max_length: float = 7.,\
99+
tie_axis: tuple = (0,1,1), order: int = 0, prototype_dict: dict = {}, name = None, color = None):
100+
101+
super().__init__(copy_from, max_buds_segment,thickness, thickness_increment, growth_length,\
102+
max_length, tie_axis, order, color)
103+
if copy_from:
104+
self.__copy_constructor__(copy_from)
105+
else:
106+
self.prototype_dict = prototype_dict
107+
if not name:
108+
self.name = str(self.__class__.__name__) +'_'+ str(self.__class__.count)
109+
Trunk.count+=1
110+
111+
def is_bud_break(self, num_buds_segment):
112+
if (rd.random() > 0.02*(1 - num_buds_segment/self.max_buds_segment)):
113+
return False
114+
return True
115+
116+
def create_branch(self):
117+
if rd.random() > 0.8:
118+
return Spur(copy_from = self.prototype_dict['spur'])
119+
else:
120+
return Branch(copy_from = self.prototype_dict['branch'])
121+
122+
def grow(self):
123+
pass
124+
125+
126+
127+
128+
#Pass transition probabs? --> solve with abstract classes
129+
130+
#basicwood_prototypes = {}
131+
#basicwood_prototypes['trunk'] = Trunk(tie_axis = [0,1,1], max_length = 20, thickness_increment = 0.02, prototype_dict = basicwood_prototypes, color = 0)
132+
#basicwood_prototypes['branch'] = Branch(tie_axis = [0,1,1], max_length = 20, thickness_increment = 0.005, prototype_dict = basicwood_prototypes, color = 1)
133+
#basicwood_prototypes['spur'] = Spur(tie_axis = [0,1,1], max_length = 1, thickness_increment = 0.005, prototype_dict = basicwood_prototypes, color = 2)
134+
135+
growth_length = 0.1
136+
#everything is relative to growth length
137+
basicwood_prototypes = {}
138+
basicwood_prototypes['trunk'] = Trunk(tie_axis = (0,1,1), max_length = 2.5/growth_length, thickness = 0.01, growth_length = 0.1,thickness_increment = 0.0005, prototype_dict = basicwood_prototypes, color = 0)
139+
basicwood_prototypes['branch'] = Branch(tie_axis = (0,1,1), max_length = .45/growth_length, thickness = 0.01, growth_length = 0.1,thickness_increment = 0.00005, prototype_dict = basicwood_prototypes, color = 1)
140+
basicwood_prototypes['nontrunkbranch'] = NonTrunk(tie_axis = (0,0,1), max_length = 0.1/growth_length, growth_length = 0.1, thickness = 0.0001,thickness_increment = 0.0001, prototype_dict = basicwood_prototypes, color = 1)
141+
basicwood_prototypes['spur'] = Spur(tie_axis = (0,1,1), max_length = 0.01/growth_length, thickness = 0.005, growth_length = 0.01,thickness_increment = 0., prototype_dict = basicwood_prototypes, color = 2)
142+
143+
#init
144+
trunk_base = Trunk(copy_from = basicwood_prototypes['trunk'])
145+
time_count = 0
146+
label = True
147+
def generate_points_v_trellis():
148+
x = np.full((7,), 1.45).astype(float)
149+
#z = np.arange(3, 24, 3).astype(float)
150+
y = np.full((7,), 0).astype(float)
151+
z = np.arange(0.6,3.4, 0.45)
152+
pts = []
153+
id = 0
154+
for i in range(x.shape[0]):
155+
pts.append((-x[i], y[i], z[i]))
156+
id+=1
157+
pts.append((x[i], y[i], z[i]))
158+
id+=1
159+
return pts
160+
161+
162+
163+
support = Support(generate_points_v_trellis(), 14 , 1 , None, (0,0,1), None)
164+
num_iteration_tie = 30
165+
166+
###Tying stuff begins
167+
168+
def ed(a,b):
169+
return np.linalg.norm(a-b)
170+
171+
def get_energy_mat(branches, arch):
172+
#branches = [i for i in branches if "Branch" in i.name]
173+
num_branches = len(branches)
174+
num_wires = len(list(arch.branch_supports.values()))
175+
energy_matrix = np.ones((num_branches,num_wires))*np.inf
176+
#print(energy_matrix.shape)
177+
for branch_id, branch in enumerate(branches):
178+
if branch.has_tied:
179+
continue
180+
for wire_id, wire in arch.branch_supports.items():
181+
if wire.num_branch>=1:
182+
continue
183+
energy_matrix[branch_id][wire_id] = ed(wire.point,branch.end)/2+ed(wire.point,branch.start)/2#+v.num_branches*10+branch.bend_energy(deflection, curr_branch.age)
184+
return energy_matrix
185+
186+
def decide_guide(energy_matrix, branches, arch):
187+
for i in range(energy_matrix.shape[0]):
188+
min_arg = np.argwhere(energy_matrix == np.min(energy_matrix))
189+
#print(min_arg)
190+
if(energy_matrix[min_arg[0][0]][min_arg[0][1]] == np.inf):# or energy_matrix[min_arg[0][0]][min_arg[0][1]] > 1:
191+
return
192+
if not (branches[min_arg[0][0]].has_tied == True):# and not (arch.branch_supports[min_arg[0][1]].num_branch >=1):
193+
#print("Imp:",min_arg[0][0], min_arg[0][1], energy_matrix[min_arg[0][0]][min_arg[0][1]])
194+
branches[min_arg[0][0]].guide_target = arch.branch_supports[min_arg[0][1]]#copy.deepcopy(arch.branch_supports[min_arg[0][1]].point)
195+
#trellis_wires.trellis_pts[min_arg[0][1]].num_branches+=1
196+
for j in range(energy_matrix.shape[1]):
197+
energy_matrix[min_arg[0][0]][j] = np.inf
198+
for j in range(energy_matrix.shape[0]):
199+
energy_matrix[j][min_arg[0][1]] = np.inf
200+
201+
def tie(lstring):
202+
for j,i in enumerate(lstring):
203+
if i == 'C' and i[0].type.__class__.__name__ == 'Branch':
204+
if i[0].type.tie_updated == False:
205+
continue
206+
curr = i[0]
207+
if i[0].type.guide_points:
208+
#print("tying ", i[0].type.name, i[0].type.guide_target.point)
209+
i[0].type.tie_updated = False
210+
i[0].type.guide_target.add_branch()
211+
lstring, count = i[0].type.tie_lstring(lstring, j)
212+
213+
return True
214+
return False
215+
216+
#Pruning strategy
217+
218+
def pruning_strategy(lstring): #Remove remnants of cut
219+
cut = False
220+
221+
for j,i in enumerate(lstring):
222+
223+
if i.name == 'C' and i[0].type.age > 10 and i[0].type.has_tied == False and i[0].type.cut == False:
224+
225+
i[0].type.cut = True
226+
#print("Cutting", i[0].type.name)
227+
lstring = cut_from(j, lstring)
228+
229+
return True
230+
231+
return False
232+
233+
def StartEach(lstring):
234+
global parent_child_dict
235+
for i in parent_child_dict[trunk_base.name]:
236+
if i.tie_updated == False:
237+
i.tie_update()
238+
239+
240+
def EndEach(lstring):
241+
global parent_child_dict, support
242+
tied = False
243+
244+
if (getIterationNb()+1)%num_iteration_tie == 0:
245+
energy_matrix = get_energy_mat(parent_child_dict[trunk_base.name], support)
246+
print(energy_matrix)
247+
decide_guide(energy_matrix, parent_child_dict[trunk_base.name], support)
248+
for branch in parent_child_dict[trunk_base.name]:
249+
branch.update_guide(branch.guide_target)
250+
print(branch.name, branch.guide_target)
251+
while tie(lstring):
252+
pass
253+
while pruning_strategy(lstring):
254+
pass
255+
return lstring
256+
257+
parent_child_dict = {}
258+
parent_child_dict[trunk_base.name] = []
259+
#print(generate_points_ufo())
260+
module Attractors
261+
module grow_object
262+
module bud
263+
module branch
264+
module C
265+
Axiom: Attractors(support)grow_object(trunk_base)
266+
derivation length: 100
267+
268+
production:
269+
#Decide whether branch internode vs trunk internode need to be the same size.
270+
grow_object(o) :
271+
if o == None:
272+
produce *
273+
if o.length >= o.max_length:
274+
o.age+=1
275+
nproduce *
276+
else:
277+
if label:
278+
nproduce SetColor(o.color)
279+
o.grow_one()
280+
produce I(o.growth_length, o.thickness, o)bud(ParameterSet(type = o, num_buds = 0))grow_object(o)
281+
282+
bud(t) :
283+
if t.type.is_bud_break(t.num_buds):
284+
new_object = t.type.create_branch()
285+
if new_object == None:
286+
produce *
287+
parent_child_dict[new_object.name] = []
288+
parent_child_dict[t.type.name].append(new_object)
289+
#Store new object somewhere
290+
t.num_buds+=1
291+
t.type.num_branches+=1
292+
nproduce [@RGetPos(new_object.start)C(ParameterSet(type = new_object))/(rd.random()*360)&(rd.random()*90)grow_object(new_object)GetPos(new_object.end)]bud(t)
293+
294+
295+
I(s,r,o) --> I(s,r+o.thickness_increment, o)
296+
_(r) --> _(r+o.thickness_increment)
297+
298+
homomorphism:
299+
300+
I(a,r,o) --> F(a,r)
301+
S(a,r,o) --> F(a,r)
302+
303+
production:
304+
Attractors(support):
305+
pttodisplay = support.attractor_grid.get_enabled_points()
306+
if len(pttodisplay) > 0:
307+
produce [,(3) @g(PointSet(pttodisplay,width=10))]

docs/Makefile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Minimal makefile for Sphinx documentation
2+
#
3+
4+
# You can set these variables from the command line.
5+
SPHINXOPTS =
6+
SPHINXBUILD = sphinx-build
7+
SPHINXPROJ = simpleble
8+
SOURCEDIR = source
9+
BUILDDIR = build
10+
11+
# Put it first so that "make" without argument is like "make help".
12+
help:
13+
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14+
15+
.PHONY: help Makefile
16+
17+
# Catch-all target: route all unknown targets to Sphinx using the new
18+
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19+
%: Makefile
20+
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

docs/_static/envy_3d.mov

10.1 MB
Binary file not shown.

docs/_static/envy_grow.mov

1.3 MB
Binary file not shown.

0 commit comments

Comments
 (0)