Getting started

Singlet Lens

Build and trace a simple single-element lens in Optiland — learning the core syntax for surface definition, aperture, fields, and wavelengths.

BeginnerBasicsNumPy backend5 min read

Introduction

This tutorial walks through the construction of a simple singlet lens in Optiland. Instead of starting from a long finished script, it first shows the complete system once and then rebuilds it piece by piece so you can see how surfaces, aperture, fields, and wavelengths fit together.

By the end, you will know the minimal workflow for defining and visualizing a sequential optical system from scratch.

Core concepts used

optic.Optic()
The root container for an optical system. Holds all surfaces, aperture, fields, and wavelengths.
add_surface(...)
Adds an optical interface to the system. You specify the surface index, material, and additional parameters like radius and thickness (distance to the next surface) as keyword arguments.
set_aperture(...)
Sets the limiting aperture type and size — here, entrance pupil diameter (EPD).
add_field(...)
Adds a field point. For an on-axis system, a single field at y=0 is sufficient.
add_wavelength(...)
Defines the wavelengths used during ray tracing, in microns.
draw()
Renders a 2D cross-section of the optical system with traced ray paths.

Step-by-step build

1

Preview the complete singlet script

This shows the full code to generate a singlet lens. In the next section, we will go into each part of this code in more detail.

python
import numpy as np

from optiland import optic

singlet = optic.Optic()

# define surfaces
singlet.add_surface(index=0, radius=np.inf, thickness=np.inf)
singlet.add_surface(index=1, radius=20, thickness=7, is_stop=True, material="N-SF11")
singlet.add_surface(index=2, radius=np.inf, thickness=18)
singlet.add_surface(index=3)

# define aperture
singlet.set_aperture(aperture_type="EPD", value=25)

# define fields
singlet.set_field_type(field_type="angle")
singlet.add_field(y=0)

# define wavelengths
singlet.add_wavelength(value=0.5, is_primary=True)

# view in 3D - Note this opens a new window, but we add a photo below to
# show the visualization
singlet.draw3D()
2

Preview the initial 2D layout

python
# view in 2D
singlet.draw(num_rays=10)
2D view of the singlet lens
3

Start the detailed lens definition

Note:
The draw() method returns the matplotlib figure and axes.
When running Optiland in a script, you must explicitly call plt.show() or fig.show() to display the plot.

In Jupyter notebooks, figures are displayed automatically.

Create the optic:

In Optiland, lenses are instances of the "Optic" class. The process for defining a lens starts with creating an empty "Optic" object as follows:

python
lens = optic.Optic()
4

Add the object surface

We now want to populate the lens object with surfaces. Let's first add the object surface, which will be at infinity and will have a radius of infinity, i.e. it is a plane. We add the surface by calling the "add_surface" method. We must specify the index as 0 to indicate this is the first surface.

python
lens.add_surface(index=0, radius=np.inf, thickness=np.inf)
5

Add the singlet element

Let's now add a singlet lens. The lens will be defined as follows:

  • Material: N-SF11
  • Thickness: 7 mm
  • Radius side 1: 20 mm
  • Radius side 2: infinity
  • Stop surface: 1

Optiland defines lenses one surface at a time, so we define each side of the lens separately. The material always corresponds to the material after interaction with a surface (refraction or reflection). Likewise, the thickness corresponds to the thickness after the surface and it is positive for surfaces to the right. This also implies that we must define the distance from the second surface to the next surface, which we'll define as 18 mm.

Note that we specify the indices of the two surfaces as 1 and 2, after we already specified the object plane with index 0. Also note that Optiland uses millimeters by default.

python
lens.add_surface(index=1, thickness=7, radius=20, is_stop=True, material="N-SF11")
lens.add_surface(index=2, radius=np.inf, thickness=18)
6

Add the image plane

Lastly, let's add the image plane. By default, the radius is infinity, so we can exclude it. We also can omit thickness, as there are no surfaces beyond the image. We need only to define the index, which is 3.

python
lens.add_surface(index=3)
7

Set the system aperture

Now, we can define the aperture of the system. Let's choose entrance pupil diameter (EPD) as the aperture type with a value of 25 mm.

The options for aperture type are:

  • 'EPD' - entrance pupil diameter

  • 'imageFNO' - image-space F-number

  • 'objectNA' - object-space numerical aperture

  • 'float_by_stop_size' - the aperture size floats with the size of the stop diameter, which is defined by the value argument.

    python
    lens.set_aperture(aperture_type="EPD", value=25)
8

Add the on-axis field

Let's add the fields of the lens. We'll keep it simple and add a single field of type "angle" with a value of 0.

The options for field types are:

  • 'angle' - the angle of the field in object space

  • 'object_height' - the height of the object

  • 'paraxial_image_height' - the image height of a paraxial chief ray

  • 'real_image_height' - the image height of a real chief ray

    python
    lens.set_field_type(field_type="angle")
    lens.add_field(y=0)
9

Define the design wavelength

Lastly, let's define the wavelengths of the system. We define a single wavelength at 0.5 µm.

python
lens.add_wavelength(value=0.5, is_primary=True)
10

Draw the finished lens

Let's view the lens in 2D. We can do this by calling the draw method.

Note that you can also pass a projection argument, allowing you to plot in the "YZ" plane (default), "XZ" plane, or "XY" plane.

The 2D visualization is now interactive! You can hover over surfaces, lenses, and ray bundles to get more information. You can also customize the look and feel of the plots using themes. See the gallery for an example of how to use themes.

python
lens.draw(num_rays=10)
View the lens
11

Render the 3D interactive visualization

Finally, we view the lens in 3D using the "draw3D" function. Note that this opens a new window.

python
lens.draw3D()
Show full code listing
python
import numpy as np

from optiland import optic

singlet = optic.Optic()

# define surfaces
singlet.add_surface(index=0, radius=np.inf, thickness=np.inf)
singlet.add_surface(index=1, radius=20, thickness=7, is_stop=True, material="N-SF11")
singlet.add_surface(index=2, radius=np.inf, thickness=18)
singlet.add_surface(index=3)

# define aperture
singlet.set_aperture(aperture_type="EPD", value=25)

# define fields
singlet.set_field_type(field_type="angle")
singlet.add_field(y=0)

# define wavelengths
singlet.add_wavelength(value=0.5, is_primary=True)

# view in 3D - Note this opens a new window, but we add a photo below to
# show the visualization
singlet.draw3D()

# view in 2D
singlet.draw(num_rays=10)

lens = optic.Optic()

lens.add_surface(index=0, radius=np.inf, thickness=np.inf)

lens.add_surface(index=1, thickness=7, radius=20, is_stop=True, material="N-SF11")
lens.add_surface(index=2, radius=np.inf, thickness=18)

lens.add_surface(index=3)

lens.set_aperture(aperture_type="EPD", value=25)

lens.set_field_type(field_type="angle")
lens.add_field(y=0)

lens.add_wavelength(value=0.5, is_primary=True)

lens.draw(num_rays=10)

lens.draw3D()

Conclusions

You have built your first optical system in Optiland. Along the way you learned:

  • How to create an Optic container and populate it surface by surface using add_surface().
  • How to control the aperture (set_aperture()), field points (add_field()), and wavelengths (add_wavelength()).
  • How material assignment and thickness conventions work — material is defined after a surface, and thickness is the distance to the next surface.
  • How to visualize the system in 2D with draw() and in 3D with draw3D().

This minimal workflow is the foundation for every optical design in Optiland, from a simple singlet to a complex multi-element system.

Next tutorials