Using the BRDF module

In this notebook, we demonstrate how to use the pySCATMECH BRDF module. This module defines the class BRDF_Model, which provides an interface to SCATMECH’s BRDF_Model, which in turn, is a base class for representing all models that predict the bidirectional reflectance distribution function (BRDF).

First, we import the necessary libraries. We will be using matplotlib.pyplot for graphing.

In [1]:
from pySCATMECH.brdf import *
import matplotlib.pyplot as plt

Let’s create a Correlated_Roughness_Stack_BRDF_Model and print its default parameters.

In [2]:
model = BRDF_Model('Correlated_Roughness_Stack_BRDF_Model')
print(model)
{None : 'Correlated_Roughness_Stack_BRDF_Model',
'lambda' : '0.532',
'substrate' : '(4.05,0.05)',
'type' : '0',
'psd' : {None : 'ABC_PSD_Function',
          'A' : '0.01',
          'B' : '362',
          'C' : '2.5'},
'stack' : 'No_StackModel'}

We can create the model with parameters. Notice in the following how we can set parameters that are themselves models.

In [3]:
psd = {None : "ABC_PSD_Function",
       "A" : 0.01,
       "B" : 360,
       "C" : 2.4}

stack = {None : 'SingleFilm_StackModel',
         'material' : 1.59,
         'thickness': 0.05
        }

parameters = {'lambda' : 0.532,
              'substrate' : 4.05+0.05j,
              'type' : 0,
              'stack' : stack,
              'psd' : psd}

model = BRDF_Model('Correlated_Roughness_Stack_BRDF_Model',parameters)
print(model)
{None : 'Correlated_Roughness_Stack_BRDF_Model',
'lambda' : '0.532',
'substrate' : '(4.05,0.05)',
'type' : '0',
'psd' : {None : 'ABC_PSD_Function',
          'A' : '0.01',
          'B' : '360',
          'C' : '2.4'},
'stack' : {None : 'SingleFilm_StackModel',
            'material' : '(1.59,0)',
            'thickness' : '0.05'}}

We can set parameters later using the setParameters function.

In [4]:
parameters['lambda'] = 0.600
psd['C'] = 2.3
stack['thickness'] = 0.1
model.setParameters(parameters)
print(model)
{None : 'Correlated_Roughness_Stack_BRDF_Model',
'lambda' : '0.6',
'substrate' : '(4.05,0.05)',
'type' : '0',
'psd' : {None : 'ABC_PSD_Function',
          'A' : '0.01',
          'B' : '360',
          'C' : '2.3'},
'stack' : {None : 'SingleFilm_StackModel',
            'material' : '(1.59,0)',
            'thickness' : '0.1'}}

One can use keyword arguments to set parameters, and if the parameters are models, one can set them to models. Note that because lambda is a Python keyword, we use wavelength in place of the parameter lambda.

In [5]:
# Needed to use OpticalFunction, Film, and FilmStack...
from pySCATMECH.fresnel import *

# Define two materials...
SiO2 = OpticalFunction(lambda L: 1.4580 + 0.00354/L**2,np.arange(0.2,1.5,0.1))
Si = OpticalFunction('silicon')

# Define the power spectral density function...
psd = Model("ABC_PSD_Function", A=0.01, B=360, C=2.4)

# Define the stack...
stack =  FilmStack([Film(SiO2,thickness =0.05)])

model.setParameters(wavelength = 0.600, substrate=Si, stack = stack, psd = psd)
print(model)
{None : 'Correlated_Roughness_Stack_BRDF_Model',
'lambda' : '0.6',
'substrate' : 'silicon',
'type' : '0',
'psd' : {None : 'ABC_PSD_Function',
          'A' : '0.01',
          'B' : '360',
          'C' : '2.4'},
'stack' : {None : 'Stack_StackModel',
            'stack' : 'zm95qrx7RMFx.tmp 0.05'}}
In [6]:
model.getParameters()
Out[6]:
{None: 'Correlated_Roughness_Stack_BRDF_Model',
 'lambda': '0.6',
 'substrate': 'silicon',
 'type': '0',
 'psd': 'ABC_PSD_Function',
 'psd.A': '0.01',
 'psd.B': '360',
 'psd.C': '2.4',
 'stack': 'Stack_StackModel',
 'stack.stack': 'zm95qrx7RMFx.tmp 0.05'}

Let’s evaluate the BRDF! The BRDF, in general, is a Mueller matrix. (Note here that deg is defined in pySCATMECH to be \(\pi/180\).) Let’s evaluate it for incident and scattering angles, \(\theta_i=60^\circ\) and \(\theta_s=30^\circ\), respectively:

In [7]:
print(model.MuellerBRDF(60*deg, 30*deg))
[[ 4.21363211e-06  6.14518402e-07  0.00000000e+00  0.00000000e+00]
 [ 6.14518402e-07  4.21363211e-06 -0.00000000e+00  0.00000000e+00]
 [ 0.00000000e+00 -0.00000000e+00 -3.83310078e-06  1.63841420e-06]
 [ 0.00000000e+00  0.00000000e+00 -1.63841420e-06 -3.83310078e-06]]

We can determine it for an input Stokes vector of p-polarization and with polarization insensitivity:

In [8]:
mBRDF = model.MuellerBRDF(60*deg, 30*deg)
inc = Polarization('p')
sens = Sensitivity('u')
sens @ mBRDF @ inc
Out[8]:
3.599113704481351e-06

Another way of doing this is:

In [9]:
model.BRDF(60*deg, 30*deg, inc=Polarization('p'), sens=Sensitivity('u'))
Out[9]:
3.599113704481351e-06

The unpolarized BRDF is:

In [10]:
model.BRDF(60*deg, 30*deg)
Out[10]:
4.2136321066656146e-06

Let’s plot the BRDF:

In [11]:
thetaslist = np.arange(-89, 90, 1)
BRDFlist = [model.BRDF(60*deg, thetas*deg, inc=[1,-1,0,0], sens=[1,0,0,0]) for thetas in thetaslist]

plt.figure()
plt.yscale('log')
plt.plot(thetaslist,BRDFlist)
plt.ylabel('BRDF (sr$^{-1}$)')
plt.xlabel(r'$\theta_\mathrm{r}$ (degrees)')
plt.xticks(np.linspace(-90,90,7))
plt.show()
Graph showing results of preceding Python code: BRDF as a function of angle with a sharp peak at 60 degrees

We can vary two angles:

In [12]:
thetaslist = np.arange(-89,90,1)
plt.figure()
plt.yscale('log')
for thetai in [0,30,60]:
    BRDFlist = [model.BRDF(thetai*deg,thetas*deg,inc=[1,-1,0,0],sens=[1,0,0,0]) for thetas in thetaslist]
    plt.plot(thetaslist,BRDFlist,label = r"$\theta_i=%d^\circ$" % thetai)

plt.xticks(np.linspace(-90,90,7))
plt.ylabel('BRDF (sr$^{-1}$)')
plt.xlabel(r'$\theta_\mathrm{r}$ (degrees)')
plt.legend()
plt.show()
Graph showing results of preceding Python code: Three BRDFs as a function of angle with sharp peaks at 0, 30, and 60 degrees

We can even vary parameters. Also, note that sometimes it is useful to plot BRDF on a logarithmic scale.

In [13]:
thetaslist = np.linspace(-89,89,100)

plt.figure()
for t in [0.1,0.2,0.3,0.4,0.5]:
    stack = FilmStack([Film(SiO2,thickness = t)])
    model.setParameters(stack=stack)
    BRDFlist = [model.BRDF(60*deg,thetas*deg,inc=[1,-1,0,0],sens=[1,0,0,0]) for thetas in thetaslist]
    plt.plot(thetaslist,BRDFlist,label = r"$D = %g$" % t)

plt.yscale('log')
plt.xticks(np.linspace(-90,90,7))
plt.xlabel(r'$\theta_\mathrm{r}$ (degrees)')
plt.ylabel('BRDF (sr$^{-1}$)')
plt.legend()
plt.show()
Graph showing results of preceding Python code: Five BRDFs as a function of angle with sharp peaks at 60 degrees