 • Search in book...
• Toggle Font Controls
© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2021
M. Cywiak, D. CywiakMulti-Platform Graphics Programming with Kivyhttps://doi.org/10.1007/978-1-4842-7113-1_15

# 15. Stereoscopic Cylindrical Coordinates Plotting

(1)
Leon, Guanajuato, Mexico
(2)
Queretaro, Mexico

In this chapter, we describe elements for plotting stereoscopic 3D cylindrical functions. For this purpose, we focus on the so-called aberrations of optical lenses, also referred to as Seidel aberrations .

Figure 15-1 shows the program running on an Android cell phone with a stereoscopic plot of one of these terms, referred to as spherical aberration. Figure 15-1Screenshot of the program on an Android cell phone showing a plot of a spherical aberration term

The functionality of the buttons is the same as in previous chapters.

Seidel aberrations consist of algebraic terms, expressed as typical cylindrical coordinate functions, which can be obtained by expanding the transmittance phase function of an ideal lens by a Taylor series. According to the wave theory of light, in the process of focusing an illuminating light source by a lens, the interaction of the lens with the wavefronts of light prevents the lens from focusing on a single point, generating instead a spatial light distribution around the focal point. A precise analytical tool to calculate the spatial distribution of the focused profiles is given by the Fresnel diffraction integral , which allows one to introduce the concept of the phase transmittance function of a lens. In the next section, we provide an introductory analytical description of this subject.

## 15.1 Ideal Lens Focusing: The Fresnel Diffraction Integral

In Figure 15-2, an illuminating beam is located at an initial plane with coordinates (x, y). The spatial amplitude distribution of the illuminating beam at the initial plane is represented by ψ(x, y). The light propagates to the right, from plane (x, y) to an observation plane with coordinates (ξ, η), placed at a distance z. The planes are parallel. The amplitude distribution of the propagated field is given by ψF(ξ, η). Figure 15-2An initial field with amplitude distribution ψ(x, y) propagates a distance z toward an observation plane. The parameter ψF(ξ, η) represents the amplitude distribution at the plane of observation
As mentioned, an accurate tool to calculate the amplitude distribution of the field at the observation plane is given by the Fresnel diffraction integral, expressed as follows: (15.1)

For our program, we will apply the Fresnel diffraction integral to the case of an ideal lens. As is well known, a physical property that characterizes an ideal lens is its capability of focusing a spatially unlimited plane wave. Therefore, at the plane of observation, the focused light distribution should consist of an ideal geometrical point. Analytically, the corresponding equation of this point at the plane of observation is represented as δ(ξ, η). Here the δ symbol represents a Dirac delta function.

In Equation (15.1), we use the following substitution: (15.2)

In Equation (15.2), ψ(x, y) represents the transmittance function of the ideal lens. In the exponential function with an imaginary argument, the quadratic expression is referred to as the phase of the lens. We will now see that this quadratic negative phase results in a convergent wavefront.

Substituting Equation (15.2) into Equation (15.1) gives the following equation: (15.3)

In Equation (15.3), we substituted z with f, the lens focal length.

By expanding the binomial terms in the second exponential in the integral of Equation (15.3) and canceling same terms, we get: (15.4)
In Equation (15.4), we recognize that the integral corresponds to one of the representations found in the mathematical tables for a two-dimensional Dirac delta function; that is: (15.5)
Substituting Equation (15.5) into Equation (15.4) gives the following equation: (15.6)
We now apply the property for λ > 0 and f > 0, which allows us to simplify Equation (15.6) as follows: (15.7)

Equation (15.7) demonstrates that when an ideal lens is illuminated by a plane wave that propagates parallel to the optical axes, the lens concentrates (or focuses) the light precisely at the center of the observation plane. The focusing spot consists of an ideal geometrical point, provided that the plane of observation is placed precisely at a lens focal distance from the initial plane.

## 15.2 Departure from the Ideal Lens

As properties of real lenses deviate from ideal ones, we must devise a more realistic approach. Let’s add a linear term to the ideal lens phase of Equation (15.2), as follows: (15.8)

In Equation (15.8), the parameter a represents a constant term.

Substituting Equation (15.8) into Equation (15.1) and calculating the integral gives the following: (15.9)

Equation (15.9) shows that the focused spot has been shifted from the origin in the x-direction, due to the linear term introduced in Equation (15.8). We conclude that the lens has tilted the wavefront with respect to the optical axes. An analog analysis applies to the y-direction.

This approach can be made more general by replacing the phase of the lens by a transmittance function, as follows: (15.10)
Now, based on the experience gain in our example, we will assume that the phase function in Equation (15.10) depends on the combination of three independent variables: (15.11)
Equation (15.11) can now be expanded in a similar manner to a Taylor series: (15.12)

In Equation (15.12), each coefficient in the series has three sub-indexes. Each sub-index corresponds to the power of its corresponding expansion term. The first sub-index corresponds to the power of the term (x² + y²). The second sub-index corresponds to the power of a². Finally, the third sub-index corresponds to the power of ax. For example, A1, 0, 1 is the coefficient of the term of the products of (x² + y²)¹ by (a²)0 by (ax)¹.

## 15.3 The Wave Aberration Function in Cylindrical Coordinates

Equation (15.12) is one of the possible expansions of an aberrated wavefront. An algebraic sum of these terms can represent a real lens. Due to circular symmetry in the lens-manufacturing process, the expansion may fit better for analytical lens modeling if the expansion is expressed in cylindrical coordinates. Then, we express x and y as follows: (15.13)

In Equation (15.13) the variables ρ and θ are, as usual, the radial and polar coordinates, respectively.

Substituting Equation (15.13) into Equation (15.12) gives the following: (15.14)

Note that there are other possible expressions for the coefficients.

Table 15-1 gives a brief listing of common names for the aberration terms in Equation (15.14).
Table 15-1

Definition of Some Aberration Terms

Coefficient

Term

Typical Name

A1, 0, 0

ρ²

Focus

A0, 1, 0

a²

Piston

A0, 0, 1

aρcos(θ)

Tilt

A2, 0, 0

(ρ²)²

Spherical

A0, 2, 0

(a²)²

Piston

A0, 0, 2

(aρcos(θ))²

Astigmatism

A1, 1, 0

ρ²a²

Field curvature

A1, 0, 1

ρ²(aρcos(θ))

Coma

A0, 1, 1

a²(aρcos(θ))

Distortion

In the following section, we describe the program that will stereoscopically plot the aberration terms in cylindrical coordinates.

## 15.4 Stereoscopic Plot of Wave Aberration Terms in Cylindrical Coordinates

As in previous chapters, we need two points of projection to create two images slightly shifted horizontally. The following code sets the coordinates of the projecting points. As in our previous examples, D is the distance between the plane of projection and the screen.
D=800;
VX1=100; VY1=200; VZ1=0;
VX2=130; VY2=200; VZ2=0;

We will place the plot of the aberration terms at the middle between the plane of projection and the screen, using the percentage variable P with a value of 0.5.

Next, we set the number of pixels for the plot and create the mesh, as shown in Listing 15-1.
import numpy as np
N=40;  # Number of pixels to represent the function
y=np.zeros( (N+1,N+1) );
# Creating Mesh array
x1=np.zeros( (N+1,N+1) ); z1=np.zeros( (N+1,N+1) );
Listing 15-1

Creating the Mesh

As in previous chapters, the vertical heights are represented by the y-coordinate; x represents the horizontal coordinate and z represents the depth.

Now we create the radial. and polar coordinates using the code in Listing 15-2.
Pi=np.pi;
Rho=np.zeros(N+1); Theta=np.zeros(N+1);
for n in range(0,N+1):
Rho[n]=n/N;
Theta[n]=n/N*2*Pi;
Listing 15-2

Creating and Filling the Radial and Polar Coordinates

The size of both arrays is N+1. The radial array, Rho, has N+1 entries, with entries ranging from 0 to 1. The polar array, Theta, stores values from 0 to 2π.

To control the sizes of the width and depth of the plots, we will define a scaling factor, L.
L=70; # Horizontal and depth scale factor.
Now, we set D equal to 800, which is the distance between the plane of projection and the screen. We also set the percentage value P to 0.5.
D=800;
P=0.5; #Place the function at z=D/2
Now we can create the mesh in the x-z plane with the code shown in Listing 15-3.
ZG0=P*D;
for n in range (0,N+1):
for m in range(0,N+1):#Filling Mesh
x1[n][m]=L*Rho[n]*np.cos(Theta[m]);
z1[n][m]=L*Rho[n]*np.sin(Theta[m])+ZG0;
Listing 15-3

Creating the Mesh in the x-z Plane

We will also define a vertical scaling amplitude for y, as follows:
Amplitude=100; # Vertical scale factor.
Now we can fill the vertical array with data of one of the aberration terms. In the example in Listing 15-4, we use the term that corresponds to spherical aberration.
for n in range(0,N+1): #Filling function pixels
for m in range(0,N+1):
# Spherical aberration
y[n][m]=Amplitude * Rho[n]**4;
Listing 15-4

Filling the Vertical Array

In this code, the spherical aberration term is independent of Theta. For an aberration term depending on Theta, such as tilt for example, the code would be as follows:
for n in range(0,N+1): # Filling function pixels
for m in range(0,N+1):
# Tilt
y[n][m]=Amplitude * Rho[n]*np.cos(Theta[m]);
The remaining functions of the program are the same as in previous chapters. The complete code for main.py and File.kv are shown in Listing 15-5 and Listing 15-6, respectively.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.graphics import Line, Ellipse, Color
from kivy.clock import Clock
from kivy.core.image import Image as CoreImage
from PIL import Image, ImageDraw, ImageFont
import io
import os
import numpy as np
from kivy.lang import Builder
os.path.join(os.path.dirname(os.path.abspath(
__file__)), 'File.kv')
);
# Avoid Form1 of being resizable
from kivy.config import Config
Config.set("graphics","resizable", False);
Config.set('graphics', 'width',  '480');
Config.set('graphics', 'height', '680');
D=800;
VX1=100; VY1=200; VZ1=0;
VX2=130; VY2=200; VZ2=0;
P=0.5; # Place the function at z=D/2
N=40;  # Number of pixels to represent the function
y=np.zeros( (N+1,N+1) );
# Mesh
x1=np.zeros( (N+1,N+1) ); z1=np.zeros( (N+1,N+1) );
L=70;
Pi=np.pi;
XG0=0; ZG0=P*D; Amplitude=100;
Rho=np.zeros(N+1); Theta=np.zeros(N+1);
for n in range(0,N+1):
Rho[n]=n/N;
Theta[n]=n/N*2*Pi;
for n in range (0,N+1):
for m in range(0,N+1):#Filling Mesh
x1[n][m]=L*Rho[n]*np.cos(Theta[m]);
z1[n][m]=L*Rho[n]*np.sin(Theta[m])+ZG0;
for n in range(0,N+1): #Filling function pixels
for m in range(0,N+1):
y[n][m]=Amplitude * Rho[n]**4;
PointList=np.zeros( (N+1,2) );
def GraphFunction(VX,VY,VZ,Which):
global x1,y, z1, N;
# MaxY=np.max(y)
if (Which==0):
r,g,b = 200, 0, 0; #red Image
Draw=Draw1
else:
r,g,b= 0, 200, 200; #cyan image
Draw=Draw2
for n in range (0,N+1): #Horizontal Lines
for m in range (0,N+1):
Factor=(D-VZ)/(D-z1[n][m]-VZ);
xA=XC+Factor*(x1[n][m]-VX)+VX;
yA=YC-Factor*(y[n][m]-VY)-VY;
PointList[m]=xA,yA;
List=tuple( map(tuple,PointList) );
Draw.line( List, fill=(r,g,b), width=2 );
for n in range (0,N+1): #Vertical Lines
for m in range (0,N+1):
Factor=(D-VZ)/(D-z1[m][n]-VZ);
xA=XC+Factor*(x1[m][n]-VX)+VX;
yA=YC-Factor*(y[m][n]-VY)-VY;
PointList[m]=xA,yA;
List=tuple( map(tuple,PointList) );
Draw.line( List, fill=(r,g,b), width=2 );
Font = ImageFont.truetype('Gargi.ttf', 40);
Draw1.text( (10,10), "3D", fill =
(255,0,0,1), font=Font);
Draw2.text( (30,10), "3D", fill =
(0,255,255,1), font=Font);
def ShowScene(B):
Array1=np.array(PilImage1);
Array2=np.array(PilImage2);
Array3=Array1 | Array2;
PilImage3=Image.fromarray(Array3);
Memory=io.BytesIO();
PilImage3.save(Memory, format="png");
Memory.seek(0);
ImagePNG=CoreImage(Memory, ext="png");
B.ids.Screen1.texture=ImagePNG.texture;
ImagePNG.remove_from_cache()
Memory.close();
PilImage3.close();
Array1=None;
Array2=None;
Array3=None;
def ClearObjects():
Draw1.rectangle( (0, 0, W-10, H-10), fill=
(60, 70, 30, 1) );
Draw2.rectangle( (0, 0, W-10, H-10), fill=
(60, 70, 30, 1) );
def RotateFunction(B, Sense):
global XG0, ZG0
if Sense==-1:
Teta=np.pi/180*(-4.0);
else:
Teta=np.pi/180*(4.0);
Cos_Teta=np.cos(Teta)
Sin_Teta=np.sin(Teta);
X0=XG0;  Y0=0;  Z0=ZG0 # Center of rotation
for n in range(0,N+1):
for m in range(0,N+1):
if (B.ids.Button3.state=="down" or
B.ids.Button4.state=="down"):
yP=(y[n][m]-Y0)*Cos_Teta
+ (x1[n][m]-X0)*Sin_Teta + Y0;
xP=-(y[n][m]-Y0)*Sin_Teta
+(x1[n][m]-X0)*Cos_Teta + X0;
y[n][m]=yP;
x1[n][m]=xP;
if (B.ids.Button5.state=="down" or
B.ids.Button6.state=="down"):
yP=(y[n][m]-Y0)*Cos_Teta
+ (z1[n][m]-Z0)*Sin_Teta + Y0;
zP=-(y[n][m]-Y0)*Sin_Teta
+(z1[n][m]-Z0)*Cos_Teta + Z0;
y[n][m]=yP;
z1[n][m]=zP;
if (B.ids.Button7.state=="down" or
B.ids.Button8.state=="down"):
xP=(x1[n][m]-X0)*Cos_Teta
+ (z1[n][m]-Z0)*Sin_Teta + X0;
zP=-(x1[n][m]-X0)*Sin_Teta
+(z1[n][m]-Z0)*Cos_Teta + Z0;
x1[n][m]=xP;
z1[n][m]=zP;
class Form1(FloatLayout):
def __init__(Handle, **kwargs):
super(Form1, Handle).__init__(**kwargs);
Event1=Clock.schedule_once(Handle.Initialize);
def Initialize(B, *args):
global W,H, XC,YC;
global PilImage1,PilImage2, Draw1,Draw2;
# P= Percentage of the D distance
global P, Amplitude;
W,H=B.ids.Screen1.size;
XC=int (W/2)+P/(1-P)*VX1+30;
YC=int(H/2)-P/(1-P)*VY1+110;
PilImage1= Image.new('RGB', (W-10, H-10),
(60, 70, 30));
Draw1 = ImageDraw.Draw(PilImage1);
PilImage2= Image.new('RGB', (W-10, H-10),
(60, 70, 30));
Draw2 = ImageDraw.Draw(PilImage2);
Font = ImageFont.truetype('Gargi.ttf', 70)
Draw1.text( (30,200), "3D Images", fill =
(255,0,0,1), font=Font);
Draw2.text( (50,200), "3D Images", fill =
(0,255,255,1), font=Font);
ShowScene(B);
def Button1_Click(B):
global Draw1, Draw2;
ClearObjects(); # Clearing Draw1 and Draw2
GraphFunction(VX1,VY1,VZ1,0);
GraphFunction(VX2,VY2,VZ2,1);
ShowScene(B);
def Button2_Click(B):
ClearObjects(); # Clearing Draw1 and Draw2
Font = ImageFont.truetype('Gargi.ttf', 70)
Draw1.text( (30,200), "3D Images", fill =
(255,0,0,1), font=Font);
Draw2.text( (50,200), "3D Images", fill =
(0,255,255,1), font=Font);
ShowScene(B);
def Button3_Click(B):
RotateFunction(B,1);
ClearObjects(); # Clearing Draw1 and Draw2
GraphFunction(VX1,VY1,VZ1,0);
GraphFunction(VX2,VY2,VZ2,1);
ShowScene(B);
def Button4_Click(B):
RotateFunction(B,-1),
ClearObjects();
GraphFunction(VX1,VY1,VZ1,0);
GraphFunction(VX2,VY2,VZ2,1);
ShowScene(B);
def Button5_Click(B):
RotateFunction(B,-1),
ClearObjects();
GraphFunction(VX1,VY1,VZ1,0);
GraphFunction(VX2,VY2,VZ2,1);
ShowScene(B);
def Button6_Click(B):
RotateFunction(B,1),
ClearObjects();
GraphFunction(VX1,VY1,VZ1,0);
GraphFunction(VX2,VY2,VZ2,1);
ShowScene(B);
def Button7_Click(B):
RotateFunction(B,-1),
ClearObjects();
GraphFunction(VX1,VY1,VZ1,0);
GraphFunction(VX2,VY2,VZ2,1);
ShowScene(B);
def Button8_Click(B):
RotateFunction(B,1),
ClearObjects();
GraphFunction(VX1,VY1,VZ1,0);
GraphFunction(VX2,VY2,VZ2,1);
ShowScene(B);
# This is the Start Up code.
class StartUp (App):
def build (BU):
BU.title="Form1"
return Form1();
if __name__ =="__main__":
StartUp().run();
Listing 15-5

Code for the main.py File

#:set W 440
#:set H 440
<Form1>:
id : Form1
Image:
id: Screen1
size_hint: None,None
pos_hint: {"x":0.04, "y":0.34}
size: W,H
canvas.before:
Color:
rgba: 0.9, 0.9, 0, 1
RoundedRectangle:
pos:  self.pos
size: self.size
Button:
id: Button1
on_press: Form1.Button1_Click()
text: "Button1"
size_hint: None,None
pos_hint: {"x": 0.2, "y":0.03}
size: 100,30
Button:
id: Button2
on_press: Form1.Button2_Click()
text: "Button2"
size_hint: None,None
pos_hint: {"x": 0.63, "y":0.03}
size: 100,30
Button:
id: Button3
on_press: Form1.Button3_Click()
text: "Button3"
size_hint: None,None
pos_hint: {"x": 0.05, "y":0.12}
size: 100,30
always_release: True
Button:
id: Button4
on_press: Form1.Button4_Click()
text: "Button4"
size_hint: None,None
pos_hint: {"x": 0.73, "y":0.12}
size: 100,30
Button:
id: Button5
on_press: Form1.Button5_Click()
text: "Button5"
size_hint: None,None
pos_hint: {"x": 0.05, "y":0.20}
size: 100,30
Button:
id: Button6
on_press: Form1.Button6_Click()
text: "Button6"
size_hint: None,None
pos_hint: {"x": 0.73, "y":0.20}
size: 100,30
Button:
id: Button7
on_press: Form1.Button7_Click()
text: "Button7"
size_hint: None,None
pos_hint: {"x": 0.05, "y":0.28}
size: 100,30
Button:
id: Button8
on_press: Form1.Button8_Click()
text: "Button8"
size_hint: None,None
pos_hint: {"x": 0.73, "y":0.28}
size: 100,30
Listing 15-6

Code for the file.kv File

Figure 15-3 shows plots of five aberration terms obtained with the program, corresponding to the coefficients A0, 0, 1, A0, 0, 2, A1, 0, 0, A1, 0, 1, and A2, 0, 0 of Equation (15.12). Figure 15-3Wavefront aberration terms corresponding to the coefficients A0, 0, 1, A0, 0, 2, A1, 0, 0, A1, 0, 1, and A2, 0, 0 of Equation (15.12), plotted with the program

## 15.5 Summary

In this chapter, we introduced elements for plotting stereoscopic 3D cylindrical functions. In particular, we presented working examples of Seidel aberrations. We provided a brief outline of the Fresnel diffraction integral and applied this integral to review the focusing properties of ideal lenses. We extended these concepts to lens aberrations, illustrating programming methods for plotting the aberration terms in stereoscopic views.

• No Comment
..................Content has been hidden....................