python pyqt matplotlib computation GUI app plotting wrong - python

everyone!
I am working on a project to use python pyqt4 to design a GUI for computing V-I curves of PV panel. I want to input the required parameter and compute the PV panel output current and use matplotlib to plot the V-I curve.
My code work well. However, I found that the curve I get from the app is wrong, while directly compute it is right. The computation methods are exactly same. But when I put the computation in GUI is different.
The code is here:
import sys
from PyQt4 import QtGui
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
import numpy as np
from scipy.optimize import fsolve
q = 1.6 * (10 ** -19)
k = 1.38 * (10 ** -23)
Tref = 25
Eg = 1.1
def calReverseSaturationCurrent(Isc,Voc,Ns,A,Tc):
a = np.exp(q*Voc/(Ns*k*A*Tc))-1
return Isc/a
def calSaturationCurrent(Irs,Tc,A):
return Irs*((Tc/Tref)**3)*np.exp(q*Eg*(1/Tref - 1/Tc)/(k*A))
def calPhotocurrent(Isc,Ki,Tc,insolation):
return (Isc + Ki*(Tc-Tref))*insolation
def calOutputCurrent(Isc,Voc,Ns,A,Tc,Ki,insolation,Np,Rs,Rsh,V):
Irs = calReverseSaturationCurrent(Isc,Voc,Ns,A,Tc)
Is = calSaturationCurrent(Irs,Tc,A)
Iph = calPhotocurrent(Isc,Ki,Tc,insolation)
I0 = 0
I = np.array([])
def f(ii,*arg):
temp_v = arg[0]
return ii - Np*Iph+Np*Is*(np.exp(q*(temp_v/Ns+ii*Rs/Np)/(k*Tc*A))-1)
for vv in V:
ii=fsolve(f,I0,args=vv)
I = np.append(I,ii)
return I
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.toolbar = NavigationToolbar(self.canvas, self)
self.toolbar.hide()
self.idealFactor = QtGui.QLabel('Ideal Factor A')
self.shortCircuitCurrent = QtGui.QLabel('Short Circuit Current')
self.openCircuitVoltage = QtGui.QLabel('Open Circuit Voltage')
self.temperatureCoefficient = QtGui.QLabel('Temperature Coefficient')
self.seriesCells = QtGui.QLabel('Series Cells')
self.parallelCells = QtGui.QLabel('Parallel Cells')
self.seriesResistance = QtGui.QLabel('Series Resistance')
self.shuntResistance = QtGui.QLabel('Shunt Resistance')
self.outputVoltageMin = QtGui.QLabel('Min Output Voltage')
self.outputVoltageMax = QtGui.QLabel('Max Output Voltage')
self.workingTemperature = QtGui.QLabel('Working Temperature')
self.isolation = QtGui.QLabel('Isolation')
self.idealFactorEdit = QtGui.QDoubleSpinBox()
self.idealFactorEdit.setValue(1.0)
self.shortCircuitCurrentEdit = QtGui.QDoubleSpinBox()
self.shortCircuitCurrentEdit.setValue(3.17)
self.openCircuitVoltageEdit = QtGui.QDoubleSpinBox()
self.openCircuitVoltageEdit.setValue(21.8)
self.temperatureCoefficientEdit = QtGui.QDoubleSpinBox()
self.temperatureCoefficientEdit.setValue(0.065)
self.seriesCellsEdit = QtGui.QDoubleSpinBox()
self.seriesCellsEdit.setValue(36.0)
self.parallelCellsEdit = QtGui.QDoubleSpinBox()
self.parallelCellsEdit.setValue(1.0)
self.seriesResistanceEdit = QtGui.QDoubleSpinBox()
self.seriesResistanceEdit.setValue(0.1)
self.shuntResistanceEdit = QtGui.QDoubleSpinBox()
self.shuntResistanceEdit.setValue(float("inf"))
self.outputVoltageMinEdit = QtGui.QDoubleSpinBox()
self.outputVoltageMinEdit.setValue(8)
self.outputVoltageMaxEdit = QtGui.QDoubleSpinBox()
self.outputVoltageMaxEdit.setValue(11)
self.workingTemperatureEdit = QtGui.QDoubleSpinBox()
self.workingTemperatureEdit.setValue(25)
self.isolationEdit = QtGui.QDoubleSpinBox()
self.isolationEdit.setValue(1)
self.calBtn = QtGui.QPushButton('Get V-I',self)
self.calBtn.clicked.connect(self.getVI)
grid = QtGui.QGridLayout()
self.setLayout(grid)
grid.addWidget(self.idealFactor, 1, 0)
grid.addWidget(self.idealFactorEdit, 1, 1)
grid.addWidget(self.shortCircuitCurrent, 1, 2)
grid.addWidget(self.shortCircuitCurrentEdit, 1, 3)
grid.addWidget(self.openCircuitVoltage, 2, 0)
grid.addWidget(self.openCircuitVoltageEdit, 2, 1)
grid.addWidget(self.temperatureCoefficient, 2, 2)
grid.addWidget(self.temperatureCoefficientEdit, 2, 3)
grid.addWidget(self.seriesCells, 3, 0)
grid.addWidget(self.seriesCellsEdit, 3, 1)
grid.addWidget(self.parallelCells, 3, 2)
grid.addWidget(self.parallelCellsEdit, 3, 3)
grid.addWidget(self.seriesResistance, 4, 0)
grid.addWidget(self.seriesResistanceEdit, 4, 1)
grid.addWidget(self.shuntResistance, 4, 2)
grid.addWidget(self.shuntResistanceEdit, 4, 3)
grid.addWidget(self.outputVoltageMax, 5, 0)
grid.addWidget(self.outputVoltageMaxEdit, 5, 1)
grid.addWidget(self.outputVoltageMin, 5, 2)
grid.addWidget(self.outputVoltageMinEdit, 5, 3)
grid.addWidget(self.workingTemperature, 6, 0)
grid.addWidget(self.workingTemperatureEdit, 6, 1)
grid.addWidget(self.isolation, 6, 2)
grid.addWidget(self.isolationEdit, 6, 3)
grid.addWidget(self.calBtn, 7,0)
grid.addWidget(self.canvas, 8, 0, 6, 0)
self.setGeometry(300, 300, 1000, 800)
self.setWindowTitle('PYPV')
self.setWindowIcon(QtGui.QIcon('pvPanel.png'))
self.show()
def getVI(self):
A = self.idealFactorEdit.value()
Isc = self.shortCircuitCurrentEdit.value()
Voc = self.openCircuitVoltageEdit.value()
Ki = self.temperatureCoefficientEdit.value()
Ns = self.seriesCellsEdit.value()
Np = self.parallelCellsEdit.value()
Rs = self.seriesResistanceEdit.value()
Rsh = self.shuntResistanceEdit.value()
Vmin = self.outputVoltageMinEdit.value()
Vmax = self.outputVoltageMaxEdit.value()
V = np.linspace(Vmin,Vmax,1000)
Tc = self.workingTemperatureEdit.value()
insolation = self.isolationEdit.value()
I = calOutputCurrent(Isc,Voc,Ns,A,Tc,Ki,insolation,Np,Rs,Rsh,V)
plt.cla()
ax = self.figure.add_subplot(111)
ax.plot(V,I)
self.canvas.draw()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I am sure that the computing method is right. I tested without the GUI. The result is fine. The code is below:
import numpy as np
from scipy.optimize import fsolve
from matplotlib import pyplot as plt
q = 1.6 * (10 ** -19)
k = 1.38 * (10 ** -23)
Tref = 25
Eg = 1.1
A = 1.0
V = np.linspace(8,11,1000)
Isc = 3.17
Ki = 0.07
Ns = 36.0
Np = 1.0
Voc = 21.8
Rs = 0.1
#Rsh = float("inf")
Tc = 25
insolation = 1
def calReverseSaturationCurrent(Isc,Voc,Ns,A,Tc):
a = np.exp(q*Voc/(Ns*k*A*Tc))-1
return Isc/a
def calSaturationCurrent(Irs,Tc,A):
return Irs*((Tc/Tref)**3)*np.exp(q*Eg*(1/Tref - 1/Tc)/(k*A))
def calPhotocurrent(Isc,Ki,Tc,insolation):
return (Isc + Ki*(Tc-Tref))*insolation
def calOutputCurrent(Isc,Voc,Ns,A,Tc,Ki,insolation,Np,Rs,Rsh,V):
Irs = calReverseSaturationCurrent(Isc,Voc,Ns,A,Tc)
Is = calSaturationCurrent(Irs,Tc,A)
Iph = calPhotocurrent(Isc,Ki,Tc,insolation)
I0 = 0
I = np.array([])
def f(ii,*arg):
temp_v = arg[0]
return ii - Np*Iph+Np*Is*(np.exp(q*(temp_v/Ns+ii*Rs/Np)/(k*Tc*A))-1)
for vv in V:
ii=fsolve(f,I0,args=vv)
I = np.append(I,ii)
return I
def makePlots(I,V):
plt.plot(V,I)
plt.show()
def test():
I = calOutputCurrent(Isc,Voc,Ns,A,Tc,Ki,insolation,Np,Rs,Rsh,V)
makePlots(I,V)
if __name__ == "__main__":
test()
Below are the two plots:
Without using GUI
With GUI
English is not my first language. I hope I describe the problem well.
Thanks in advance!

My guess is that you use Python-2, where the / operator behaves differently for integers than for floats. In the first case it is an integer-division: the result is an integer and the remainder is discarded. In the second case it is what you would typically expect from division. This is a source of many bugs so in Python-3 the / operator is always a regular division (returning a float), and the integer-division can be done with //. For more details see PEP-238. Note that your calSaturationCurrent function contains such an integer division: 1/Tc yields 0 in Python-2.
To make Python-2 behave like Python 3 you can use a 'future-import'. If you add the following line at the top of your program (before your other import statements) you will get the same results with and without GUI.
from __future__ import division
Now, you probably also used Python-2 for your non-GUI version, so why did you get different results in the first place? My best guess is that in the gui-version of your program, the values you get from Qt are slightly different (far behind the decimal point that is). The fact that your program is susceptible for this indicates it may have bugs.
For instance, if you print the result of the calSaturationCurrent function, it is 3.42723507299e-122 without GUI, and 0.0 with the GUI. This means that the function f, whose roots you are trying to solve, is always very close to 0 or exactly 0. I would not trust the results, and would start by making a plot of f itself.

Related

I'm trying to figure out this error " TypeError: Cannot cast array data from dtype('O') to dtype('float64') according to the rule 'safe'"

I'm running this code any this error keeps popping up "TypeError: Cannot cast array data from dtype('O') to dtype('float64') according to the rule 'safe'". Most likely this line is causing a problem
----> 2 sols = odeint(VJJmodel,[0,0],ts, args=tuple([params]) )
Please advise.
from scipy.integrate import odeint
import numpy as np
import scipy.constants as const
from matplotlib import pyplot as plt
from scipy.interpolate import interp1d
from scipy.integrate import quad
import collections
Ic = 1e-6
Rsg = 10e3
Rn = Rsg
Cj = 2e-15
Rin = 100e0
Vgap = 4*Ic*Rn/np.pi #Gap voltage linked to IcRn, SIS relation
Vn = 1.1 #0.1*Vgap/Ic/Rin
V0 = Vn*Rin*Ic
phi0 = const.value('mag. flux quantum')
wp = 1/np.sqrt(Cj*phi0/2/np.pi/Ic)
params = {'Vn':Vn, 'Rsg': Rsg, 'Rn':Rn, 'Vgap':Vgap, 'Cj':Cj, 'Rin':Rin, 'wp':wp}
print(Vn)
taustop = 20* wp * Rsg*Cj
params['taustop'] = taustop
print(params)
def Rj(V,params):
Vgap = params['Vgap']
if V>Vgap:
return params['Rn']
elif V<=Vgap:
return params['Rsg']
def Q(V,params):
Cj = params['Cj']
Rin = params['Rin']
wp = params['wp']
return Cj*wp/(1/Rj(V,params)+1/Rin)
def Vs(t,params):
if not hasattr(t,'__len__'):
t = np.array([t])
result = []
for x in t:
if x <params['taustop']/2:
result.append(x/(params['taustop']/2)*params['Vn'])
else:
result.append(params['Vn'])
#return params['Vn']
result = np.ones(len(t))*params['Vn']
return np.array(result)
def VJJmodel(z,t,params):
f = z[0]
g = z[1]
V = phi0/2/np.pi*g
fp = g
gp = Vs(t,params)-(1/Q(V,params)*g+np.sin(f))
return np.array([fp,gp])
ts = np.linspace(0,2*taustop,2000)
sols = odeint(VJJmodel,[0,0],ts, args=tuple([params]) )
solsfunc = interp1d(ts,sols.T)
Vjunc = lambda x: phi0/2/np.pi * wp * solsfunc(x)[1]
taurep = 2*np.pi/np.average(np.diff(sols[:,0])/np.diff(ts))
fig = plt.figure(figsize=(10,10))
plt.clf()
plt.subplot(3, 1, 1)
plt.title('Voltage/Vgap')
plt.plot(ts,Vjunc(ts) / Vgap)
plt.subplot(3, 1, 2)
plt.title('Current')
plt.plot(ts,np.sin(solsfunc(ts)[0]))
plt.plot(ts,(Vs(ts,params)*Ic*Rin-Vjunc(ts))/Rin/Ic)
plt.ylim([-2,2])
plt.subplot(3, 1, 3)
plt.title('Phase')
plt.plot(ts,solsfunc(ts)[0])
plt.show()
plt.close(fig)
I tried to rewrite it but it didn't work.

Tkinter GUI of real time multiple graphs

I have asked this before and maybe I was not clear so, asking again. I am taking 4 different sensor values from arduino and plotting them real time using matplotlib of python. The real time plotting of multiple sensors is working, but I need them in a GUI with a start and a stop button. How do I modify my following code such that I get 4 real time plots in a GUI. This code is working, I just need to see my graphs in a GUI. Thanks in advance and help me the additional modification as I am new to python and still learning.
import copy
from threading import Thread
from tkinter import Tk, Frame
import serial
import time
import collections
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import struct
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
A = 0
B = 0
C = 0
D = 0
Sum = 0
Az = 0
El = 0
class serialPlot:
def __init__(self, serialPort='com5', serialBaud=38400, plotLength=100, dataNumBytes=2, numPlots=1):
self.port = serialPort
self.baud = serialBaud
self.plotMaxLength = plotLength
self.dataNumBytes = dataNumBytes
self.numPlots = numPlots
self.rawData = bytearray(numPlots * dataNumBytes)
self.dataType = None
if dataNumBytes == 2:
self.dataType = 'h' # 2 byte integer
elif dataNumBytes == 4:
self.dataType = 'f' # 4 byte float
self.data = []
self.privateData = None # for storing a copy of the data so all plots are synchronized
for i in range(numPlots): # give an array for each type of data and store them in a list
self.data.append(collections.deque([0] * plotLength, maxlen=plotLength))
self.isRun = True
self.isReceiving = False
self.thread = None
self.plotTimer = 0
self.previousTimer = 0
print('Trying to connect to: ' + str(serialPort) + ' at ' + str(serialBaud) + ' BAUD.')
try:
self.serialConnection = serial.Serial(serialPort, serialBaud, timeout=4)
print('Connected to ' + str(serialPort) + ' at ' + str(serialBaud) + ' BAUD.')
except:
print("Failed to connect with " + str(serialPort) + ' at ' + str(serialBaud) + ' BAUD.')
def readSerialStart(self):
if self.thread == None:
self.thread = Thread(target=self.backgroundThread)
self.thread.start()
# Block till we start receiving values
while self.isReceiving != True:
time.sleep(0.1)
def getSerialData(self, frame, lines, lineValueText, lineLabel, timeText, pltNumber):
if pltNumber == 0: # in order to make all the clocks show the same reading
currentTimer = time.perf_counter()
self.plotTimer = int((currentTimer - self.previousTimer) * 1000) # the first reading will be erroneous
self.previousTimer = currentTimer
self.privateData = copy.deepcopy(
self.rawData) # so that the 4 values in our plots will be synchronized to the same sample time
timeText.set_text('Plot Interval = ' + str(self.plotTimer) + 'ms')
data = self.privateData[(pltNumber * self.dataNumBytes):(self.dataNumBytes + pltNumber * self.dataNumBytes)]
value, = struct.unpack(self.dataType, data)
self.data[pltNumber].append(value) # we get the latest data point and append it to our array
lines.set_data(range(self.plotMaxLength), self.data[pltNumber])
lineValueText.set_text('[' + lineLabel + '] = ' + str(value))
if lineLabel == 'Detector A':
global A
A = float(value)
if lineLabel == 'Detector B':
global B
B = float(value)
if lineLabel == 'Detector C':
global C
C = float(value)
if lineLabel == 'Detector D':
global D
D = float(value)
Sum = (A + B + C + D)
Az = (A + D - C - B)
El = (A + B - C - D)
def backgroundThread(self): # retrieve data
time.sleep(1.0) # give some buffer time for retrieving data
self.serialConnection.reset_input_buffer()
while (self.isRun):
self.serialConnection.readinto(self.rawData)
self.isReceiving = True
def close(self):
self.isRun = False
self.thread.join()
self.serialConnection.close()
print('Disconnected...')
def makeFigure(xLimit, yLimit, title):
xmin, xmax = xLimit
ymin, ymax = yLimit
fig = plt.figure()
ax = plt.axes(xlim=(xmin, xmax), ylim=(int(ymin - (ymax - ymin) / 10), int(ymax + (ymax - ymin) / 10)))
ax.set_title(title)
ax.set_xlabel("Time")
ax.set_ylabel("Detector Output")
ax.grid(True)
return fig, ax
class Window(Frame):
def __init__(self, figure, master, SerialReference):
Frame.__init__(self, master)
self.entry = None
self.setPoint = None
self.master = master # a reference to the master window
self.serialReference = SerialReference # keep a reference to our serial connection so that we can use it for bi-directional communicate from this class
self.initWindow(figure) # initialize the window with our settings
def initWindow(self, figure):
self.master.title("Real Time Plot")
canvas = FigureCanvasTkAgg(figure, master=self.master)
#toolbar = NavigationToolbar2TkAgg(canvas, self.master)
#canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
def main():
portName = 'COM5'
# portName = '/dev/ttyUSB0'
baudRate = 38400
maxPlotLength = 100 # number of points in x-axis of real time plot
dataNumBytes = 4 # number of bytes of 1 data point
numPlots = 4 # number of plots in 1 graph
s = serialPlot(portName, baudRate, maxPlotLength, dataNumBytes, numPlots) # initializes all required variables
s.readSerialStart() # starts background thread
# plotting starts below
pltInterval = 50 # Period at which the plot animation updates [ms]
lineLabelText = ['Detector A', 'Detector B', 'Detector C', 'Detector D']
title = ['Detector A', 'Detector B', 'Detector C', 'Detector D']
xLimit = [(0, maxPlotLength), (0, maxPlotLength), (0, maxPlotLength), (0, maxPlotLength)]
yLimit = [(-1, 1), (-1, 1), (-1, 1), (-1, 1)]
style = ['r-', 'g-', 'b-', 'y-'] # linestyles for the different plots
anim = []
for i in range(numPlots):
fig, ax = makeFigure(xLimit[i], yLimit[i], title[i])
lines = ax.plot([], [], style[i], label=lineLabelText[i])[0]
timeText = ax.text(0.50, 0.95, '', transform=ax.transAxes)
lineValueText = ax.text(0.50, 0.90, '', transform=ax.transAxes)
anim.append(
animation.FuncAnimation(fig, s.getSerialData, fargs=(lines, lineValueText, lineLabelText[i], timeText, i),
interval=pltInterval)) # fargs has to be a tuple
plt.legend(loc="upper left")
plt.show()
s.close()
if __name__ == '__main__':
main()

Nothing Happens When Running Script in Spyder

I am running this code and oddly, nothing happens. There is no error nor does it freeze. It simply just runs the code without storing variables, nothing is printed out and it doesn't open the window that is supposed to show the plot. So it simply does nothing. It is very odd. This worked only a few minutes ago and I did not change anything about it previously. I did make sure that the variable explorer is displaying all the definitions in the script. I intentionally removed the plotting section at the end since it just made the code set longer and the same issue persists here without it.
Code:
#Import libraries
import numpy as np
from scipy.integrate import odeint
#from scipy.integrate import solve_ivp
from time import time
import matplotlib.pyplot as plt
from matplotlib.pyplot import grid
from mpl_toolkits.mplot3d import Axes3D
import numpy, scipy.io
from matplotlib.patches import Circle
'''
import sympy as sy
import random as rand
from scipy import interpolate
'''
'''
Initiate Timer
'''
TimeStart = time()
'''
#User defined inputs
'''
TStep = (17.8E-13)
TFinal = (17.8E-10)
R0 = 0.02
V0X = 1E7
ParticleCount = 1 #No. of particles to generate energies for energy generation
BInput = 0.64 #Magnitude of B field near pole of magnet in experiment
ScaleV0Z = 1
'''
#Defining constants based on user input and nature (Cleared of all errors!)
'''
#Defining Space and Particle Density based on Pressure PV = NkT
k = 1.38E-23 #Boltzman Constant
#Natural Constants
Q_e = -1.602E-19 #Charge of electron
M_e = 9.11E-31 #Mass of electron
JToEv = 6.24E+18 #Joules to eV conversion
EpNaut = 8.854187E-12
u0 = 1.256E-6
k = 1/(4*np.pi*EpNaut)
QeMe = Q_e/M_e
'''
Create zeros matrices to populate later (Cannot create TimeIndex array!)
'''
TimeSpan = np.linspace(0,TFinal,num=round((TFinal/TStep)))
TimeIndex = np.linspace(0,TimeSpan.size,num=TimeSpan.size)
ParticleTrajectoryMat = np.zeros([91,TimeSpan.size,6])
BFieldTracking = np.zeros([TimeSpan.size,3])
InputAngle = np.array([np.linspace(0,90,91)])
OutputAngle = np.zeros([InputAngle.size,1])
OutputRadial = np.zeros([InputAngle.size,1])
'''
Define B-Field
'''
def BField(x,y,z):
InputCoord = np.array([x,y,z])
VolMag = 3.218E-6 #Volume of magnet in experiment in m^3
BR = np.sqrt(InputCoord[0]**2 + InputCoord[1]**2 + InputCoord[2]**2)
MagMoment = np.array([0,0,(BInput*VolMag)/u0])
BDipole = (u0/(4*np.pi))*(((3*InputCoord*np.dot(MagMoment,InputCoord))/BR**5)-(MagMoment/BR**3))
#BVec = np.array([BDipole[0],BDipole[1],BDipole[2]])
#print(BDipole[0],BDipole[1],BDipole[2])
return (BDipole[0],BDipole[1],BDipole[2])
'''
Lorentz Force Differential Equations Definition
'''
def LorentzForce(PosVel,t,Constants):
X,Y,Z,VX,VY,VZ = PosVel
Bx,By,Bz,QeMe = Constants
BFInput = np.array([Bx,By,Bz])
VelInput = np.array([VX,VY,VZ])
Accel = QeMe * (np.cross(VelInput, BFInput))
LFEqs = np.concatenate((VelInput, Accel), axis = 0)
return LFEqs
'''
Cartesean to Spherical coordinates converter function. Returns: [Radius (m), Theta (rad), Phi (rad)]
'''
def Cart2Sphere(xIn,yIn,zIn):
P = np.sqrt(xIn**2 + yIn**2 + zIn**2)
if xIn == 0:
Theta = np.pi/2
else:
Theta = np.arctan(yIn/xIn)
Phi = np.arccos(zIn/np.sqrt(xIn**2 + yIn**2 + zIn**2))
SphereVector = np.array([P,Theta,Phi])
return SphereVector
'''
Main Loop
'''
for angletrack in range(0,InputAngle.size):
MirrorAngle = InputAngle[0,angletrack]
MirrorAngleRad = MirrorAngle*(np.pi/180)
V0Z = np.abs(V0X/np.sin(MirrorAngleRad))*np.sqrt(1-(np.sin(MirrorAngleRad))**2)
V0Z = V0Z*ScaleV0Z
#Define initial conditions
V0 = np.array([[V0X,0,V0Z]])
S0 = np.array([[0,R0,0]])
ParticleTrajectoryMat[0,:] = np.concatenate((S0,V0),axis=None)
for timeplace in range(0,TimeIndex.size-1):
ICs = np.concatenate((S0,V0),axis=None)
Bx,By,Bz = BField(S0[0,0],S0[0,1],S0[0,2])
BFieldTracking[timeplace,:] = np.array([Bx,By,Bz])
AllConstantInputs = [Bx,By,Bz,QeMe]
t = np.array([TimeSpan[timeplace],TimeSpan[timeplace+1]])
ODESolution = odeint(LorentzForce,ICs,t,args=(AllConstantInputs,))
ParticleTrajectoryMat[angletrack,timeplace+1,:] = ODESolution[1,:]
S0[0,0:3] = ODESolution[1,0:3]
V0[0,0:3] = ODESolution[1,3:6]
MatSize = np.array([ParticleTrajectoryMat.shape])
RowNum = MatSize[0,1]
SphereMat = np.zeros([RowNum,3])
SphereMatDeg = np.zeros([RowNum,3])
for cart2sphereplace in range(0,RowNum):
SphereMat[cart2sphereplace,:] = Cart2Sphere(ParticleTrajectoryMat[angletrack,cart2sphereplace,0],ParticleTrajectoryMat[angletrack,cart2sphereplace,1],ParticleTrajectoryMat[angletrack,cart2sphereplace,2])
for rad2deg in range(0,RowNum):
SphereMatDeg[rad2deg,:] = np.array([SphereMat[rad2deg,0],(180/np.pi)*SphereMat[rad2deg,1],(180/np.pi)*SphereMat[rad2deg,2]])
PhiDegVec = np.array([SphereMatDeg[:,2]])
RVec = np.array([SphereMatDeg[:,0]])
MinPhi = np.amin(PhiDegVec)
MinPhiLocationTuple = np.where(PhiDegVec == np.amin(PhiDegVec))
MinPhiLocation = int(MinPhiLocationTuple[1])
RAtMinPhi = RVec[0,MinPhiLocation]
OutputAngle[angletrack,0] = MinPhi
OutputRadial[angletrack,0] = RAtMinPhi
print('Mirror Angle Input (In deg): ',InputAngle[0,angletrack])
print('Mirror Angle Output (In deg): ',MinPhi)
print('R Value at minimum Phi (m): ',RAtMinPhi)
InputAngleTrans = np.matrix.transpose(InputAngle)
CompareMat = np.concatenate((InputAngleTrans,OutputAngle),axis=1)

Python+Chaco+Traits - rendering bug: unexpected fills of line plot of large data?

Using the minimal example below, the line plot of a large (some 110k points) plot I get (with python 2.7, numpy 1.5.1, chaco/enable/traits 4.3.0) is this:
However, that is bizarre, because it is a line plot, and there shouldn't be any filled areas in there? Especially since the data is sawtooth-ish signal? It's as if there is a line at y~=37XX, above which there is color filling?! But sure enough, if I zoom into an area, I get the rendering I expect - without the unexpected fill:
Is this a bug - or is there something I'm doing wrong? I tried to use use_downsampling, but it makes no difference...
The test code:
import numpy as np
import numpy.random as npr
from pprint import pprint
from traits.api import HasTraits, Instance
from chaco.api import Plot, ArrayPlotData, VPlotContainer
from traitsui.api import View, Item
from enable.component_editor import ComponentEditor
from chaco.tools.api import PanTool, BetterSelectingZoom
tlen = 112607
alr = npr.randint(0, 4000, tlen)
tx = np.arange(0.0, 30.0-0.00001, 30.0/tlen)
ty = np.arange(0, tlen, 1) % 10000 + alr
pprint(len(ty))
class ChacoTest(HasTraits):
container = Instance(VPlotContainer)
traits_view = View(
Item('container', editor=ComponentEditor(), show_label=False),
width=800, height=500, resizable=True,
title="Chaco Test"
)
def __init__(self):
super(ChacoTest, self).__init__()
pprint(ty)
self.plotdata = ArrayPlotData(x = tx, y = ty)
self.plotobj = Plot(self.plotdata)
self.plotA = self.plotobj.plot(("x", "y"), type="line", color=(0,0.99,0), spacing=0, padding=0, alpha=0.7, use_downsampling=True)
self.container = VPlotContainer(self.plotobj, spacing=5, padding=5, bgcolor="lightgray")
#~ container.add(plot)
self.plotobj.tools.append(PanTool(self.plotobj))
self.plotobj.overlays.append(BetterSelectingZoom(self.plotobj))
if __name__ == "__main__":
ChacoTest().configure_traits()
I am able to reproduce the error and talking with John Wiggins (maintainer of Enable), it is a bug in kiva (which chaco uses to paint on the screen):
https://github.com/enthought/enable
The good news is that this is a bug in one of the kiva backend that you can use. So to go around the issue, you can run your script choosing a different backend:
ETS_TOOLKIT=qt4.qpainter python <NAME OF YOUR SCRIPT>
if you use qpainter or quartz, the plot looks (on my machine) as expected. If you choose qt4.image (the Agg backend), you will reproduce the issue. Unfortunately, the Agg backend is the default one. To change that, you can set the ETS_TOOLKIT environment variable to that value:
export ETS_TOOLKIT=qt4.qpainter
The bad news is that fixing this isn't going to be an easy task. Please feel free to report the bug in github (again https://github.com/enthought/enable) if you want to be involved in this. If you don't, I will log it in the next couple of days. Thanks for reporting it!
Just a note - I found this:
[Enthought-Dev] is chaco faster than matplotlib
I recall reading somewhere that you are expected to implement the
_downsample method because the optimal algorithm depends on the type
of data you're collecting.
And as I couldn't find any examples with _downsample implementation other than decimated_plot.py referred in that post, which isn't standalone - I tried and built a standalone example, included below.
The example basically has messed up drag and zoom, (plot disappears if you go out of range, or stretches upon a drag move) - and it starts zoomed in; but it is possible to zoom it out in the range shown in the OP - and then it displays the exact same plot rendering problem. So downsampling isn't the solution per se, so this is likely a bug?
import numpy as np
import numpy.random as npr
from pprint import pprint
from traits.api import HasTraits, Instance
from chaco.api import Plot, ArrayPlotData, VPlotContainer
from traitsui.api import View, Item
from enable.component_editor import ComponentEditor
from chaco.tools.api import PanTool, BetterSelectingZoom
#
from chaco.api import BaseXYPlot, LinearMapper, AbstractPlotData
from enable.api import black_color_trait, LineStyle
from traits.api import Float, Enum, Int, Str, Trait, Event, Property, Array, cached_property, Bool, Dict
from chaco.abstract_mapper import AbstractMapper
from chaco.abstract_data_source import AbstractDataSource
from chaco.array_data_source import ArrayDataSource
from chaco.data_range_1d import DataRange1D
tlen = 112607
alr = npr.randint(0, 4000, tlen)
tx = np.arange(0.0, 30.0-0.00001, 30.0/tlen)
ty = np.arange(0, tlen, 1) % 10000 + alr
pprint(len(ty))
class ChacoTest(HasTraits):
container = Instance(VPlotContainer)
traits_view = View(
Item('container', editor=ComponentEditor(), show_label=False),
width=800, height=500, resizable=True,
title="Chaco Test"
)
downsampling_cutoff = Int(4)
def __init__(self):
super(ChacoTest, self).__init__()
pprint(ty)
self.plotdata = ArrayPlotData(x = tx, y = ty)
self.plotobj = TimeSeriesPlot(self.plotdata)
self.plotobj.setplotranges("x", "y")
self.container = VPlotContainer(self.plotobj, spacing=5, padding=5, bgcolor="lightgray")
self.plotobj.tools.append(PanTool(self.plotobj))
self.plotobj.overlays.append(BetterSelectingZoom(self.plotobj))
# decimate from:
# https://bitbucket.org/mjrosen/neurobehavior/raw/097ef3719d1263a8b303d29c31ab71b6e792ab04/cns/widgets/views/decimated_plot.py
def decimate(data, screen_width, downsampling_cutoff=4, mode='extremes'):
data_width = data.shape[-1]
downsample = np.floor((data_width/screen_width)/4.)
if downsample > downsampling_cutoff:
return globals()['decimate_'+mode](data, downsample)
else:
return data
def decimate_extremes(data, downsample):
last_dim = data.ndim
offset = data.shape[-1] % downsample
if data.ndim == 2:
shape = (len(data), -1, downsample)
else:
shape = (-1, downsample)
data = data[..., offset:].reshape(shape).copy()
data_min = data.min(last_dim)
data_max = data.max(last_dim)
return data_min, data_max
def decimate_mean(data, downsample):
offset = len(data) % downsample
if data.ndim == 2:
shape = (-1, downsample, data.shape[-1])
else:
shape = (-1, downsample)
data = data[offset:].reshape(shape).copy()
return data.mean(1)
# based on class from decimated_plot.py, also
# neurobehavior/cns/chaco_exts/timeseries_plot.py ;
# + some other code from chaco
class TimeSeriesPlot(BaseXYPlot):
color = black_color_trait
line_width = Float(1.0)
line_style = LineStyle
reference = Enum('most_recent', 'trigger')
traits_view = View("color#", "line_width")
downsampling_cutoff = Int(100)
signal_trait = "updated"
decimate_mode = Str('extremes')
ch_index = Trait(None, Int, None)
# Mapping of data names from self.data to their respective datasources.
datasources = Dict(Str, Instance(AbstractDataSource))
index_mapper = Instance(AbstractMapper)
value_mapper = Instance(AbstractMapper)
def __init__(self, data=None, **kwargs):
super(TimeSeriesPlot, self).__init__(**kwargs)
self._index_mapper_changed(None, self.index_mapper)
self.setplotdata(data)
self._plot_ui_info = None
return
def setplotdata(self, data):
if data is not None:
if isinstance(data, AbstractPlotData):
self.data = data
elif type(data) in (ndarray, tuple, list):
self.data = ArrayPlotData(data)
else:
raise ValueError, "Don't know how to create PlotData for data" \
"of type " + str(type(data))
def setplotranges(self, index_name, value_name):
self.index_name = index_name
self.value_name = value_name
index = self._get_or_create_datasource(index_name)
value = self._get_or_create_datasource(value_name)
if not(self.index_mapper):
imap = LinearMapper()#(range=self.index_range)
self.index_mapper = imap
if not(self.value_mapper):
vmap = LinearMapper()#(range=self.value_range)
self.value_mapper = vmap
if not(self.index_range): self.index_range = DataRange1D() # calls index_mapper
if not(self.value_range): self.value_range = DataRange1D()
self.index_range.add(index) # calls index_mapper!
self.value_range.add(value)
# now do it (right?):
self.index_mapper = LinearMapper(range=self.index_range)
self.value_mapper = LinearMapper(range=self.value_range)
def _get_or_create_datasource(self, name):
if name not in self.datasources:
data = self.data.get_data(name)
if type(data) in (list, tuple):
data = array(data)
if isinstance(data, np.ndarray):
if len(data.shape) == 1:
ds = ArrayDataSource(data, sort_order="none")
elif len(data.shape) == 2:
ds = ImageData(data=data, value_depth=1)
elif len(data.shape) == 3:
if data.shape[2] in (3,4):
ds = ImageData(data=data, value_depth=int(data.shape[2]))
else:
raise ValueError("Unhandled array shape in creating new plot: " \
+ str(data.shape))
elif isinstance(data, AbstractDataSource):
ds = data
else:
raise ValueError("Couldn't create datasource for data of type " + \
str(type(data)))
self.datasources[name] = ds
return self.datasources[name]
def get_screen_points(self):
self._gather_points()
return self._downsample()
def _data_changed(self):
self.invalidate_draw()
self._cache_valid = False
self._screen_cache_valid = False
self.request_redraw()
def _gather_points(self):
if not self._cache_valid:
range = self.index_mapper.range
#if self.reference == 'most_recent':
# values, t_lb, t_ub = self.get_recent_range(range.low, range.high)
#else:
# values, t_lb, t_ub = self.get_range(range.low, range.high, -1)
values, t_lb, t_ub = self.data[self.value_name][range.low:range.high], range.low, range.high
#if self.ch_index is None:
# self._cached_data = values
#else:
# #self._cached_data = values[:,self.ch_index]
self._cached_data = values
self._cached_data_bounds = t_lb, t_ub
self._cache_valid = True
self._screen_cache_valid = False
def _downsample(self):
if not self._screen_cache_valid:
val_pts = self._cached_data
screen_min, screen_max = self.index_mapper.screen_bounds
screen_width = screen_max-screen_min
values = decimate(val_pts, screen_width, self.downsampling_cutoff,
self.decimate_mode)
if type(values) == type(()):
n = len(values[0])
s_val_min = self.value_mapper.map_screen(values[0])
s_val_max = self.value_mapper.map_screen(values[1])
self._cached_screen_data = s_val_min, s_val_max
else:
s_val_pts = self.value_mapper.map_screen(values)
self._cached_screen_data = s_val_pts
n = len(values)
t = np.linspace(*self._cached_data_bounds, num=n)
t_screen = self.index_mapper.map_screen(t)
self._cached_screen_index = t_screen
self._screen_cache_valid = True
return [self._cached_screen_index, self._cached_screen_data]
def _render(self, gc, points):
idx, val = points
if len(idx) == 0:
return
gc.save_state()
gc.set_antialias(True)
gc.clip_to_rect(self.x, self.y, self.width, self.height)
gc.set_stroke_color(self.color_)
gc.set_line_width(self.line_width)
#gc.set_line_width(5)
gc.begin_path()
#if len(val) == 2:
if type(val) == type(()):
starts = np.column_stack((idx, val[0]))
ends = np.column_stack((idx, val[1]))
gc.line_set(starts, ends)
else:
gc.lines(np.column_stack((idx, val)))
gc.stroke_path()
self._draw_default_axes(gc)
gc.restore_state()
if __name__ == "__main__":
ChacoTest().configure_traits()

Perspective projection and rotation in python

I've tried searching but none of the other questions seem to be like mine. I'm more or less experimenting with perspective projection and rotation in python, and have run into a snag. I'm sure my projection equations are accurate, as well as my rotation equations; however, when I run it, the rotation starts normal, but begins to swirl inwards until the vector is in the same position as the Z axis (the axis I am rotating over).
''' Imports '''
from tkinter import Tk, Canvas, TclError
from threading import Thread
from math import cos, sin, radians, ceil
from time import sleep
''' Points class '''
class pPoint:
def __init__(self, fPoint, wWC, wHC):
self.X = 0
self.Y = 0
self.Z = 0
self.xP = 0
self.yP = 0
self.fPoint = fPoint
self.wWC = wWC
self.wHC = wHC
def pProject(self):
self.xP = (self.fPoint * (self.X + self.wWC)) / (self.fPoint + self.Z)
self.yP = (self.fPoint * (self.Y + self.wHC)) / (self.fPoint + self.Z)
''' Main class '''
class Main:
def __init__(self):
''' Declarations '''
self.wWidth = 640
self.wHeight = 480
self.fPoint = 256
''' Generated declarations '''
self.wWC = self.wWidth / 2
self.wHC = self.wHeight / 2
''' Misc declarations '''
self.gWin = Tk()
self.vPoint = pPoint(self.fPoint, self.wWC, self.wHC)
self.vPoint.X = 50
self.vPoint.Y = 60
self.vPoint.Z = -25
self.vPoint.pProject()
self.ang = 0
def initWindow(self):
self.gWin.minsize(self.wWidth, self.wHeight)
self.gWin.maxsize(self.wWidth, self.wHeight)
''' Create canvas '''
self.gCan = Canvas(self.gWin, width = self.wWidth, height = self.wHeight, background = "black")
self.gCan.pack()
def setAxis(self):
''' Create axis points '''
self.pXax = pPoint(self.fPoint, self.wWC, self.wHC)
self.pXbx = pPoint(self.fPoint, self.wWC, self.wHC)
self.pYax = pPoint(self.fPoint, self.wWC, self.wHC)
self.pYbx = pPoint(self.fPoint, self.wWC, self.wHC)
self.pZax = pPoint(self.fPoint, self.wWC, self.wHC)
self.pZbx = pPoint(self.fPoint, self.wWC, self.wHC)
''' Set axis points '''
self.pXax.X = -(self.wWC)
self.pXax.Y = 0
self.pXax.Z = 1
self.pXbx.X = self.wWC
self.pXbx.Y = 0
self.pXbx.Z = 1
self.pYax.X = 0
self.pYax.Y = -(self.wHC)
self.pYax.Z = 1
self.pYbx.X = 0
self.pYbx.Y = self.wHC
self.pYbx.Z = 1
self.pZax.X = 0
self.pZax.Y = 0
self.pZax.Z = -(self.fPoint) / 2
self.pZbx.X = 0
self.pZbx.Y = 0
self.pZbx.Z = (self.fPoint * self.wWC) - self.fPoint
def projAxis(self):
''' Project the axis '''
self.pXax.pProject()
self.pXbx.pProject()
self.pYax.pProject()
self.pYbx.pProject()
self.pZax.pProject()
self.pZbx.pProject()
def drawAxis(self):
''' Draw the axis '''
self.gCan.create_line(self.pXax.xP, self.pXax.yP, self.pXbx.xP, self.pXbx.yP, fill = "white")
self.gCan.create_line(self.pYax.xP, self.pYax.yP, self.pYbx.xP, self.pYbx.yP, fill = "white")
self.gCan.create_line(self.pZax.xP, self.pZax.yP, self.pZbx.xP, self.pZbx.yP, fill = "white")
def prePaint(self):
self.vA = self.gCan.create_line(self.wWC, self.wHC, self.vPoint.xP, self.vPoint.yP, fill = "red")
def paintCanvas(self):
try:
while True:
self.ang += 1
if self.ang >= 361:
self.ang = 0
self.vPoint.X = (self.vPoint.X * cos(radians(self.ang))) - (self.vPoint.Y * sin(radians(self.ang)))
self.vPoint.Y = (self.vPoint.X * sin(radians(self.ang))) + (self.vPoint.Y * cos(radians(self.ang)))
self.vPoint.pProject()
self.gCan.coords(self.vA, self.wWC, self.wHC, self.vPoint.xP, self.vPoint.yP)
self.gWin.update_idletasks()
self.gWin.update()
sleep(0.1)
except TclError:
pass
mMain = Main()
mMain.initWindow()
mMain.setAxis()
mMain.projAxis()
mMain.drawAxis()
mMain.prePaint()
mMain.paintCanvas()
Thank you for any input :)
EDIT: Sorry, I just realized I forgot to put my question. I just want to know why it is gravitating inward, and not just rotating "normally"?
This section is wrong:
self.ang += 1
if self.ang >= 361:
self.ang = 0
self.vPoint.X = (self.vPoint.X * cos(radians(self.ang))
- self.vPoint.Y * sin(radians(self.ang)))
self.vPoint.Y = (self.vPoint.X * sin(radians(self.ang))
+ self.vPoint.Y * cos(radians(self.ang)))
self.vPoint.pProject()
For two reasons:
self.ang will take integers in the open range [0 - 360], which means the angle 360 (== 0) is repeated.
In each iteration, you rotate the point from the previous iteration by the angle. As a result, your first frame is at 1 degree, your second at 1+2 = 3, the third at 1 + 2 + 3... You should either be:
rotating the point from the previous iteration by a constant angle each time (1°). This suffers from the problem mentioned in my comment
rotating the initial point by the current angle of rotation each time
Not actualy related to your problem, but I strongly suggest you to use Numpy to perform geometric transformations, specially if it involves 3D points.
Below, I post a sample snippet, I hope it helps:
import numpy
from math import radians, cos, sin
## suppose you have a Nx3 cloudpoint (it might even be a single row of x,y,z coordinates)
cloudpoint = give_me_a_cloudpoint()
## this will be a rotation around Y azis:
yrot = radians(some_angle_in_degrees)
## let's create a rotation matrix using a numpy array
yrotmatrix = numpy.array([[cos(yrot), 0, -sin(yrot)],
[0, 1, 0],
[sin(yrot), 0, cos(yrot)]], dtype=float)
## apply the rotation via dot multiplication
rotatedcloud = numpy.dot(yrotmatrix, pointcloud.T).T # .T means transposition

Categories