• 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_13

# 13. Stereoscopic Plots in Spherical Coordinates

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

In this chapter, we describe elements for creating stereoscopic scenes of functions expressed in spherical coordinates. As in Chapter 12, we focus on spherical harmonics.

Figure 13-1 shows a screenshot of the program running on an Android cell phone. It shows a plot of the square of the absolute real value of the spherical harmonic l=3 and m=2, written as |Pl, m(cos(θ)) cos ()|2.

In the following sections, we describe this program.

## 13.1 Creating the Stereoscopic Scenes

As in previous chapters, to create the stereoscopic perception, we need two images slightly displaced horizontally. In our program, this is accomplished by using two points of projection. The corresponding parameters are as follows:
D=500;
VX1=60; VY1=60; VZ1=0;
VX2=80; VY2=60; VZ2=0;

In this code, D is the normal distance between the screen and the plane of projection.

To create stereoscopic views of our spherical harmonics plots, we will treat our graphs as three-dimensional objects. We will project them similarly as we did with the polygons. We will place them, centered, between the plane of projection and the screen by utilizing the percentage variable P, introduced in previous chapters, with a value of 0.5.

We will calculate the associated Legendre polynomials using Equation (12.​9). As this equation involves factorials and derivatives, we will need to create its corresponding symbolic expression. The code in Listing 13-1 creates the required formula.
x=sp.symbols("x");
def Plm(l,m ):
N=( 1/(2**l) )*(1 /sp.factorial(l) )
return N * (  (1-x**2)**(m/2)  )
* sp.diff( (x**2-1)**l, x,m+l );
Listing 13-1

Symbolically Calculating the Associated Legendre Polynomials Using Equation (12.​9)

Next, we apply the lambdify directive to obtain the corresponding numerical function, as in Chapter 12. The code looks as follows:
L_Num=3;  M_Num=2  #Legendre l, m orders
R=sp.lambdify( x, Plm(L_Num,M_Num) );

As in Chapter 12, the numerical function R(x) gives the values of Pl, m(x). For example, R(x) in the previous code gives P3, 2(x).

We will now create three arrays. The first two arrays, Theta and Phi, will store the angular variables, and the F array will store the associated Legendre polynomials , as shown in Listing 13-2.
N=40;  # Number of pixels to represent the function
Pi=np.pi;
Theta=np.zeros(N+1); Phi=np.zeros(N+1);
for n in range(0,N+1):
Theta[n]=n/N*Pi;
Phi[n]=n/N*2*Pi;
F=np.zeros( (N+1,N+1) );
for n in range(0,N+1):
for m in range(0,N+1):
F[n,m]=np.abs( R(np.cos(Theta[n]))*np.cos(M_Num*Phi[m]) )**2;
Listing 13-2

Creating the Angle Arrays and the Function

It is worth noticing that the Theta and Phi arrays are one-dimensional arrays with sizes N+1. In contrast, F is a two-dimensional (N+1) x (N+1) array.

To continue, we create the mesh in the same manner as in Chapter 12, using the following code:
x1=np.zeros( (N+1,N+1) );
y=np.zeros( (N+1,N+1) );
z1=np.zeros( (N+1,N+1) );
Now we create the plot, as shown in Listing 13-3.
L=2.4; D=500;
for n in range(0,N+1):
for m in range(0,N+1):
x1[n][m]=L*F[n][m]*np.sin(Theta[n])
*np.cos(Phi[m]);
z1[n][m]=L*F [n][m]*np.sin(Theta[n])
*np.sin(Phi[m])+D/2;
y[n][m]=L*F[n][m]*np.cos(Theta[n]);
Listing 13-3

Filling the Mesh and Vertical Coordinate

When the program starts, Initialize(B, *args) creates two PIL images, called PilImage1 and PilImage2, to store the left and right images required for our stereoscopic program. Additionally, two instances, Draw1 and Draw2, are created for drawing. PilImage1 will store the red image, while PilImage2 will store the cyan image. A third image, PilImage3, will hold the result of the bitwise OR operation between the two images.

We will need an array to store the set of points that composes each horizontal or vertical line of the plot. Therefore, we will create PointList with size (N+1)x2 using this directive:
PointList=np.zeros( (N+1,2) )

GraphFunction(VX, VY, VZ, Which) will now plot the function on its corresponding instance, according to the Which value. If the point of projection corresponds to the left one, the parameter called Which is 0 and the plotting color is red. If the point of projection corresponds to the one placed at the right, the Which parameter is 1 and the plotting color is cyan.

The code listings for main.py and file.kv are shown in Listing 13-4 and Listing 13-5, respectively.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
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
import sympy as sp
from kivy.lang import Builder
os.path.join(os.path.dirname(os.path.abspath(
__file__)), 'File.kv')
);
#import scipy.special as Special
#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');
x=sp.symbols("x");
def Plm(l,m ):
N=( 1/(2**l) )*(1 /sp.factorial(l) )
return N * (  (1-x**2)**(m/2)  )
* sp.diff( (x**2-1)**l, x,m+l );
R=sp.lambdify( x, Plm(3,2) );
def P2(x):
return 15*x*(1-x**2);
XC=0; YC=0; W=0; H=0;
D=500;
VX1=60; VY1=60; VZ1=0;
VX2=80; VY2=60; VZ2=0;
P=0.5; #Place the function at z=D/2
N=40;  #Number of pixels to represent the function
Pi=np.pi;
Theta=np.zeros(N+1); Phi=np.zeros(N+1);
for n in range(0,N+1):
Theta[n]=n/N*Pi;
Phi[n]=n/N*2*Pi;
M_Num=2; N_Num=3; #Legendre m,n orders
F=np.zeros( (N+1,N+1) );
for n in range(0,N+1):
for m in range(0,N+1):
F[n,m]=np.abs( R(np.cos(Theta[n]))
*np.cos(M_Num*Phi[m]) )**2;
x1=np.zeros( (N+1,N+1) );
y=np.zeros( (N+1,N+1) );
z1=np.zeros( (N+1,N+1) );
L=2.4;
for n in range(0,N+1):
for m in range(0,N+1):
x1[n][m]=L*F[n][m]*np.sin(Theta[n])
*np.cos(Phi[m]);
z1[n][m]=L*F [n][m]*np.sin(Theta[n])
*np.sin(Phi[m])+D/2;
y[n][m]=L*F[n][m]*np.cos(Theta[n]);
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 = 255, 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:
Theta=np.pi/180*(-4.0);
else:
Theta=np.pi/180*(4.0);
Cos_Theta=np.cos(Theta)
Sin_Theta=np.sin(Theta);
X0=0;  Y0=0;  Z0=D/2 # 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_Theta
+ (x1[n][m]-X0)*Sin_Theta + Y0;
xP=-(y[n][m]-Y0)*Sin_Theta
+(x1[n][m]-X0)*Cos_Theta + 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_Theta
+ (z1[n][m]-Z0)*Sin_Theta + Y0;
zP=-(y[n][m]-Y0)*Sin_Theta
+(z1[n][m]-Z0)*Cos_Theta + 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_Theta
+ (z1[n][m]-Z0)*Sin_Theta + X0;
zP=-(x1[n][m]-X0)*Sin_Theta
+(z1[n][m]-Z0)*Cos_Theta + 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;
YC=int(H/2)-P/(1-P)*VY1;
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 13-4

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 13-5

Code for the file.kv File

Figure 13-2 shows plots of |Pl, m(cos(θ)) cos ()|2 for different l. m, values, obtained with the code listed previously.

## 13.2 Summary

In this chapter, we introduced elements for creating stereoscopic scenes of functions expressed in spherical coordinates. We provided working examples for creating the required images, ORing them, and displayed them on a Kivy screen.

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