# -*- python -*-
#
# OpenAlea.mtg
#
# Copyright 2008-2009 INRIA - CIRAD - INRA
#
# File author(s): Christophe Pradal <christophe.pradal.at.cirad.fr>
#
# Distributed under the Cecill-C License.
# See accompanying file LICENSE.txt or copy at
# http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html
#
# OpenAlea WebSite : http://openalea.gforge.inria.fr
#
################################################################################
''' Data and default geometric parameters used to compute
the geometric interpretation of a MTG (i.e. PlantFrame)
:Description:
The dressing data contains the default data that are used to define
the geometry of an MTG vertices (i.e. of a plant entities)
and to compute their geometric parameters when inference algorithms
cannot be applied. These data are basically constant values and may be
redefined in the dressing file.
If no dressing file is defined, default (hard-coded) values are used
(see `Dressing files`). The dressing file .drf , if it exists in the
current directory, is always used as a default dressing file.
Objects of type DRESSING_DATA is used by primitive Plantframe.
It may also be used by primitive Plot when VIRTUAL_PATTERNs are plotted.
:Examples:
>>> g = MTG('a_MTG') #doctest: +SKIP
>>> d = DressingData('file') #doctest: +SKIP
>>> pf = PlantFrame(1, Scale=3, DressingData=d) #doctest: +SKIP
>>> Plot(pf) #doctest: +SKIP
'''
from math import pi
import os
from openalea.plantgl.scenegraph import AmapSymbol
[docs]
class DressingData(object):
""" Data and default geometric parameters.
The dressing data are the default data that are used to define
the geometric models associated with geometric entities
and to compute their geometric parameters when inference algorithms
cannot be applied.
These data are basically constant values and may be redefined by the user.
"""
def __init__(self, **kwds):
""" Build an object containing default parameters and geometric models
associated to category.
:Parameters:
- Classes : dict between a Class name (key) and a 3D shape (value).
- BranchPatterns: dict between a category and a 3D curve.
- LeafClass : symbol name associated to the leaf component.
- FlowerClass : symbol name associated to the flower component.
- FruitClass : symbol name associated to the fruit component.
- LengthUnit : Unit used to divide all the length data (default : 1)
- DiameterUnit : Unit used to divide all the length data (default : 1)
- AlphaUnit : Unit used to divide all the insertion angle (180/pi)
- AzimuthUnit : Unit used to divide all the azimuth angle (180/pi)
- DefaultEdge : Type of edge used to reconstruct a connected MTG (PLUS or LESS)
- DefaultAlpha : Default insertion angle (value in degrees with respect to the horizontal plane).
- DefaultTeta : Default first Euler angle
- DefaultPhi : Default second Euler angle
- DefaultPsi : Default third Euler angle
- DefaultTrunkCategory : Default category for elements of the plant trunk.
- DefaultCategory : The default category of the other axes than the trunk is their (botanical) order starting at 0 on the trunk.
- Alpha : Nature of the insertion angle (Absolute or Relative)
- Phyllotaxy : Phyllotaxic angle (given in degrees) or in number of turns over number of leaves for this number of turns (default : 180)
- MinLength: dict betwwen Class and default value for element of class `Class`.
- MinTopDiameter: dict betwwen Class and default value for element of class `Class`.
- MinBottomDiameter: dict betwwen Class and default value for element of class `Class`.
- LeafLength = 1
- LeafTopDiameter = 2
- LeafBottomDiameter = 2
- LeafAlpha = 0
- LeafBeta = 0
- FruitLength = 1
- FruitTopDiameter = 1
- FruitBottomDiameter = 1
- FruitAlpha = 0
- FruitBeta = 0
- FlowerLength = 10
- FlowerTopDiameter = 5
- FlowerBottomDiameter = 5
- FlowerAlpha = 180
- FlowerBeta = 0
- DefaultTrunkCategory = 0
- DefaultDistance = 1000
- NbPlantsPerLine = 6
# Colors for interpolation
- MediumThreshold
- MinThreshold
- MaxThreshold
Defining only top and bottom radius rather than width and height imply
an axial or radial symmetry
which is often the case for plant (leaves, branches, fruit).
"""
self.classes = kwds.get('Classes', {})
self.branch_patterns = kwds.get('BranchPatterns', {})
self.leaf_class = kwds.get('LeafClass')
self.flower_class = kwds.get('FlowerClass')
self.fruit_class = kwds.get('FruitClass')
self.length_unit = kwds.get('LengthUnit',1)
self.diameter_unit = kwds.get('DiameterUnit',1)
self.azimuth_unit = kwds.get('AzimuthUnit', 180/pi)
self.alpha_unit = kwds.get('AlphaUnit', 180/pi)
self.default_edge = kwds.get('DefaultEdge')
self.default_alpha= kwds.get('DefaultAlpha', 30)
self.default_teta= kwds.get('DefaultTeta', 0)
self.default_phi= kwds.get('DefaultPhi', 0)
self.default_psi= kwds.get('DefaultPsi', 0)
self.min_length = kwds.get('MinLength', {})
self.min_topdia = kwds.get('MinTopDiameter', {})
self.min_botdia = kwds.get('MinBottomDiameter', {})
self.leaf_class= kwds.get('LeafClass', 'L')
self.leaf_length = kwds.get('LeafLength', 50)
self.leaf_topdia = kwds.get('LeafTopDiameter', 5)
self.leaf_botdia = kwds.get('LeafBottomDiameter', 5)
self.leaf_alpha = kwds.get('LeafAlpha', 30)
self.leaf_beta= kwds.get('LeafBeta', 180)
self.fruit_class= kwds.get('FruitClass', 'F')
self.fruit_length = kwds.get('FruitLength', 50)
self.fruit_topdia = kwds.get('FruitTopDiameter', 5)
self.fruit_botdia = kwds.get('FruitBottomDiameter', 5)
self.fruit_alpha = kwds.get('FruitAlpha', 30)
self.fruit_beta= kwds.get('FruitBeta', 180)
self.flower_class= kwds.get('FlowerClass', 'W')
self.flower_length = kwds.get('FlowerLength', 50)
self.flower_topdia = kwds.get('FlowerTopDiameter', 5)
self.flower_botdia = kwds.get('FlowerBottomDiameter', 5)
self.flower_alpha = kwds.get('FlowerAlpha', 30)
self.flower_beta= kwds.get('FlowerBeta', 180)
self.min_threshold = kwds.get('MinThreshold',(0,255,0))
self.medium_threshold = kwds.get('MediumThreshold',(18,13,2))
self.max_threshold = kwds.get('MinThreshold',(0,255,255))
self.nb_whorl = kwds.get('Whorl', 2)
# Symbols is a dict between a name and a geom object
self.symbols = {}
# TODO: finish all the possible args.
# Refactor on smaller elements build separetly:
# - geometric properties by organs (leaves, flower, fruits).
# - geometric parameters for the plant
# - phyllotaxy, position between plants, ...
def dressing_data_from_file(fn):
f=open(fn)
dresser = dressing_data(f)
f.close()
return dresser
def dressing_data(file):
""" Parse a dressing data file and return a Dressing Data object.
"""
dresser = DressingData()
grammar = Reader(dresser)
words = []
line = 0
for l in file:
line+=1
l = l.strip()
if l.startswith('#') or not l:
continue
else:
status = grammar.parse(l, line)
print('\n'.join(grammar.errors))
return dresser
###############################################################
## Define the parser which is just a dict from keyword to a function
class Reader(object):
def __init__(self, dresser):
self.dresser = dresser
self.build_grammar()
self.init()
def init(self):
self.errors = []
self.smbpath = None
def parse(self, l, line):
""" Parse a line and update the dresser.
Parse a line of a dressing file by calling the appropriate function
and update the dresser object with the new values.
TODO: Error management
"""
self.line = line
words = l.split()
if words[0] in self.grammar:
key = words[0]
# process the line
f = self.grammar[key]
f(words)
else:
self._add_error(l, 'Unable to parse this line')
return True
def build_grammar(self):
self.grammar = {}
grammar = self.grammar
grammar['SMBPath'] = self.smb_path
grammar['SMBModel'] = self.smb_model
grammar['Class'] = self.klass
grammar['BranchPattern'] = self.branch_pattern
grammar['LeafClass'] = self.leaf_klass
grammar['FlowerClass'] = self.flower_klass
grammar['FruitClass'] = self.fruit_klass
grammar['LengthUnit'] = self.length_unit
grammar['DiameterUnit'] = self.diameter_unit
grammar['AlphaUnit'] = self.alpha_unit
grammar['AzimuthUnit'] = self.azimuth_unit
grammar['DefaultEdge'] = self.default_edge
grammar['DefaultAlpha'] = self.default_alpha
grammar['DefaultTeta'] = self.default_teta
grammar['DefaultPhi'] = self.default_phi
grammar['DefaultPsi'] = self.default_psi
grammar['DefaultTrunkCategory'] =self.default_trunk_category
grammar['DefaultCategory'] = self.default_category
grammar['Alpha'] =self.alpha
grammar['Phyllotaxy'] = self.phyllotaxy
grammar['MinLength'] = self.min_length
grammar['MinTopDiameter'] = self.min_top_diameter
grammar['MinBottomDiameter'] = self.min_bottom_diameter
grammar['LeafLength'] = self.leaf_length
grammar['LeafTopDiameter'] = self.leaf_topdia
grammar['LeafBottomDiameter'] = self.leaf_botdia
grammar['LeafAlpha'] = self.leaf_alpha
grammar['LeafBeta'] = self.leaf_beta
grammar['FruitLength'] = self.fruit_length
grammar['FruitTopDiameter'] = self.fruit_topdia
grammar['FruitBottomDiameter'] = self.fruit_botdia
grammar['FruitAlpha'] = self.fruit_alpha
grammar['FruitBeta'] = self.fruit_beta
grammar['FlowerLength'] = self.flower_length
grammar['FlowerTopDiameter'] = self.flower_topdia
grammar['FlowerBottomDiameter'] = self.flower_botdia
grammar['FlowerAlpha'] = self.flower_alpha
grammar['FlowerBeta'] = self.flower_beta
grammar['DefaultTrunkCategory'] = self.def_trunk_cat
grammar['DefaultDistance'] = self.def_dist
grammar['NbPlantsPerLine'] = self.nbplants_line
grammar['MediumThreshold'] = self.med_thres
grammar['MinThreshold'] = self.min_thres
grammar['MaxThreshold'] = self.max_thres
def _add_error(self, l, note=''):
msg = 'ERROR (%d): %s'%(self.line, ' '.join(l))
if note:
msg = '\n'.join([msg, ' --> %s'%note])
self.errors.append(msg)
def smb_path(self, l):
"SMBPath = path"
p = l[2]
if os.path.exists(p):
self.smbpath = p
else:
self._add_error(l, 'Invalid path')
def smb_model(self, l):
"SMBModel node = nentn105"
if not self.smbpath:
return
name = l[1]
file = l[3]+'.smb'
absfile = os.path.join(self.smbpath, file)
if os.path.exists(os.path.join(self.smbpath, file)):
geom = AmapSymbol(absfile)
if geom.isValid():
self.dresser.symbols[name] = geom
else:
self._add_error(l, 'Impossible to locate symbol file')
def klass (self, l):
"Class B = node"
class_name = l[1]
smb_name = l[3]
d = self.dresser
if smb_name in d.symbols:
d.classes[class_name] = smb_name
else:
self._add_error(l, '%s is not defined'%smb_name)
def branch_pattern (self, l):
"BranchPattern apple_forms = file.crv"
self._add_error(l, 'BranchPattern is not yet implemented')
def leaf_klass (self, l):
"LeafClass = Z"
name = l[2]
d = self.dresser
if name in d.symbols:
d.leaf_class = name
else:
self._add_error(l, '%s is not defined'%name)
def flower_klass(self, l):
"FlowerClass = Z"
name = l[2]
d = self.dresser
if name in d.symbols:
d.flower_class = name
else:
self._add_error(l, '%s is not defined'%name)
def fruit_klass (self, l):
"FruitClass = Z"
name = l[2]
d = self.dresser
if name in d.symbols:
d.fruit_class = name
else:
self._add_error(l, '%s is not defined'%name)
def length_unit(self, l):
"LengthUnit = 1"
unit = l[2]
try:
self.dresser.length_unit = float(unit)
except:
self._add_error(l, 'Bad unit format')
def diameter_unit(self, l):
"DiameterUnit = 1"
unit = l[2]
try:
self.dresser.diameter_unit = float(unit)
except:
self._add_error(l, 'Bad unit format')
def alpha_unit(self, l):
"AlphaUnit = 1"
unit = l[2]
try:
self.dresser.alpha_unit = float(unit)
except:
self._add_error(l, 'Bad unit format')
def azimuth_unit(self, l):
"AzimuthUnit = 1"
unit = l[2]
try:
self.dresser.azimuth_unit = float(unit)
except:
self._add_error(l, 'Bad unit format')
def default_edge(self, l):
"DefaultEdge = PLUS | LESS"
edge_type = l[2]
if edge_type in ['PLUS', 'LESS']:
self.default_edge = edge_type
else:
self._add_error(l, 'Bad edge format')
def default_alpha(self, l):
angle = l[2]
try:
self.dresser.default_alpha = float(angle)
except:
self._add_error(l, 'Bad angle format')
def default_teta(self, l):
angle = l[2]
try:
self.dresser.default_teta= float(angle)
except:
self._add_error(l, 'Bad angle format')
def default_phi(self, l):
angle = l[2]
try:
self.dresser.default_phi= float(angle)
except:
self._add_error(l, 'Bad angle format')
def default_psi (self, l):
angle = l[2]
try:
self.dresser.default_psi= float(angle)
except:
self._add_error(l, 'Bad angle format')
def default_trunk_category(self, l):
pass
def default_category(self, l):
pass
def alpha(self, l):
"Alpha = Absolute | Relative"
_alpha = l[2]
if _alpha in ['Absolute', 'Relative']:
self.dresser.alpha = _alpha
else:
self._add_error(l, 'Should be Absolute or Relative ')
def phyllotaxy(self, l):
angle = l[2]
try:
self.dresser.phyllotaxy= float(angle)
except:
self._add_error(l)
def min_length(self, l):
"MinLenght A = 10"
name = l[1]
value = l[3]
self.dresser.min_length[name] = float(value)
def min_top_diameter(self, l):
"MinTopDiameter A = 10"
name = l[1]
value = l[3]
self.dresser.min_topdia[name] = float(value)
def min_bottom_diameter(self, l):
"MinBottomDiameter A = 10"
name = l[1]
value = l[3]
self.dresser.min_botdia[name] = float(value)
def leaf_length(self, l):
"LeafLength = 100"
value = l[2]
try:
self.dresser.leaf_length= float(value)
except:
self._add_error(l, 'Bad format')
def leaf_topdia(self, l):
value = l[2]
try:
self.dresser.leaf_topdia= float(value)
except:
self._add_error(l, 'Bad format')
def leaf_botdia(self, l):
value = l[2]
try:
self.dresser.leaf_botdia= float(value)
except:
self._add_error(l, 'Bad format')
def leaf_alpha(self, l):
value = l[2]
try:
self.dresser.leaf_alpha= float(value)
except:
self._add_error(l, 'Bad format')
def leaf_beta(self, l):
value = l[2]
try:
self.dresser.leaf_beta= float(value)
except:
self._add_error(l, 'Bad format')
def fruit_length(self, l):
"LeafLength = 100"
value = l[2]
try:
self.dresser.fruit_length= float(value)
except:
self._add_error(l, 'Bad format')
def fruit_topdia(self, l):
value = l[2]
try:
self.dresser.fruit_topdia= float(value)
except:
self._add_error(l, 'Bad format')
def fruit_botdia(self, l):
value = l[2]
try:
self.dresser.fruit_botdia= float(value)
except:
self._add_error(l, 'Bad format')
def fruit_alpha(self, l):
value = l[2]
try:
self.dresser.fruit_alpha= float(value)
except:
self._add_error(l, 'Bad format')
def fruit_beta(self, l):
value = l[2]
try:
self.dresser.fruit_beta= float(value)
except:
self._add_error(l, 'Bad format')
def flower_length(self, l):
"LeafLength = 100"
value = l[2]
try:
self.dresser.flower_length= float(value)
except:
self._add_error(l, 'Bad format')
def flower_topdia(self, l):
value = l[2]
try:
self.dresser.flower_topdia= float(value)
except:
self._add_error(l, 'Bad format')
def flower_botdia(self, l):
value = l[2]
try:
self.dresser.flower_botdia= float(value)
except:
self._add_error(l, 'Bad format')
def flower_alpha(self, l):
value = l[2]
try:
self.dresser.flower_alpha= float(value)
except:
self._add_error(l, 'Bad format')
def flower_beta(self, l):
value = l[2]
try:
self.dresser.flower_beta= float(value)
except:
self._add_error(l, 'Bad format')
def def_trunk_cat(self, l):
pass
def def_dist(self, l):
pass
def nbplants_line(self, l):
pass
def med_thres(self, l):
pass
def min_thres(self, l):
pass
def max_thres(self, l):
pass