The Slab Simulation
As already mentioned in the beginner tutorial, slab simulation is the first separate simulation step, which creates a slab of material that has certain spectral reflectance and transmittance properties. What these properties are – that is for the user to decide. This piece of tutorial will dig into how they can be defined in HyperBlend from the practical point of view. For the scientific foundation and details, the reader is encouraged to take a look at the first two published HyperBlend papers, i.e., [RRPolonen22] and [RLP23].
The slab simulation consists of the actual simulations that are done by using various Solver s, training new solvers, and generating/reading/writing slab spectra. Since we already touched the topic of spectra generation in the beginner tutorial, we dig a bit deeper into that first, and then cover the other topics.
The slab simulation is based on a virtual “measurements” of a slab’s reflectance and transmittance. This is based on a Blender scene at root/Internal/slab_sim_template.blend
A bit more advanced target spectra
Custom PROSPECT leaf spectra
HyperBlend is totally agnostic to the source of the slab spectra. In the beginner tutorial, we created a random reflectance and transmittance spectra using PROSPECT simulator. However, the power of prospect is in that you can define biochemical and (to some extent) biophysical variables of a simulated leaf, so let’s look how that is done through HyperBlend.
from src.setup import initialization
from src.slab_simulation import interface as SMI
if __name__ == "__main__":
runtime = initialization.initialize()
slab_sim_name = "prospect_slab"
SMI.generate_prospect_leaf(
slab_sim_name=slab_sim_name,
signal_id=0,
n=1.5,
ab=32.0,
ar=8.0,
brown=0.0,
w=0.016,
m=0.009,
ant=0.0,
)
SMI.solve_slab_material_parameters(
runtime=runtime,
slab_sim_name=slab_sim_name,
range_start=400,
range_end=1000,
resolution=100,
)
The signal_id is zero by default, but if you create multiple leaf spectra
in a loop (as you probably would in any real application), just pass
the looping index as signal_id
We simply called PROSPECT with desired carotenoid and chlorophyll (etc.)
content and solved again for slab material parameters. See the documentation
of generate_prospect_leaf() for the explanation
of parameter names.
Using real-world leaf spectra
We can also use real-world measurements as a source. The example below shows how to use a custom target as target data and how to save and solve the slab parameters:
from src.setup import initialization
from src.slab_simulation import interface as SMI
from src.data import toml_handling as TH
if __name__ == "__main__":
runtime = initialization.initialize()
slab_sim_name = "try_manual_set"
# Example data list of lists where inner list holds the data
target = [[400, 0.21435, 0.26547], [401, 0.21431, 0.26540]]
# Write data to disk in a format the HyperBlend can understand
TH.write_target(slab_sim_name, target, signal_id=0)
# Solve as before
SMI.solve_slab_material_parameters(runtime=runtime, slab_sim_name=slab_sim_name)
In this toy example we have only two spectral bands defined as target.
If you have your measured data, for example, in a .csv file, you will have to
read it yourself into the list structure, and then it will be compliable with
the rest of HyperBlend code. Internally, HyperBlend saves most of non-image data
as .toml files. (Only files read by Blender scripts are saved as CSV.) That is
the reason we introduce one new import: from src.data import toml_handling as TH.
The data.toml_handling handles all (or at least most) writing and
reading of toml files.
Slab Simulation Solvers
As explained in paper [RLP23], HyperBlend has three slab simulation Solver s that can be used for solving the renderable slab material parameters. In the examples above, we used the default one which is a neural network based solution. It arguably offers the best balance between precision and speed.
For advanced users who want to simulate something else than the available forest scenes, or who want to develop the software further, it may be necessary to retrain the solvers. As the training data should be generated using the original optimization based solver (slowest but most accurate of the ones offered), we cover some of those topics now.
So, the three available slab solvers are:
opt (optimization based), original solver. Accurate but very slow
nn (neural network), which is fast (200 times faster than opt) with good accuracy (decrease of 2-4 times compared to opt)
surf (surface fitting), which is worse than nn in accuracy but around the same speed
The bolded names are how the solvers are called inside the code (as strings).
The modules they are coded in are named accordingly:
slab_simulation.opt, slab_simulation.nn, and slab_simulation.surf.
In general, you don’t need to deal with these modules directly, unless you are a developer.
If we would like to solve the previous example using the opt solver, we would call it like this:
SMI.solve_slab_material_parameters(runtime=runtime, slab_sim_name=slab_sim_name, solver='opt')
For using the surface
fitting method, we would call it with argument solver='surf'. Calling the solver is
still a bit more complicated than this because one may have trained multiple sets of solvers.
The call we use above will select the desired default solver that are available in the repository.
If you train custom solvers (we’ll cover how to, next), they will reside under root/Slab solvers/.
Say, you trained a new set of solvers called 'My own solvers'. Then
you would call the solve_slab_material_parameters()
like so
SMI.solve_slab_material_parameters(
runtime=runtime,
slab_sim_name=slab_sim_name,
range_start=400,
range_end=1000,
resolution=100,
solver="nn",
solver_dirname="My own solvers",
)
Training custom solvers
Training new solvers comes into question if:
you want to improve the existing default ones
you need different slab thickness or do other changes to the slab simulation environment
The first case is more about if you want to do something or not. The default solvers are pretty good, but they can definitely be improved. In the second case, if you introduced some changes to the slab simulation environment (i.e., you are a developer), you must retrain the solvers as they will no longer give correct results (although they may look like doing sensible things).
Note
The opt solver does not rely on training but it does need/benefit from a good starting guess of where to start the optimization process.
Training data
In slab_simulation.interface, there are two functions related to training:
slab_simulation.interface.train_models() and
slab_simulation.interface.iterative_train(). The latter essentially runs the
former with specific settings iteratively for a set number of times.
Let’s take a look how to call the former one who does the training data generation
and the actual training with one call. We’ll take it step by step as the function
takes a lot of attributes.
if __name__ == "__main__":
runtime = initialization.initialize()
SMI.train_models(runtime=runtime, slab_sim_name_for_training='My training data', generate_data=True, dry_run=True)
This will generate a set of new training data with the default options. Except,
because we set dry_run=True, it only prints out how many data points it would
have created without actually creating any. Let’s change the way the training data will be
generated with some arguments
SMI.train_models(
runtime=runtime,
slab_sim_name_for_training="My training data",
generate_data=True,
dry_run=True,
starting_guess_type="curve",
similarity_rt=0.25,,
train_points_per_dim=20
)
As stated in the API documentation for argument starting_guess_type:
String, one of ‘hard-coded’, ‘curve’, ‘surf’ in order of increasing complexity.
Hard-coded is only needed if training the other methods from absolute scratch (for
example if leaf material parameter count or bounds change in future development).
Curve fitting ‘curve’ is the method presented in the first HyperBlend paper
[RRPolonen22]. It will
only work in cases where R and T are relatively close to each other (around +- 0.2).
Surface fitting method ‘surf’ can be used after the first training iteration has been carried
out. It can more robustly adapt to situations where R and T are dissimilar.
The similarity of R (reflectance) and T (Transmittance) argument similarity_rt
are discussed to some extent
in all papers published on HyperBlend and it is a very much a subject hindering the
accuracy and performance of HyperBlend. So, if you are a developer, I hope you can
solve this problem some day. Roughly, this RT similarity requirement means that
if R and T are far away from each other (e.g. R=0.05 and T=0.95 as they live in
interval 0-1), the slab model is incapable of producing good, reliable, or consistent
results.
The argument train_points_per_dim=20 tells to how many parts the R and T dimensions are split.
In other words, with value 20, there will be 20 different values for R and 20 for T.
The final number of training points would then be some fraction of 20x20,
depending on what the similarity requirement value is. If it is 1, the number
of generated points would be 400, i.e., all the points are simulated. Note,
that the points will be later pruned, so that only results whose root mean squared
error does not exceed certain threshold (2% by default) are accepted.
Actual training
So, let’s assume we have some training data now generated and move to the training. The training and data generation are very intertwined, so separating them here is a bit forced, but let’s try anyways.
For training, we will still use the same call but with different argument values.
1SMI.train_models(
2 runtime=runtime,
3 slab_sim_name_for_training="My training data",
4 generate_data=False,
5 dry_run=False,
6 train_surf=True,
7 train_nn=True,
8 layer_count=5,
9 layer_width=1000,
10 epochs=300,
11 batch_size=32,
12 learning_rate=0.01,
13 patience=30,
14 split=0.1,
15 show_plot=False,
16 solver_name_to_save="My own solvers",
17 solver_name_to_use=None,
18)
Now we set generate_data=False, since we already generated the data under that name.
We set train_surf=True and train_nn=True, so they get trained. The rest are mostly
parameters for the training. The layer_count, layer_width, epochs,
batch_size, learning_rate, patience, and split are related to NN training.
Surface fitting training does not have exposed arguments, so if you want to change something,
you will have to dig into its source code slab_simulation.surf.
The last two arguments are very handy in iterative training where you would want the result of the previous iteration to be used in training the next iteration.
When you MUST train
As said earlier, the root/Internal/slab_sim_template.blend is the base of the slab simulation. It contains a box with width of 1 m and thickness of 2 mm. If, for any reason you would need, say, a 10 mm thick slab of material, you would have to change the thickness in the slab_sim_template.blend file and retrain the nn and surf solvers.
Note
If you are a developer, the work was started on making copies of the
slab_sim_template so that several thickness slabs could coexist in
the slab simulation phase, but the time run out to finish that development.
The filename can already be passed to the blender script, but it needs
an implementation to rendering.blender_control.run_slab_wl_render()
and rendering.blender_control.run_parallel_slab_wl_render().
Another way would be to make the slab itself parametric, and just give
the thickness as a parameter.
One last thing. It is very important so:
Warning
There is a magical density scaling constant in
slab_simulation.slab_commons and
blender_scripts.bs_setup_forest. Its value is 3000. The
particle density (as all other parameters) in slab simulation vary from
0 to 1, and this is nowhere near enough density to get the R and T values
we want, this density scaling is used to fix it. Moreover, the value 3000
is for 2 mm leaf slab. If you want a 1 mm thick leaf, you should raise the
constant to 6000. For other than leaf simulation purposes, you may need
to figure out a new good scaling factor.
And once again, if you are a developer, please fix this ugly hard-coded constant that has to be synced over separate files.
That’s all for slab simulation. We should be ready to get to the actual meat of HyperBlend: The System Simulation.