4. Light models options: how to set up the light models

4.1. Content

  • CARIBU

    • Computing the sun position

    • Grid of virtual sensors

    • Other parameters

  • RATP

    • Leaf angle distribution

    • Triangles tesselation in a grid

    • Other parameters

4.2. Introduction

During our use of lightvegemanager, we added special features for each known light models. This notebook gives you a small introduction to them.

The parameters of those features are stored in a dict.

[1]:
import os
from lightvegemanager.LVM import LightVegeManager
from pgljupyter import SceneWidget
from lightvegemanager.trianglesmesh import random_triangle_generator

4.3. CARIBU

This is the complete parameters you can provide with CARIBU:

caribu_args = {
                    "sun algo" : "ratp",
                    "sun algo" : "caribu",

                    "caribu opt" : {
                                    band0 = (reflectance, transmittance),
                                    band1 = (reflectance, transmittance),
                                    ...
                                    },
                    "debug" : bool,
                    "soil mesh" : bool,
                    "sensors" : ["grid", dxyz, nxyz, orig, vtkpath, "vtk"]
                }

4.3.1. Computing the sun position

In order to compute the sun position, you can use either the algorithm from RATP or CARIBU. The (x, y, z) output is formatted in CARIBU format.

[2]:
caribu_args = { "sun algo" : "caribu" }

lighting = LightVegeManager(lightmodel="caribu", lightmodel_parameters=caribu_args)
lighting.build(geometry=[(0., 0., 0.), (1., 0., 0.), (1., 1., 1.)])
energy = 500.
hour = 15
day = 264
lighting.run(energy=energy, hour=hour, day=day)

sun_caribu = lighting.sun
print(sun_caribu)
(0.33506553253259913, -0.8798617206271511, -0.3370080733212115)
[3]:
caribu_args = { "sun algo" : "ratp" }

lighting = LightVegeManager(lightmodel="caribu", lightmodel_parameters=caribu_args)
lighting.build(geometry=[(0., 0., 0.), (1., 0., 0.), (1., 1., 1.)])
energy = 500.
hour = 15
day = 264
lighting.run(energy=energy, hour=hour, day=day)

sun_ratp = lighting.sun
print(sun_ratp)
(0.33241183146897624, -0.8800565622452903, -0.3391206592769639)
[4]:
dist = (sum([ (x-y)**2 for x,y in zip(sun_ratp, sun_caribu) ])) ** (1/2)
print("euclidean dist = ",dist," m")
euclidean dist =  0.003397515564596359  m

4.3.2. Grid of virtual sensors

If you can to match a grid of voxels, you can generate a set of virtual sensors following a 3D grid. You need to precise the dimension of the grid: - dxyz: [dx, dy, dz] size of one voxel - nxyz: [nx, ny, nz] number of voxels on each xyz axis - orig: [0x, 0y, 0z] origin point of the grid

You can write a geometric visualisation of the sensors in VTK format, or export a plantGL Scene of with the method to_VTK and to_plantGL and the option virtual_sensors=True.

Also, you can provide a grid following some or all of the input scenes by defining a dict. For example:

sensors = {
        scene_id_1 : ["grid", dxyz, nxyz, orig],
        scene_id_2 : ["grid", dxyz, nxyz, [x + 0.5 for x in orig] ]
        }
[3]:
# grid dimensions
dxyz = [1.] * 3
nxyz = [5, 5, 7]
orig = [0.] * 3
[4]:
# random triangles
nb_triangles = 50
spheresize = (1., 0.3) # vertices of triangles are the sphere surface
triangles = []
for i in range(nb_triangles):
    triangles.append(random_triangle_generator(worldsize=(0., 5.), spheresize=spheresize))
[5]:
caribu_args = { "sensors" : ["grid", dxyz, nxyz, orig] }

lighting = LightVegeManager(lightmodel="caribu", lightmodel_parameters=caribu_args, environment={"infinite":True})
lighting.build(geometry=triangles)

You can visualize the grid of sensors with plantGL through the method plantGL_sensors

[6]:
SceneWidget(lighting.to_plantGL(virtual_sensors=True)[1],
            position=(-2.5, -2.5, 0.0),
            size_display=(600, 400),
            plane=True,
            size_world = 10,
            axes_helper=True)
[6]:

The lighting results are stored in sensors_outputs

[9]:
energy = 500.
hour = 15
day = 264
lighting.run(energy=energy, hour=hour, day=day)

print(lighting.sensors_outputs)
{'par': {0: 0.5357183881257019, 1: 0.4686494843618967, 2: 0.43585410695767945, 3: 0.7773054342426935, 4: 0.7653565097977126, 5: 0.8234079750501035, 6: 0.4758490954881396, 7: 0.5623902514683649, 8: 0.6648303684704121, 9: 0.7523744755517257, 10: 0.757628513619047, 11: 0.7759095328197766, 12: 0.5042164344666725, 13: 0.5273952940619577, 14: 0.618460535502636, 15: 0.7471856369784272, 16: 0.7002461334186968, 17: 0.8083881379807872, 18: 0.5731391756180516, 19: 0.6005392197593685, 20: 0.5830230691495272, 21: 0.668598834740319, 22: 0.7491856052337844, 23: 0.7778896638896193, 24: 0.45941263771422963, 25: 0.5231994757990336, 26: 0.5730702589058677, 27: 0.6802830730422291, 28: 0.768563206195468, 29: 0.8783160299524551, 30: 0.4879256729568832, 31: 0.3986838108537597, 32: 0.5107879595674222, 33: 0.7104544959233176, 34: 0.6104289235914225, 35: 0.8335714361222757, 36: 0.49402264002060914, 37: 0.5236661627298473, 38: 0.5559437694123442, 39: 0.7801809144392181, 40: 0.7482355370820352, 41: 0.8703959530204384, 42: 0.4437212327094782, 43: 0.48943914843165776, 44: 0.6265471398912993, 45: 0.6242676962673399, 46: 0.6242541832847409, 47: 0.8029465687456536, 48: 0.4552489809791363, 49: 0.5706269863386113, 50: 0.6707753329147661, 51: 0.6173647331272717, 52: 0.5817093348224309, 53: 0.8761273435958801, 54: 0.39061205289281303, 55: 0.4876144245567773, 56: 0.6081462607497906, 57: 0.6490748578167109, 58: 0.6112989310198034, 59: 0.9081399625253275, 60: 0.4382387194995509, 61: 0.24511969967129915, 62: 0.5564013821314899, 63: 0.6811375757235048, 64: 0.770734538810677, 65: 0.6876474556435463, 66: 0.4333876306307797, 67: 0.5117511864924423, 68: 0.5452643050348251, 69: 0.5855297120040052, 70: 0.7679378169458118, 71: 0.7374054106105965, 72: 0.46718762617607673, 73: 0.515406942360565, 74: 0.5498630958709402, 75: 0.38157016078052153, 76: 0.6230183331063252, 77: 0.9297880197486428, 78: 0.42012696485414797, 79: 0.43441190921900263, 80: 0.576547648435128, 81: 0.575563926783583, 82: 0.5587431661289568, 83: 0.9428844615982549, 84: 0.40797647513225416, 85: 0.32045180842520204, 86: 0.3860501936085704, 87: 0.6035746806015782, 88: 0.6761415094988514, 89: 0.9147231881446279, 90: 0.35723846481058197, 91: 0.29971902254716964, 92: 0.4617676006672053, 93: 0.6670186733643282, 94: 0.7729889949287282, 95: 0.8223626142139061, 96: 0.43689040185742767, 97: 0.5034266289477778, 98: 0.614485119215983, 99: 0.6753609146208617, 100: 0.8209389374222003, 101: 0.88137358368075, 102: 0.5060744695288328, 103: 0.5006922321596806, 104: 0.5450143532935812, 105: 0.669825913948119, 106: 0.7948585486270389, 107: 0.9497236962858565, 108: 0.5240235293870577, 109: 0.5291828813059647, 110: 0.5584025594129878, 111: 0.7009911880575855, 112: 0.6644575143621863, 113: 0.896722396622886, 114: 0.48066310311501675, 115: 0.45339172597584754, 116: 0.4928664818657807, 117: 0.6921498380364002, 118: 0.6582322232034805, 119: 0.8062622917317241, 120: 0.4165301298570477, 121: 0.47752211531730254, 122: 0.5422072915168136, 123: 0.7320985553256096, 124: 0.760961505134305, 125: 0.8425522336315453, 126: 0.48285036844495494, 127: 0.4897295356464291, 128: 0.6751897497018744, 129: 0.7650357314236249, 130: 0.6888417962718921, 131: 0.9099594684378505, 132: 0.4801370709108044, 133: 0.5233846696457297, 134: 0.5942217135899492, 135: 0.7384234333722701, 136: 0.8064837117567087, 137: 0.955340358423788, 138: 0.5755258055313021, 139: 0.5351956047253674, 140: 0.5430459909905389, 141: 0.7603523762894485, 142: 0.8096038729562012, 143: 0.9210479253387107, 144: 0.49256349385820036, 145: 0.4352151194578986, 146: 0.4353486297408457, 147: 0.7458275318718196, 148: 0.7991276473463365, 149: 0.9117731862505419}}

You can also visualize the results in the plantGL scene

[10]:
SceneWidget(lighting.to_plantGL(lighting=True, virtual_sensors=True)[1],
            position=(-2.5, -2.5, 0.0),
            size_display=(600, 400),
            plane=True,
            size_world = 10,
            axes_helper=True)
[10]:
[11]:
scene, sensors = lighting.to_plantGL(lighting=True, virtual_sensors=True)
SceneWidget(scene + sensors,
            position=(-2.5, -2.5, 0.0),
            size_display=(600, 400),
            plane=True,
            size_world = 10,
            axes_helper=True)
[11]:

4.3.3. Other parameters

In additional features, you can activate the debug mode in CARIBU, which describe the internal steps.

[6]:
caribu_args = { "debug" : True }

lighting = LightVegeManager(lightmodel="caribu", lightmodel_parameters=caribu_args)
lighting.build(geometry=[(0., 0., 0.), (1., 0., 0.), (1., 1., 1.)])

energy = 500.
hour = 15
day = 264
lighting.run(energy=energy, hour=hour, day=day)
Prepare scene 1
done
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
Cell In[6], line 9
      7 hour = 15
      8 day = 264
----> 9 lighting.run(energy=energy, hour=hour, day=day)

File c:\users\mwoussen\cdd\codes\dev\lightvegemanager\src\lightvegemanager\tool.py:533, in LightVegeManager.run(self, energy, day, hour, parunit, truesolartime, id_sensors)
    531 if sun_sky_option == "mix":
    532     start = time.time()
--> 533     raw_sun, aggregated_sun = run_caribu(*arg)
    534     arg[0] = c_scene_sky
    535     raw_sky, aggregated_sky = run_caribu(*arg)

File c:\users\mwoussen\cdd\codes\dev\lightvegemanager\src\lightvegemanager\CARIBUinputs.py:370, in run_caribu(c_scene, direct_active, infinite, sensors, energy)
    329 """runs caribu depending on input options
    330
    331 :param c_scene: instance of CaribuScene containing geometry, light source(s), opt etc...
   (...)
    367 :rtype: dict of dict, dict of dict
    368 """
    369 if sensors is None :
--> 370     raw, aggregated = c_scene.run(direct=direct_active, infinite=infinite)
    371 else :
    372     raw, aggregated = c_scene.run(direct=direct_active, infinite=infinite,
    373                                                         sensors=sensors)

File ~\AppData\Local\miniconda3\envs\mobidivpy37\lib\site-packages\alinea.caribu-8.0.7-py3.8.egg\alinea\caribu\CaribuScene.py:568, in CaribuScene.run(self, direct, infinite, d_sphere, layers, height, screen_size, screen_resolution, sensors, split_face, simplify)
    566         self.canfile = os.path.join(self.tempdir,'cscene.can')
    567         self.optfile = os.path.join(self.tempdir,'band0.opt')
--> 568         write_scene(triangles, materials, canfile = self.canfile, optfile = self.optfile)
    570 else:
    571     # self.materialvalues is a cache for the computation of the material list
    572     materials = self.materialvalues

File ~\AppData\Local\miniconda3\envs\mobidivpy37\lib\site-packages\alinea.caribu-8.0.7-py3.8.egg\alinea\caribu\caribu.py:177, in write_scene(triangles, materials, canfile, optfile)
    175 o_string, labels = opt_string_and_labels(materials)
    176 can_string = triangles_string(triangles, labels)
--> 177 open(canfile,'w').write(can_string)
    178 open(optfile,'w').write(o_string)

FileNotFoundError: [Errno 2] No such file or directory: './caribuscene_2672400716080\\cscene.can'

You can also use the soilmesh option and get the lighting hitting the soil. The method soilenergy get you access to its result.

[12]:
caribu_args = { "soil mesh" : True }

lighting = LightVegeManager(lightmodel="caribu", lightmodel_parameters=caribu_args)
lighting.build(geometry=[(0., 0., 0.), (1., 0., 0.), (1., 1., 1.)])

energy = 500.
hour = 15
day = 264
lighting.run(energy=energy, hour=hour, day=day)

print(lighting.soilenergy)
{'Qi': 0.6750540873627096, 'Einc': 0.6750540873627096}

4.4. RATP

This is the complete parameters you can provide with CARIBU:

ratp_args = {
                # Grid specifications
                "voxel size" : [dx, dy, dz],
                "voxel size" : "dynamic",

                "full grid" : bool,

                "origin" : [xorigin, yorigin, zorigin],
                "origin" : [xorigin, yorigin],

                "number voxels" : [nx, ny, nz],
                "grid slicing" : "ground = 0."
                "tesselation level" : int

                # Leaf angle distribution
                "angle distrib algo" : "compute global",
                "angle distrib algo" : "compute voxel",
                "angle distrib algo" : "file",

                "nb angle classes" : int,
                "angle distrib file" : filepath,

                # Vegetation type
                "soil reflectance" : [reflectance_band0, reflectance_band1, ...],
                "reflectance coefficients" : [reflectance_band0, reflectance_band1, ...],
                "mu" : [mu_scene0, mu_scene1, ...]
            }

4.4.1. Leaf angle distribution

Leaf angle distribution can be generated in 3 ways: - from a file, one distribution per specy - global and dynamically, it generates a distribution from a triangles mesh for each specy - per voxel and dynamically, it generates a distribution from the triangles located in each voxel

[2]:
# random triangles
nb_triangles = 50
spheresize = (1., 0.3) # vertices of triangles are the sphere surface
worldsize = (0., 5.)
triangles = [random_triangle_generator(worldsize=worldsize, spheresize=spheresize) for i in range(nb_triangles)]

4.4.1.1. File

You need the flag "file" and to specify the file path.

[15]:
filepath = os.path.join(os.path.dirname(os.path.abspath("")), "data", "luzerne_angle_distrib.data")
ratp_parameters = { "angle distrib algo" : "file", "angle distrib file" : filepath }

# initialize the instance
lighting = LightVegeManager(lightmodel="ratp", lightmodel_parameters=ratp_parameters)

# build the scene
lighting.build(geometry=triangles)

print(lighting.leafangledistribution)
{'global': [[0.1382, 0.1664, 0.1972, 0.1925, 0.1507, 0.0903, 0.0425, 0.0172, 0.005]]}

4.4.1.2. Global distribution

You need the flag "compute global" and to specify the number of angle classes you need.

[3]:
ratp_parameters = { "angle distrib algo" : "compute global", "nb angle classes" : 9 }

# initialize the instance
lighting = LightVegeManager(lightmodel="ratp", lightmodel_parameters=ratp_parameters)

# build the scene
lighting.build(geometry=triangles)

print(lighting.leafangledistribution)
{'global': [[0.0, 0.01403472520994376, 0.14553389876181141, 0.13870880399511626, 0.17786938035077418, 0.06842434118299212, 0.09608726894836216, 0.20983153542452182, 0.14951004612647875]]}

4.4.1.3. Local distribution

You need the flag "compute voxel" and to specify the number of angle classes you need. You will get one distribution for each voxel of your grid and each specy.

[6]:
ratp_parameters = {
                    "voxel size" : [1., 1., 1.],
                    "angle distrib algo" : "compute voxel",
                    "nb angle classes" : 9
                    }

# initialize the instance
lighting = LightVegeManager(lightmodel="ratp", lightmodel_parameters=ratp_parameters)

# build the scene
lighting.build(geometry=triangles)

print("Global")
print(lighting.leafangledistribution["global"])
print("\n\n Local")
for a in lighting.leafangledistribution["voxel"]:
    print(a[0])
Global
[[0.009674637223767685, 0.06468549489243054, 0.10705660994810652, 0.10196105003699654, 0.07575759861707439, 0.3044721953841436, 0.06446975879644407, 0.14828246866341796, 0.12364018643761852]]


 Local
[0. 0. 0. 0. 0. 0. 0. 1. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 1.]
[0. 0. 1. 0. 0. 0. 0. 0. 0.]
[0.         0.         0.55805526 0.44194474 0.         0.
 0.         0.         0.        ]
[0. 0. 0. 0. 0. 0. 1. 0. 0.]
[0. 0. 0. 0. 1. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 1. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 1. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 1.]
[0. 0. 0. 0. 1. 0. 0. 0. 0.]
[0. 1. 0. 0. 0. 0. 0. 0. 0.]
[0. 1. 0. 0. 0. 0. 0. 0. 0.]
[0. 1. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 1. 0. 0. 0. 0. 0.]
[0. 1. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 1. 0. 0. 0. 0.]
[0.         0.         0.41000514 0.58999486 0.         0.
 0.         0.         0.        ]
[0. 0. 0. 0. 0. 1. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 1.]
[0. 0. 1. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 1. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 1. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 1. 0. 0. 0. 0.]
[0. 0. 0. 0. 1. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 1. 0. 0. 0.]
[0.         0.         0.58893012 0.41106988 0.         0.
 0.         0.         0.        ]
[0. 0. 0. 0. 1. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 1. 0. 0. 0.]
[0.         0.30170822 0.         0.         0.         0.
 0.         0.         0.69829178]
[0.         0.         0.         0.17828753 0.         0.
 0.         0.         0.82171247]
[0. 0. 0. 1. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 1. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 1.]
[0. 0. 0. 1. 0. 0. 0. 0. 0.]
[0. 1. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 1. 0.]
[0. 0. 0. 1. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 1. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 1.]
[0. 0. 1. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 1. 0.]
[0. 0. 0. 0. 0. 0. 1. 0. 0.]

For visualization of the situation

[7]:
SceneWidget(lighting.to_plantGL(printtriangles=True, printvoxels=True),
            position=(-2.5, -2.5, 0.0),
            size_display=(600, 400),
            plane=True,
            size_world = 10,
            axes_helper=True)
[7]:

4.4.2. Triangles tesselation in a grid

You can reduce the error while transferring a triangle mesh to a voxel mesh by subdividing triangles across multiple voxels.

bc649c749fa84ca58902fcf5b5dbbead

You only need to precise how many times you want to subdivide the triangles.

[21]:
ratp_parameters = { "voxel size" : [1., 1., 1.], "tesselation level" : 7 }

# initialize the instance
lighting = LightVegeManager(lightmodel="ratp", lightmodel_parameters=ratp_parameters)

# build the scene
lighting.build(geometry=triangles)

SceneWidget(lighting.to_plantGL(printtriangles=True, printvoxels=True),
            position=(-2.5, -2.5, 0.0),
            size_display=(600, 400),
            plane=True,
            size_world = 10,
            axes_helper=True)
[21]:

4.4.3. Other parameters

By default, the number of voxels is dynamically computed following the voxel size and mesh limits, but you can force its number.

Voxel size can also be dynamically computed and is based on 3 times the longest triangle.