SEM image tutorial

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

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


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.

Sketch of the setup

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

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:

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 output.det

You should see the following SEM image:

Resulting SEM image