4.3. Elements (Lens, Ray Source, …)

4.3.1. Overview

4.3.1.1. Types

In optrace the class Element denotes an object which has 0-2 surfaces and belongs to the tracing geometry.

Tracing Elements

Tracing elements are Elements with direct ray interaction:

RaySource

An element with a light emitting surface

Lens

An element with two surfaces on which light is refracted.

IdealLens

A Lens, except that it has a planar surface and refracts light without aberrations

Filter

Element with a surface on which wavelength-dependent filtering takes place.

Aperture

Similar to a Filter, except that incident light is completely absorbed.

Rendering Elements

Elements with no ray interaction for tracing, but the ability to render images of intersecting rays.

Detector

Element with a single surface on which images or spectra can be rendered

Markers

Markers are Elements for annotations in 3D space.

PointMarker

Element consisting of a point and a label

LineMarker

Element consisting of a line and a label

Volumes

Objects for plotting volumes in the TraceGUI, for instance an enclosing cylinder or a medium outline.

BoxVolume

Volume of a box or cube

CylinderVolume

Cylinder volume with the symmetry axis in direction of the optical axis

SphereVolume

A spherical volume

4.3.1.2. Shared Functions/Properties

All subclasses of Element share the following methods and properties.

Position

The position of the element is accessed using:

pos = El.pos

This returns a three element list with x, y, z coordinates. For an element consisting of a singular surface, line or point, the returned value is identical to the position of this surface/line/point. For an element consisting of two surfaces, the position depends on how the object was specified, for instance see Lens.

Extent

The extent box is the smallest encompassing bounding box that include the element. The extent property returns a list of coordinate bounds [x0, x1, y0, y1, z0, z1]. It is accessed using:

extent = El.extent

Moving

An object is moved using the move_to function. A single parameter describes the new absolute position as list [x, y, z].

El.move_to([0.2, -1.25, 3])

The element is moved in such a way that the provided position is its new El.pos.

Rotation

Using the rotate function the objects can be rotated around the z-axis and around their center. The function takes a rotation angle in degrees as parameter:

El.rotate(15)

Flipping

Flipping the element rotates it around an axis parallel to the x-axis passing through the position El.pos. Doing so, the order of front and back surface is reversed and each surface also gets flipped.

El.flip()

4.3.2. RaySource

4.3.2.1. Overview

A RaySource defines the properties for rays it creates, including

  • Emitting surface/point/line

  • Light distribution on this area

  • Emitted spectrum and power

  • Ray polarization

  • Ray orientation

  • Ray divergence

  • Source position

4.3.2.2. Surface/Point/Line Parameter

A RaySource supports the following base shapes Point, Line, CircularSurface, RectangularSurface, RingSurface, which are provided as first parameter to the RaySource() constructor.

circ = ot.CircularSurface(r=3)
RS = ot.RaySource(circ)

4.3.2.3. Position Parameter

The position in three-dimensional space is provided by the pos-parameter.

RS = ot.RaySource(circ, pos=[0, 1.2, -3.5])

4.3.2.4. Power Parameter

Providing the power the cumulative power of all rays is defined. This proves especially useful when working with multiple sources with different power ratios.

RS = ot.RaySource(circ, power=0.5)

4.3.2.5. Orientation Parameter

The base orientation type of the rays is defined by the orientation-parameter.

For orientation="Constant" the orientation is independent of the position on the emitting area. The orientation vector is then defined by the s-parameter in cartesian coordinates.

RS = ot.RaySource(circ, orientation="Constant", s=[0.7, 0, 0.7])

Or with s_sph for spherical coordinates, where the first one is the angle between the orientation and the optical axis and the second the angle inside the lateral plane. Values are provided in degrees.

RS = ot.RaySource(circ, orientation="Constant", s_sph=[20, -30])

If all rays from the source should be converging to a position conv_pos, mode orientation="Converging" should be employed:

RS = ot.RaySource(circ, orientation="Converging", conv_pos=[10, 2, -1])

It is also possible to define orientations as a function of the position of the rays. For this we need to set orientation="Function" and provide the or_func parameter. This parameter takes two numpy arrays containing the x and y-position and returns a two dimensional array with cartesian vector components in rows.

def or_func(x, y, g=5):
    s = np.column_stack((-x, -y, np.ones_like(x)*g))
    ab = (s[:, 0]**2 + s[:, 1]**2 + s[:, 2]**2) ** 0.5
    return s / ab[:, np.newaxis]

RS = ot.RaySource(circ, orientation="Function", or_func=or_func)

As with other functions, we can also provide a keyword argument dictionary for the function:

...
RS = ot.RaySource(circ, orientation="Function", or_func=or_func, or_args=dict(g=10))

4.3.2.6. Spectrum Parameter

A LightSpectrum object is provided with the spectrum parameter. For instance, this can be a predefined spectrum:

RS = ot.RaySource(circ, spectrum=ot.presets.light_spectrum.d75)

Or a user defined one:

spec = ot.LightSpectrum("Monochromatic", wl=529)
RS = ot.RaySource(circ, spectrum=spec)

4.3.2.7. Divergence Parameter

Divergence defines how rays are distributed relative to their base orientation (orientation parameter).

With divergence="None" all rays follow their orientation:

RS = ot.RaySource(circ, divergence="None", s=[0.7, 0, 0.7])

Paired with orientation="Constant" all rays are emitted in parallel.

We can also define Lambertian divergence, which follows the cosine law. div_angle defines the half opening angle of the cone volume in which the divergence is generated.

RS = ot.RaySource(circ, divergence="Lambertian", div_angle=10)

divergence="Isotropic" defines divergence with equal probability in all directions, but again only inside the cone defined by div_angle.

RS = ot.RaySource(circ, divergence="Isotropic", div_angle=10)

User functions can be defined by divergence="Function" and providing the div_func parameter. This function must take angular values in radians up to div_angle and return a normalized or unnormalized probability.

RS = ot.RaySource(circ, divergence="Function", div_func=lambda e: np.cos(e)**2, div_angle=10)

For all the combinations above we can also generate a direction distribution inside an circular arc instead of a cone. The correct way to do this is by setting div_2d=True. With div_axis_angle we can additionally define the orientation of this arc distribution.

RS = ot.RaySource(circ, divergence="Function", div_func=lambda e: np.cos(e)**2, div_2d=True, div_axis_angle=20, div_angle=10)

4.3.2.8. Image Parameter

Alternatively to a uniformly emitting area, there is a way to provide light distributions (modelled by images).

This emitting surface needs to be a ScalarImage or RGBImage object. A RectangularSurface emitting this image will be created automatically. Its size will be equal to the side lengths of the image.

image = ot.presets.image.landscape([2, 3])
RS = ot.RaySource(image)
image = ot.RGBImage(np.random.sample((300, 300, 3)), [2, 3])
RS = ot.RaySource(image)
image = ot.RGBImage("some_image_path", [2, 3])
RS = ot.RaySource(image)

Every image color generates a specific physical spectrum matching its color. This spectrum is a linear combination of the sRGB primaries in Section 5.8.5.

With image specified the spectrum parameter won’t be used.

4.3.2.9. Polarization Parameter

The polarization parameter describes the distribution of linear light polarizations.

In the default case the directions are random, parametrized by polarization="Uniform".

RS = ot.RaySource(circ, polarization="Uniform")

polarization="x" defines polarizations parallel to the x-axis.

RS = ot.RaySource(circ, polarization="x")

polarization="y" defines polarizations parallel to the y-axis.

RS = ot.RaySource(circ, polarization="y")

polarization="xy" defines random polarizations of x or y-direction.

RS = ot.RaySource(circ, polarization="xy")

The user can also set a user-defined value with polarization="Constant" and the pol_angle parameter. The polarization direction is defined by an angle inside the plane perpendicular to the ray direction.

RS = ot.RaySource(circ, polarization="Constant", pol_angle=12)

Or alternatively a list with polarization="List", the angular values in pol_angles and their probabilities in pol_probs.

RS = ot.RaySource(circ, polarization="List", pol_angles=[0, 45, 90], pol_probs=[0.5, 0.25, 0.25])

Lastly, a user defined function is set with polarization="Function" and the pol_func parameter. This parameter takes angles in range \([0, ~2 \pi]\) and returns a normalized or unnormalized probability.

Above we talked how for instance for polarization="x" the rays are parallel to the x-axis. However, depending on their actual ray orientation this isn’t always the case. Read about what the angles mean for rays not parallel to the optical axis in Section 5.1.8.

RS = ot.RaySource(circ, polarization="Function", pol_func=lambda ang: np.exp(-(ang - 30)**2/10))

4.3.3. Lens

4.3.3.1. Overview

A Lens consists of two surfaces and a medium with a RefractionIndex inbetween. Additionally, the position and a thickness parameter is required.

4.3.3.2. Example

sph1 = ot.SphericalSurface(r=3, R=10.2)
sph2 = ot.SphericalSurface(r=3, R=-20)
n = ot.RefractionIndex("Sellmeier2", coeff=[1.045, 0.266, 0.206, 0, 0])

L = ot.Lens(sph1, sph2, n=n, pos=[0, 2, 10], de=0.5)

To define a different ambient medium behind the lens (other than the ambient medium defined by the raytracer geometry), we can provide the n2 parameter.

n2 = ot.RefractionIndex("Constant", n=1.2)
L = ot.Lens(sph1, sph2, n=n, pos=[0, 2, 10], de=0.5, n2=n2)

4.3.3.3. Lens Thickness

To allow for simple definitions of lens thickness and positions, there are multiple ways to define the thickness:

  • d: thickness at the optical axis

  • de: thickness extension. Distance between largest z-position on front and lowest z-position on back

  • d1: distance between front surface center z-position and z-position of pos of Lens

  • d2: distance between z-position of pos of Lens and z-position of the back surface center

../_images/lens_thickness.svg

Fig. 4.15 \(d\) and \(d_\text{e}\) for a convex lens, a concave lens and a meniscus lens

While for a convex lens using the de is most comfortable, for concave or meniscus lenses the thickness at the optical axis d proves to be more useful. For instance, a concave lens can be defined with:

L = ot.Lens(sph2, sph1, n=n, pos=[0, 2, 10], d=0.5)

When the lens is defined by d or de the position pos[2] is at the center of the d or de distance.

With the d1 and d2 parameters we can control the position of both surfaces relative to the lens position manually. For instance, with d1=0, d2=... the lens front starts exactly at the pos of the Lens. On the other hand setting d1=..., d2=0 leads to the back surface center ending at pos.

../_images/lens_thickness_position.svg

Fig. 4.16 Defining a convex lens by de=..., by d1=0, d2=... and by d1=..., d2=0.

All cases in-between are also viable, for instance:

L = ot.Lens(sph1, sph2, n=n, pos=[0, 2, 10], d1=0.1, d2=0.6)

But only as long as the surfaces don’t collide. With a Lens object you can also access the thickness parameters:

>>> L.d
0.7
>>> L.de
0.022566018...
>>> L.d1
0.1
>>> L.d2
0.6

Or the parameters of its surfaces:

>>> L.front.ds
0.45115391...

4.3.3.4. Paraxial Properties

Paraxial analysis by using transfer matrix is described in Section 4.10. As for a setup of many lenses, we can also do paraxial analysis on a simple lens. To create a ray transfer matrix analysis object (TMA object) we call the member function tma().

>>> tma = L.tma()
>>> tma.efl
12.749973...

As the behavior can differ with the light wavelength, we can also provide a non-default wavelength in nanometers. As the lens object itself has no knowledge of the geometry surrounding it, the prior medium it is undefined. By default, a constant refractive index of 1 is assumed, but can be overwritten with the parameter n0.

>>> tma = L.tma(589.2, n0=ot.RefractionIndex("Constant", n=1.1))
>>> tma.efl
17.300045...

4.3.4. Ideal Lens

An IdealLens focusses and images light perfectly and without aberrations according to the imaging equation. The geometry is an infinitesimal thin circular area with radius r. Additionally, the optical power D and a position pos need to be provided.

IL = ot.IdealLens(r=5, D=12.5, pos=[0, 0, 9.5])

As for a normal Lens, it is possible to define a n2. Note that this does not change the optical power or focal length, as they are controlled by the D parameter.

n2 = ot.RefractionIndex("Constant", n=1.25)
IL = ot.IdealLens(r=4, D=-8.2, pos=[0, 0, 9.5], n2=n2)

4.3.5. Filter

When light hits a Filter, the ray power is transmitted according to the filter’s transmittance function.

A Filter is defined by a Surface, a position and the TransmissionSpectrum.

spec = ot.TransmissionSpectrum("Rectangle", wl0=400, wl1=500, val=0.5)
circ = ot.CircularSurface(r=5)
F = ot.Filter(circ, pos=[0, 0, 23.93], spectrum=spec)

With a filter the approximate sRGB color is calculated with F.color(). The fourth return value is the opacity for visualization. Note that the opacity is more of a visual extra than a physically correct simulation.

Calling the filter with a wavelength array returns the transmittance at these wavelengths.

>>> wl = np.array([380, 400, 550])
>>> F(wl)
array([0. , 0.5, 0. ])

When tracing, the raytracer sets all transmission values below a specific threshold T_TH to zero. This is done to avoid ghost rays, that are rays that merely contribute to the light distribution or image but are nonetheless calculated and reduce performance. An example are rays far outside of a normal distribution.

By default the relative threshold value is

>>> ot.Raytracer.T_TH
1e-05

4.3.6. Aperture

An Aperture is just a Filter that absorbs light completely for rays that hit it. In the most common use cases a RingSurface is employed as Aperture surface. As for all other elements, we also need to specify the position pos.

ring = ot.RingSurface(ri=0.05, r=5)
AP = ot.Aperture(ring, pos=[0, 2, 10.1])

4.3.7. Detector

A Detector enables the rendering of images and spectra on its geometry. But by itself, it has no effect on raytracing.

It takes a surface parameter and the position parameter as arguments.

rect = ot.RectangularSurface(dim=[1.5, 2.3])
Det = ot.Detector(rect, pos=[0, 0, 15.2])

4.3.8. Markers

4.3.8.1. PointMarker

A PointMarker annotates positions or elements inside the tracing geometry. As all markers, is has no influence on the tracing process.

In the simplest case a PointMarker is defined with a text string and a position for the Point.

M = ot.PointMarker("Text132", pos=[0.5, 9.1, 0.5])

One can scale the text and marker with text_factor or marker_factor. The actual size change is handled by the plotting GUI.

M = ot.PointMarker("Text132", pos=[0.5, 9.1, 0.5], text_factor=2.3, marker_factor=0.5)

We can also hide the marker point and only display the text with the parameter label_only=True.

M = ot.PointMarker("Text132", pos=[0.5, 9.1, 0.5], label_only=True)

Conversly, text can be hidden by leaving the text empty:

M = ot.PointMarker("", pos=[0.5, 9.1, 0.5])

4.3.8.2. LineMarker

Similarly, a LineMarker is a Line in the xy-plane with a text annotation.

In the simplest case a LineMarker is defined by a text string, radius, angle and a position.

M = ot.LineMarker(r=3, desc="Text132", angle=45, pos=[0.5, 9.1, 0.5])

One can scale the text and marker with text_factor or line_factor. The actual size change is handled by the plotting GUI.

M = ot.LineMarker(r=3, desc="Text132", pos=[0.5, 9.1, 0.5], text_factor=2.3, line_factor=0.5)

We can hide the text and only plot the marker line by leaving the text empty:

M = ot.LineMarker(r=3, desc="", pos=[0.5, 9.1, 0.5])

4.3.9. Volumes

4.3.9.1. BoxVolume

As for a RectangularSurface, the parameter dim defines the x- and y-side lengths in the lateral plane. Parameter pos describes the center of this rectangle. For a BoxVolume this surface gets extended by length length in positive z-direction, forming a three-dimensional volume.

ot.BoxVolume(dim=[10, 20], length=15, pos=[0, 2, 3])

Additionally, a plotting opacity and color can be specified:

ot.BoxVolume(dim=[10, 20], length=15, pos=[0, 2, 3], opacity=0.8, color=(0, 1, 0))

4.3.9.2. SphereVolume

A SphereVolume is defined by its center position pos and the sphere radius R:

ot.SphereVolume(R=10, pos=[0, 2, 3])

As for the other volumes, the plotting opacity and color can be specified:

ot.SphereVolume(R=10, pos=[0, 2, 3], opacity=0.8, color=(0, 0, 1))

4.3.9.3. CylinderVolume

A CylinderVolume is defined by its front surface center position pos and the cylinder radius r:

ot.CylinderVolume(r=5, length=15, pos=[0, 2, 3])

As for the other volumes, the plotting opacity and color can be specified:

ot.CylinderVolume(r=5, length=15, pos=[0, 2, 3], opacity=0.8, color=(0.5, 0.1, 0.0))

4.3.9.4. Custom Volumes

A custom Volume makes it possible to define user-defined volumes. It requires a front and back surface as parameter, as well as a position and the thickness distances d1, d2. These have the same meaning as for a Lens in Section 4.3.3.3.

You can find an example below:

front = ot.ConicSurface(r=4, k=2, R=50)
back = ot.RectangularSurface(dim=[3, 3])
vol = ot.Volume(front, back, pos=[0, 1, 2], d1=front.ds, d2=back.ds+1)

Here a conic front surface and a rectangular surface are defined. front.ds, back.ds denote the total thickness of both surfaces at their center. The overall length for this volumes is then front.ds + back.ds + 1.