# SEM image tutorial

Before you begin this tutorial, make sure you have followed these steps:

- Install cstool
- Run cstool for two materials: silicon and pmma
- Install nebula

We also recommend to follow the REELS tutorial first, because this one will be more complicated.

## Introduction

In this second tutorial, we will simulate a Scanning Electron Microscope (SEM) image. To keep things simple, the geometry we will be looking at is a single PMMA line on top of a silicon substrate. This is a common situation in lithography: the PMMA represents a developed line of photoresist on top of a silicon substrate. We are interested in a secondary electron (SE) image of this geometry.

## Input geometry

A triangle file can be downloaded here. Open it in a text editor and take a look: it is more complicated than the geometry for the REELS tutorial.

The first six triangles represent the substrate. All are at z = 0. The first pair of triangles is an interface between material 0 (substrate) and -123 (vacuum) for x < 20 nm. The second pair of triangles is between materials 0 (substrate) and 1 (resist); the x coordinates run from -20 nm to +20 nm. The last pair of triangles is again a substrate-vacuum interface for x > 20 nm.

The three following pairs of triangles represent the three sides of the line: first at x = -20 nm, then at z = 30 nm, and finally at x = + 20 nm. All of these have material 1 (resist) on one side and -123 (vacuum) on the other.

All this is wrapped in a similar box as in the REELS tutorial: a detector above (at z = 40 nm this time, because it must be above the line), mirrors on the sides, and a terminator deep down in the substrate.

This time, though, the detector has material index -125 instead of -126. Material -126 detects all electrons, but material -125 detects only the low-energy (< 50 eV) electrons. These "secondary electrons" (SEs) contain information about the shape of the sample. High-energy > 50 eV "backscattered electrons" (BSEs) do not contain much shape information, but are more sensitive to material composition instead. Right now, we are interested in SEs. If a BSE hits a detector of material -125, it passes through. In our geometry, that means it leaves the simulation box and is taken out of the simulation. An alternative approach to using material -125 is to use material -126, and apply an energy filter in post-processing. That's fine of course, but this approach means that the output files from the simulator will be a bit smaller.

## Input electrons

We will launch 500 eV primary electrons from z = 35 nm, nicely between the top of the line (at z = 30) and the detectors (at z = 40 nm). We are now interested in a SEM image, so the electrons will start on a grid in the xy plane. To mimic a real SEM even more accurately, the number of electrons per pixel will be variable (assuming Poissonian statistics) and the beam spot will be Gaussian, with σ = 1 nm.

A python script to generate such a primary electron file can be downloaded here. The relevant part of the script looks like this:

```
# Parameters
z = 35 # starting z
xpx = np.linspace(-50, 50, 101) # x pixels: between -50nm and +50nm, in steps of 1nm
ypx = np.linspace(-100, 100, 201) # y pixels: between -100nm and +100nm, in steps of 1nm
energy = 500 # Beam energy, in eV
epx = 100 # Number of electrons per pixel
sigma = 1 # Standard deviation of Gaussian beam spot size
poisson = True # Whether to use Poisson shotnoise
# Open file
with open('sem.pri', 'wb') as file:
# Iterate over pixels
for i, xmid in enumerate(xpx):
for j, ymid in enumerate(ypx):
# Number of electrons in this specific pixel
N_elec = np.random.poisson(epx) if poisson else epx
# Allocate numpy buffer
buffer = np.empty(N_elec, dtype=electron_dtype)
# Fill with data
buffer['x'] = np.random.normal(xmid, sigma, N_elec)
buffer['y'] = np.random.normal(ymid, sigma, N_elec)
buffer['z'] = z
buffer['dx'] = 0
buffer['dy'] = 0
buffer['dz'] = -1
buffer['E'] = energy
buffer['px'] = i
buffer['py'] = j
# Write buffer to file
buffer.tofile(file)
```

As before, the first set of lines defines a few parameters.
`xpx`

and `ypx`

are a list of pixel coordinates, in nm: the function
`np.linspace(start, stop, num)`

generates an array of `num`

evenly-spaced
numbers between `start`

and `stop`

.

After opening the output file, there is a nested for-loop over the pixels in the
image. `enumerate()`

means that you get both the index of the value in an array,
here `i`

and `j`

; as well as the actual value (`xmid`

and `ymid`

). We will need
the indices for the pixel indices, and the actual values for the real beam
positions in nanometers.

We then determine the number of electrons that will land at this specific pixel.
If the `poisson`

parameter was true, this is a random number; otherwise we use
the target value. We then allocate a buffer of exactly the number of electrons
we want to send into this pixel.

The buffer can then be filled. The x and y coordinates of the electrons are
random, normally distributed around xmid and ymid as discussed above. The
function `np.randon.normal(mu, sigma, size)`

generates an array of random
numbers of length `size`

. So, in these two lines, we set `buffer['x']`

and
`buffer['y']`

to an array of values with the same length as the buffer itself.
The other parameters are set to scalars, which means to fill the entire buffer
with the same value.

In the last line, we write the buffer to file and the loop continues with the next pixel.

## Running the simulator

We will now generate the primary electrons and run the simulator:

```
python sem-pri.py
nebula_gpu sem.tri sem.pri silicon.mat pmma.mat > output.det
```

If you do not have the GPU version, replace `nebula_gpu`

by `nebula_cpu_mt`

for
a CPU-only simulation.

## Analyzing the output

To analyze the output, download the analysis script.
To make a SEM image, we need to count the number of detected electrons per pixel.
We do this with numpy's `histogram2d()`

function.

You can run the script as follows:

```
python sem-analysis.py output.det
```

You should see the following SEM image: