# -*- python -*-
#
# Copyright 2015 INRIA - CIRAD - INRA
#
# 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
#
# WebSite : https://github.com/openalea-incubator/caribu
#
# ==============================================================================
"""
Core pythonic functions to call caribu shell.
"""
from openalea.caribu.label import Label
from openalea.caribu.caribu_shell import Caribu
green_leaf_PAR = (0.06, 0.07)
green_stem_PAR = (0.13,)
soil_reflectance_PAR = 0.2
default_light = (1, (0, 0, -1))
[docs]
def pattern_string(pattern_tuple):
""" format pattern as caribu file string content
"""
x1, y1, x2, y2 = pattern_tuple
pattern_tuple = [(min(x1, x2), min(y1, y2)), (max(x1, x2), max(y1, y2))]
pattern = '\n'.join([' '.join(map(str, pattern_tuple[0])),
' '.join(map(str, pattern_tuple[1])), ' '])
return pattern
[docs]
def light_string(lights):
""" format lights as caribu light file string content
"""
def _as_string(light):
e, p = light
return ' '.join(map(str, [e] + list(p))) + '\n'
lines = list(map(_as_string, lights))
return ''.join(lines)
[docs]
def opt_string(species, soil_reflectance=-1):
""" format species as caribu opt file string content
"""
n = len(species)
o_string = 'n %s\n' % n
o_string += "s d %s\n" % soil_reflectance
species_sorted_keys = sorted(species.keys())
for key in species_sorted_keys:
po = species[key]
if sum(po) <= 0:
raise ValueError('Caribu do not accept black body material (absorptance=1)')
if len(po) == 1:
o_string += 'e d %s d -1 -1 d -1 -1\n' % po
elif len(po) == 2:
o_string += 'e d -1 d %s %s' % po + ' d %s %s\n' % po
else:
o_string += 'e d -1 d %s %s d %s %s\n' % po
return o_string
[docs]
def encode_labels(materials, species, x_mat=False):
mapping = {v: k for k, v in species.items()}
def _label(material):
lab = Label()
lab.plant_id = 1
lab.optical_id = mapping[material]
if x_mat:
transparent = len(material[0]) > 1
else:
transparent = len(material) > 1
if transparent:
lab.leaf_id = 1
return str(lab)
return [_label(m) for m in materials]
[docs]
def opt_string_and_labels(materials, soil_reflectance=-1):
""" format materials as caribu opt file string content and encode label
"""
species = {i + 1: po for i, po in enumerate(list(set(materials)))}
o_string = opt_string(species, soil_reflectance)
labels = encode_labels(materials, species)
return o_string, labels
[docs]
def x_opt_strings_and_labels(x_materials, x_soil_reflectance):
""" format multispectral materials as caribu opt file strings content
"""
x_opts = list(zip(*list(x_materials.values())))
x_species = {i + 1: po for i, po in enumerate(list(set(x_opts)))}
labels = encode_labels(x_opts, x_species, x_mat=True)
opt_strings = {}
for i, k in enumerate(x_materials.keys()):
species = {k: v[i] for k, v in x_species.items()}
opt_strings[k] = opt_string(species, x_soil_reflectance[k])
return opt_strings, labels
[docs]
def triangles_string(triangles, labels):
""" format triangles and associated labels as caribu canopy string content
"""
if len(triangles) != len(labels):
raise ValueError('The number of triangles and materials should match')
def _can_string(triangle, label):
s = "p 1 %s 3" % str(label)
for pt in triangle:
s += " %.6f %.6f %.6f" % pt
return s + '\n'
lines = [_can_string(t, l) for t, l in zip(triangles, labels)]
return ''.join(lines)
[docs]
def sensor_string(triangles):
""" format sensor triangles as caribu sensor string content
"""
n = len(triangles)
o_string = '#%s\n' % n
def _sensor_can_string(idx, triangle):
s = "p 1 %s 3" % str(idx)
for pt in triangle:
s += " %.6f %.6f %.6f" % pt
return s + '\n'
lines = [_sensor_can_string(i + 1, t) for i, t in enumerate(triangles)]
return o_string + ''.join(lines)
def _absorptance(material):
if len(material) <= 2:
return 1 - sum(material)
else:
return 1 - sum(material) / 2.
[docs]
def get_incident(eabs, materials):
""" estimate incident light using absorbed light and materials
For asymmetric materials, return a mean estimate
"""
# check for integrity of caribu output
if len(eabs) != len(materials):
raise ValueError("The number of caribu outputs doesn't match the number of inputs")
alpha = (_absorptance(m) for m in materials)
return [float(e) / a if a != 0 else e for e, a in zip(eabs, alpha)]
[docs]
def write_scene(triangles, materials, canfile, optfile):
if len(triangles) != len(materials):
raise ValueError(len(triangles), len(materials))
o_string, labels = opt_string_and_labels(materials)
can_string = triangles_string(triangles, labels)
open(canfile,'w').write(can_string)
open(optfile,'w').write(o_string)
[docs]
def raycasting(triangles, materials, lights=(default_light,), domain=None,
screen_size=1536, sensors=None, debug = False, canfile = None, optfile = None):
"""Compute monochrome illumination of triangles using caribu raycasting mode.
Args:
triangles: (list of list of tuples) a list of triangles, each being defined
by an ordered triplet of 3-tuple points coordinates.
materials: (list of tuple) a list of materials defining optical properties of triangles
A material is a 1-, 2- or 4-tuple depending on its optical behavior.
A 1-tuple encode the reflectance of an opaque material
A 2-tuple encode the reflectance and transmittance of a symmetric translucent material
A 4-tuple encode the reflectance and transmittance
of the upper and lower side of an asymmetric translucent material
lights: (list of tuples) a list of (Energy, (vx, vy, vz)) tuples defining ligh sources
By default a normalised zenithal light is used.
Energy is light flux passing through a unit area (scene unit) horizontal plane.
domain: (tuple of floats) 2D Coordinates of the domain bounding the scene for its replication.
(xmin, ymin, xmax, ymax) scene is not bounded along z axis
if None (default), scene is not repeated
screen_size: (int) buffer size for projection images (pixels)
sensors: (list of list of tuples) a list of triangles defining virtual sensors
Returns:
(dict of str:property) properties computed:
- index(int) : the indices of the input triangles present in outputs ?
- label(str) : the internal barcode (canlabel) used by caribu (for debugging)
- area (float): the individual areas of triangles
- Eabs (float): the surfacic density of energy absorbed by the triangles (absorbed_energy / area)
- Ei (float): the surfacic density of energy incoming on the triangles
- Ei_inf (float): the surfacic density of energy incoming on the inferior face of the triangle.
- Ei_sup (float): the surfacic density of energy incoming on the superior face of the triangle
- sensor (dict): a dict with id, area, surfacic density of incoming
direct energy and surfacic density of incoming total energy of sensors, if any
"""
if canfile is None or optfile is None:
o_string, labels = opt_string_and_labels(materials)
can_string = triangles_string(triangles, labels)
else:
if len(triangles) != len(materials):
raise ValueError(len(triangles), len(materials))
o_string = optfile
can_string = canfile
sky_string = light_string(lights)
if domain is None:
infinite = False
pattern_str = None
else:
infinite = True
pattern_str = pattern_string(domain)
if sensors is None:
sensor_str = None
else:
sensor_str = sensor_string(sensors)
algo = Caribu(canfile=can_string,
skyfile=sky_string,
optfiles=o_string,
patternfile=pattern_str,
sensorfile=sensor_str,
direct=True,
infinitise=infinite,
projection_image_size=screen_size,
resdir=None, resfile=None, debug=debug)
algo.run()
out = algo.nrj['band0']['data']
out['Ei'] = get_incident(out['Eabs'], materials)
if sensors is not None:
out['sensors'] = algo.measures['band0']
return out
[docs]
def x_raycasting(triangles, x_materials, lights=(default_light,), domain=None,
screen_size=1536, sensors=None, debug= False, canfile = None, optfile = None):
"""Compute monochrome illumination of triangles using caribu raycasting mode.
Args:
triangles: (list of list of tuples) a list of triangles, each being defined
by an ordered triplet of 3-tuple points coordinates.
x_materials: (dict of list of tuple) a {band_name: [materials]} dict defining optical properties of triangles
for different band/wavelength
A material is a 1-, 2- or 4-tuple depending on its optical behavior.
A 1-tuple encode an opaque material characterised by its reflectance
A 2-tuple encode a symmetric translucent material defined by a reflectance and a transmittance
A 4-tuple encode an asymmetric translucent material defined the reflectance and transmittance
of the upper and lower side respectively
lights: (list of tuples) a list of (Energy, (vx, vy, vz)) tuples defining ligh sources
By default a normalised zenithal light is used.
Energy is light flux passing through a unit area (scene unit) horizontal plane.
domain: (tuple of floats) 2D Coordinates of the domain bounding the scene for its replication.
(xmin, ymin, xmax, ymax) scene is not bounded along z axis
if None (default), scene is not repeated
screen_size: (int) buffer size for projection images (pixels)
sensors: (list of list of tuples) a list of triangles defining virtual sensors
Returns:
a ({band_name: {property_name:property_values} } dict of dict) with properties:
- index(int) : the indices of the input triangles present in outputs ?
- label(str) : the internal barcode (canlabel) used by caribu (for debuging)
- area (float): the individual areas of triangles
- Eabs (float): the surfacic density of energy absorbed by the triangles (absorbed_energy / area)
- Ei (float): the surfacic density of energy incoming on the triangles
- Ei_inf (float): the surfacic density of energy incoming on the inferior face of the triangle
- Ei_sup (float): the surfacic density of energy incoming on the superior face of the triangle
- sensor (dict): a dict with id, area, surfacic density of incoming
direct energy and surfacic density of incoming total energy of sensors, if any
"""
x_out = {}
# copy to avoid altering input dict
x_materials = {k: v for k, v in x_materials.items()}
band, materials = x_materials.popitem()
out = raycasting(triangles, materials, lights=lights, domain=domain,
screen_size=screen_size, sensors=sensors, debug=debug)
x_out[band] = out
for band in x_materials:
x_out[band] = {}
absorptance = (_absorptance(m) for m in x_materials[band])
for var in out:
if var != 'Eabs':
x_out[band][var] = out[var]
else:
x_out[band]['Eabs'] = [a * e for a, e in zip(absorptance, out['Ei'])]
return x_out
[docs]
def radiosity(triangles, materials, lights=(default_light,), screen_size=1536,
sensors=None, debug=False):
"""Compute monochromatic illumination of triangles using radiosity method.
Args:
triangles: (list of list of tuples) a list of triangles, each being defined
by an ordered triplet of 3-tuple points coordinates.
materials: (list of tuple) a list of materials defining optical properties of triangles
A material is a 1-, 2- or 4-tuple depending on its optical behavior.
A 1-tuple encode an opaque material characterised by its reflectance
A 2-tuple encode a symmetric translucent material defined by a reflectance and a transmittance
A 4-tuple encode an asymmetric translucent material defined the reflectance and transmittance
of the upper and lower side respectively
lights: (list of tuples) a list of (Energy, (vx, vy, vz)) tuples defining ligh sources
By default a normalised zenital light is used.
Energy is ligth flux passing throuh a unit area (scene unit) horizontal plane.
screen_size: (int) buffer size for projection images (pixels)
sensors: (list of list of tuples) a list of triangles defining virtual sensors
Returns:
(dict of str:property) properties computed:
- index(int) : the indices of the input triangles present in outputs ?
- label(str) : the internal barcode (canlabel) used by caribu (for debuging)
- area (float): the individual areas of triangles
- Eabs (float): the surfacic density of energy absorbed by the triangles (absorbed_energy / area)
- Ei (float): the surfacic density of energy incoming on the triangles
- Ei_inf (float): the surfacic density of energy incoming on the inferior face of the triangle
- Ei_sup (float): the surfacic density of energy incoming on the superior face of the triangle
- sensor (dict): a dict with id, area, surfacic density of incoming
direct energy and surfacic density of incoming total energy of sensors, if any
"""
if len(triangles) <= 1:
raise ValueError('Radiosity method needs at least two primitives')
o_string, labels = opt_string_and_labels(materials)
can_string = triangles_string(triangles, labels)
sky_string = light_string(lights)
if sensors is None:
sensor_str = None
else:
sensor_str = sensor_string(sensors)
algo = Caribu(canfile=can_string,
skyfile=sky_string,
optfiles=o_string,
sensorfile=sensor_str,
patternfile=None,
direct=False,
infinitise=False,
sphere_diameter=-1,
projection_image_size=screen_size,
resdir=None, resfile=None,debug=debug)
algo.run()
out = algo.nrj['band0']['data']
out['Ei'] = get_incident(out['Eabs'], materials)
if sensors is not None:
out['sensors'] = algo.measures['band0']
return out
[docs]
def x_radiosity(triangles, x_materials, lights=(default_light,),
screen_size=1536, sensors=None, debug=False):
"""Compute multi-chromatic illumination of triangles using radiosity method.
Args:
triangles: (list of list of tuples) a list of triangles, each being defined
by an ordered triplet of 3-tuple points coordinates.
x_materials: (dict of list of tuple) a {band_name: [materials]} dict defining optical properties of triangles
for different band/wavelength
A material is a 1-, 2- or 4-tuple depending on its optical behavior.
A 1-tuple encode an opaque material characterised by its reflectance
A 2-tuple encode a symmetric translucent material defined by a reflectance and a transmittance
A 4-tuple encode an asymmetric translucent material defined the reflectance and transmittance
of the upper and lower side respectively
lights: (list of tuples) a list of (Energy, (vx, vy, vz)) tuples defining ligh sources
By default a normalised zenital light is used.
Energy is ligth flux passing throuh a unit area (scene unit) horizontal plane.
screen_size: (int) buffer size for projection images (pixels)
sensors: (list of list of tuples) a list of triangles defining virtual sensors
Returns:
a {band_name: {property_name:property_values} } dict of dict) with properties:
- index(int) : the indices of the input triangles present in outputs ?
- label(str) : the internal barcode (canlabel) used by caribu (for debuging)
- area (float): the individual areas of triangles
- Eabs (float): the surfacic density of energy absorbed by the triangles (absorbed_energy / area)
- Ei (float): the surfacic density of energy incoming on the triangles
- Ei_inf (float): the surfacic density of energy incoming on the inferior face of the triangle
- Ei_sup (float): the surfacic density of energy incoming on the superior face of the triangle
- sensor (dict): a dict with id, area, surfacic density of incoming
direct energy and surfacic density of incoming total energy of sensors, if any
"""
if len(triangles) <= 1:
raise ValueError('Radiosity method needs at least two primitives')
no_soil = {band:-1 for band in x_materials}
opt_strings, labels = x_opt_strings_and_labels(x_materials, no_soil)
can_string = triangles_string(triangles, labels)
sky_string = light_string(lights)
if sensors is None:
sensor_str = None
else:
sensor_str = sensor_string(sensors)
caribu = Caribu(canfile=can_string,
skyfile=sky_string,
optfiles=list(opt_strings.values()),
optnames=list(opt_strings.keys()),
patternfile=None,
sensorfile=sensor_str,
direct=False,
infinitise=False,
sphere_diameter=-1,
projection_image_size=screen_size,
resdir=None, resfile=None, debug=debug)
caribu.run()
out = {k: v['data'] for k, v in caribu.nrj.items()}
for band in out:
out[band]['Ei'] = get_incident(out[band]['Eabs'], x_materials[band])
if sensors is not None:
out[band]['sensors'] = caribu.measures[band]
return out
[docs]
def mixed_radiosity(triangles, materials, lights, domain, soil_reflectance,
diameter, layers, height, screen_size=1536, sensors=None,
debug=False):
"""Compute monochrome illumination of triangles using mixed-radiosity model.
Args:
triangles: (list of list of tuples) a list of triangles, each being defined
by an ordered triplet of 3-tuple points coordinates.
materials: (list of tuple) a list of materials defining optical properties of triangles
A material is a 1-, 2- or 4-tuple depending on its optical behavior.
A 1-tuple encode an opaque material characterised by its reflectance
A 2-tuple encode a symmetric translucent material defined by a reflectance and a transmittance
A 4-tuple encode an asymmetric translucent material defined the reflectance and transmittance
of the upper and lower side respectively
lights: (list of tuples) a list of (Energy, (vx, vy, vz)) tuples defining light sources
Energy is light flux passing through a unit area (scene unit) horizontal plane.
domain: (tuple of floats) 2D Coordinates of the domain bounding the scene for its replication.
(xmin, ymin, xmax, ymax) scene is not bounded along z axis
soil_reflectance: (float) the reflectance of the soil
diameter: diameter (scene unit) of the sphere defining the close neighbourhood for local radiosity.
layers: vertical subdivisions of scene used for approximation of far contribution
height: upper limit of canopy layers (scene unit)
screen_size: (int) buffer size for projection images (pixels)
sensors: (list of list of tuples) a list of triangles defining virtual sensors
debug: (bool) Whether Caribu should be called in debug mode
Returns:
(dict of str:property) properties computed:
- index(int) : the indices of the input triangles present in outputs ?
- label(str) : the internal barcode (canlabel) used by caribu (for debuging)
- area (float): the indiviual areas of triangles
- Eabs (float): the surfacic density of energy absorbed by the triangles (absorbed_energy / area)
- Ei (float): the surfacic density of energy incoming on the triangles
- Ei_inf (float): the surfacic density of energy incoming on the inferior face of the triangle
- Ei_sup (float): the surfacic density of energy incoming on the superior face of the triangle
- sensor (dict): a dict with id, area, surfacic density of incoming
direct energy and surfacic density of incoming total energy of sensors, if any
"""
if len(triangles) <= 1:
raise ValueError('Radiosity method needs at least two primitives')
o_string, labels = opt_string_and_labels(materials, soil_reflectance)
can_string = triangles_string(triangles, labels)
sky_string = light_string(lights)
pattern_str = pattern_string(domain)
if sensors is None:
sensor_str = None
else:
sensor_str = sensor_string(sensors)
raise NotImplementedError(
'virtual sensors are not operational for mixed_radiosity')
algo = Caribu(canfile=can_string,
skyfile=sky_string,
optfiles=o_string,
patternfile=pattern_str,
sensorfile=sensor_str,
direct=False,
infinitise=True,
nb_layers=layers,
can_height=height,
sphere_diameter=diameter,
projection_image_size=screen_size,
resdir=None, resfile=None, debug=debug)
algo.run()
out = algo.nrj['band0']['data']
out['Ei'] = get_incident(out['Eabs'], materials)
if sensors is not None:
out['sensors'] = algo.measures['band0']
return out
[docs]
def x_mixed_radiosity(triangles, materials, lights, domain, soil_reflectance,
diameter, layers, height, sensors=None, screen_size=1536, debug=False):
"""Compute multi-chromatic illumination of triangles using mixed-radiosity model.
Args:
triangles: (list of list of tuples) a list of triangles, each being defined
by an ordered triplet of 3-tuple points coordinates.
materials: (dict of list of tuple) a {band_name: [materials]} dict defining optical properties of triangles
for different band/wavelength
A material is a 1-, 2- or 4-tuple depending on its optical behavior.
A 1-tuple encode an opaque material characterised by its reflectance
A 2-tuple encode a symmetric translucent material defined by a reflectance and a transmittance
A 4-tuple encode an asymmetric translucent material defined the reflectance and transmittance
of the upper and lower side respectively
lights: (list of tuples) a list of (Energy, (vx, vy, vz)) tuples defining light sources
Energy is light flux passing through a unit area (scene unit) horizontal plane.
domain: (tuple of floats) 2D Coordinates of the domain bounding the scene for its replication.
(xmin, ymin, xmax, ymax) scene is not bounded along z axis
soil_reflectance: (dict of float) a {band_name: reflectance} dict for the reflectances of the soil
diameter: diameter (scene unit) of the sphere defining the close neighbourhood for local radiosity.
layers: vertical subdivisions of scene used for approximation of far contribution
height: upper limit of canopy layers (scene unit)
screen_size: (int) buffer size for projection images (pixels)
sensors: (list of list of tuples) a list of triangles defining virtual sensors
Returns:
a ({band_name: {property_name:property_values} } dict of dict) with properties:
- index(int) : the indices of the input triangles present in outputs ?
- label(str) : the internal barcode (canlabel) used by caribu (for debugging)
- area (float): the individual areas of triangles
- Eabs (float): the surfacic density of energy absorbed by the triangles (absorbed_energy / area)
- Ei (float): the surfacic density of energy incoming on the triangles
- Ei_inf (float): the surfacic density of energy incoming on the inferior face of the triangle
- Ei_sup (float): the surfacic density of energy incoming on the superior face of the triangle
- sensor (dict): a dict with id, area, surfacic density of incoming
direct energy and surfacic density of incoming total energy of sensors, if any
"""
if len(triangles) <= 1:
raise ValueError('Radiosity method needs at least two primitives')
opt_strings, labels = x_opt_strings_and_labels(materials, soil_reflectance)
can_string = triangles_string(triangles, labels)
sky_string = light_string(lights)
pattern_str = pattern_string(domain)
if sensors is None:
sensor_str = None
else:
sensor_str = sensor_string(sensors)
raise NotImplementedError(
'virtual sensors are not operational for mixed_radiosity')
caribu = Caribu(canfile=can_string,
skyfile=sky_string,
optfiles=list(opt_strings.values()),
optnames=list(opt_strings.keys()),
patternfile=pattern_str,
sensorfile=sensor_str,
direct=False,
infinitise=True,
nb_layers=layers,
can_height=height,
sphere_diameter=diameter,
projection_image_size=screen_size,
resdir=None, resfile=None, debug=debug)
caribu.run()
out = {k: v['data'] for k, v in caribu.nrj.items()}
for band in out:
out[band]['Ei'] = get_incident(out[band]['Eabs'], materials[band])
return out