Why cannot see error bar cap , when I move the position using ax.axes[0].lines[2].set_xdata() - move

I try to move the bar and error bar to the center of tickmark. using ax.axes.patches, move bar by bar.set_x(current_pos+(current_width)), and move the error bar by ax.axes.lines[0].set_xdata(current_pos+(current_width*1.5)).
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
%matplotlib inline
df = sns.load_dataset("penguins")
ax = sns.barplot(data=df, x="island", y="body_mass_g", ci = "sd", capsize = 0.2, hue = "species")
for i, bar in enumerate(ax.axes.patches):
# move the missing to the centre
current_width = bar.get_width()
current_pos = bar.get_x()
if i == 0:
#move bar
bar.set_x(current_pos+(current_width))
#change color and move error bar
ax.axes.lines[0].set_color("red")
ax.axes.lines[0].set_xdata(current_pos+(current_width*1.5))
#change color of error bar upper cap
ax.axes.lines[2].set_color("red")
plt.show();
when I change the position of the error bar upper cap (ax.axes.lines2.set_xdata(current_pos+(current_width*1.5))), the cap is gone.
How can I set error cap visible or move the error cap?
ax = sns.barplot(data=df, x="island", y="body_mass_g", ci = "sd", capsize = 0.2, hue = "species")
for i, bar in enumerate(ax.axes.patches):
# move the missing to the centre
current_width = bar.get_width()
current_pos = bar.get_x()
if i == 0:
#move bar
bar.set_x(current_pos+(current_width))
#change color and move error bar
ax.axes.lines[0].set_color("red")
ax.axes.lines[0].set_xdata(current_pos+(current_width*1.5))
#change color of error bar upper cap
ax.axes.lines[2].set_color("red")
#change position of error bar upper cap
ax.axes.lines[2].set_xdata(current_pos+(current_width*1.5))
plt.show();

The errorbar lines have two coordinates for start and end. For the two horizontal lines (line1 and line[2]), you should be using the get_xdata() and then adding the offset. In the below code, I have updated the the same by reading the data into a temp variable called newline, then updating it and finally updating it back using set_xdata(). I think you might want to do it for the vertical line (line[0]) as well, but since it works without this, perhaps you can leave it as is. Below is the code...
df = sns.load_dataset("penguins")
ax = sns.barplot(data=df, x="island", y="body_mass_g", ci = "sd", capsize = 0.2, hue = "species")
for i, bar in enumerate(ax.axes.patches):
# move the missing to the centre
current_width = bar.get_width()
current_pos = bar.get_x()
if i == 0:
#move bar
bar.set_x(current_pos+(current_width))
#change color and move error bar
ax.axes.lines[0].set_color("red")
ax.axes.lines[0].set_xdata(current_pos+(current_width*1.5))
#change color of error bar upper cap
ax.axes.lines[1].set_color("red")
newline=ax.axes.lines[1].get_xdata() ## Read the xdata
newline[0] += current_width ## Add offset to first parameter
newline[1] += current_width ## Add offset to second parameter
ax.axes.lines[1].set_xdata(newline) ## Set data of the line
ax.axes.lines[2].set_color("red") ##Do the same for second line
newline=ax.axes.lines[2].get_xdata()
newline[0] += current_width
newline[1] += current_width
ax.axes.lines[2].set_xdata(newline)
plt.show();

Related

Open a video with alpha and the webcam input in the same window, using pyQT

Open a video with alpha and the input from the webcam in the same window, using pyQT I have a video with alpha channel, it is in .flv or .mov format.
The code below can open this video and control its execution:
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QHBoxLayout, QVBoxLayout
from PyQt5.QtWidgets import QSizePolicy, QFileDialog, QLabel, QSlider, QStyle
from PyQt5.QtGui import QIcon, QPalette, QPixmap
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent
from PyQt5.QtMultimediaWidgets import QVideoWidget
from PyQt5.QtCore import Qt, QUrl
import sys
class Window(QWidget):
def __init__(self, windowsize):
super().__init__()
self.setWindowTitle("Rodolfo Nogueira Player")
# Posição e tamanho da janela
self.setGeometry(350, 100, 700, 500)
self.setWindowIcon(QIcon('player.png'))
self.windowsize = windowsize
p = self.palette()
p.setColor(QPalette.Window, Qt.black)
self.setPalette(p)
self.init_ui()
self.show()
def init_ui(self):
# Criação do objeto Media Player
self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface)
#Criação do objeto videoObject
videowidget = QVideoWidget()
#Criação do botão de abrir
openBtn = QPushButton('Abrir Video')
openBtn.clicked.connect(self.open_file)
# Criação do botão de play
self.playBtn = QPushButton()
self.playBtn.setEnabled(False)
self.playBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
self.playBtn.clicked.connect(self.play_video)
# Criação do slider
self.slider =QSlider(Qt.Horizontal)
self.slider.setRange(0, 0)
self.slider.sliderMoved.connect(self.set_position)
# Criando um rótulo - Receberá o erro, caso ocorra
self.label = QLabel()
# Definindo a política de tamanho
self.label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum)
# Criando um Layout hBox
hboxLayout = QHBoxLayout()
hboxLayout.setContentsMargins(0, 0, 0, 0)
# Configurando os elementos do bBox
hboxLayout.addWidget(openBtn)
hboxLayout.addWidget(self.playBtn)
hboxLayout.addWidget(self.slider)
# Criando um vBox layout
vboxLayout = QVBoxLayout()
vboxLayout.addWidget(videowidget)
vboxLayout.addLayout(hboxLayout)
vboxLayout.addWidget(self.label) # Receberá o erro, caso ocorra
self.setLayout(vboxLayout)
self.mediaPlayer.setVideoOutput(videowidget)
# Sinais do mediaPlayer
self.mediaPlayer.stateChanged.connect(self.media_state_change)
self.mediaPlayer.positionChanged.connect(self.position_changed)
self.mediaPlayer.durationChanged.connect(self.duration_changed)
def open_file(self):
filename, _ = QFileDialog.getOpenFileName(self, "Open Video")
filename = str(filename).replace('/', '\\')
if filename != '':
self.mediaPlayer.setMedia(QMediaContent(QUrl.fromLocalFile(filename)))
self.playBtn.setEnabled(True)
def play_video(self):
print('tentando o Play')
if self.mediaPlayer.state() == QMediaPlayer.PlayingState:
print('tentando o Play')
self.mediaPlayer.pause()
else:
print('tentando o Pause')
self.mediaPlayer.play()
def media_state_change(self, state):
if self.mediaPlayer.state() == QMediaPlayer.PlayingState:
self.playBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaPause))
else:
self.playBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
def position_changed(self, position):
self.slider.setValue(position)
def duration_changed(self, duration):
self.slider.setRange(0, duration)
def set_position(self, position):
self.mediaPlayer.setPosition(position)
def handre_errors(self):
self.playBtn.setEnabled(False)
self.label.setText("Erro: " + self.mediaPlayer.errorString())
app = QApplication(sys.argv)
screensize = app.desktop().availableGeometry().size()
window = Window(screensize)
sys.exit(app.exec_())
Now I want to open the input of a webcam in the background, in the same window. Preferably when I play the video I want the cam input to open in the background.
I even got this code that uses pyQT to open the cam, but I can't get the two together in the same window:
class MainWindow(QMainWindow):
# constructor
def __init__(self):
super().__init__()
# setting geometry
self.setGeometry(100, 100,
800, 600)
# setting style sheet
self.setStyleSheet("background : lightgrey;")
# getting available cameras
self.available_cameras = QCameraInfo.availableCameras()
# if no camera found
if not self.available_cameras:
# exit the code
sys.exit()
# creating a status bar
self.status = QStatusBar()
# setting style sheet to the status bar
self.status.setStyleSheet("background : white;")
# adding status bar to the main window
self.setStatusBar(self.status)
# path to save
self.save_path = ""
# creating a QCameraViewfinder object
self.viewfinder = QCameraViewfinder()
# showing this viewfinder
self.viewfinder.show()
# making it central widget of main window
self.setCentralWidget(self.viewfinder)
# Set the default camera.
self.select_camera(0)
# creating a tool bar
toolbar = QToolBar("Camera Tool Bar")
# adding tool bar to main window
self.addToolBar(toolbar)
# creating a photo action to take photo
click_action = QAction("Click photo", self)
# adding status tip to the photo action
click_action.setStatusTip("This will capture picture")
# adding tool tip
click_action.setToolTip("Capture picture")
# adding action to it
# calling take_photo method
click_action.triggered.connect(self.click_photo)
# adding this to the tool bar
toolbar.addAction(click_action)
# similarly creating action for changing save folder
change_folder_action = QAction("Change save location",
self)
# adding status tip
change_folder_action.setStatusTip("Change folder where picture will be saved saved.")
# adding tool tip to it
change_folder_action.setToolTip("Change save location")
# setting calling method to the change folder action
# when triggered signal is emitted
change_folder_action.triggered.connect(self.change_folder)
# adding this to the tool bar
toolbar.addAction(change_folder_action)
# creating a combo box for selecting camera
camera_selector = QComboBox()
# adding status tip to it
camera_selector.setStatusTip("Choose camera to take pictures")
# adding tool tip to it
camera_selector.setToolTip("Select Camera")
camera_selector.setToolTipDuration(2500)
# adding items to the combo box
camera_selector.addItems([camera.description()
for camera in self.available_cameras])
# adding action to the combo box
# calling the select camera method
camera_selector.currentIndexChanged.connect(self.select_camera)
# adding this to tool bar
toolbar.addWidget(camera_selector)
# setting tool bar stylesheet
toolbar.setStyleSheet("background : white;")
# setting window title
self.setWindowTitle("PyQt5 Cam")
# showing the main window
self.show()
# method to select camera
def select_camera(self, i):
# getting the selected camera
self.camera = QCamera(self.available_cameras[i])
# setting view finder to the camera
self.camera.setViewfinder(self.viewfinder)
# setting capture mode to the camera
self.camera.setCaptureMode(QCamera.CaptureStillImage)
# if any error occur show the alert
self.camera.error.connect(lambda: self.alert(self.camera.errorString()))
# start the camera
self.camera.start()
# creating a QCameraImageCapture object
self.capture = QCameraImageCapture(self.camera)
# showing alert if error occur
self.capture.error.connect(lambda error_msg, error,
msg: self.alert(msg))
# when image captured showing message
self.capture.imageCaptured.connect(lambda d,
i: self.status.showMessage("Image captured : "
+ str(self.save_seq)))
# getting current camera name
self.current_camera_name = self.available_cameras[i].description()
# initial save sequence
self.save_seq = 0
# method to take photo
def click_photo(self):
# time stamp
timestamp = time.strftime("%d-%b-%Y-%H_%M_%S")
# capture the image and save it on the save path
self.capture.capture(os.path.join(self.save_path,
"%s-%04d-%s.jpg" % (
self.current_camera_name,
self.save_seq,
timestamp
)))
# increment the sequence
self.save_seq += 1
# change folder method
def change_folder(self):
# open the dialog to select path
path = QFileDialog.getExistingDirectory(self,
"Picture Location", "")
# if path is selected
if path:
# update the path
self.save_path = path
# update the sequence
self.save_seq = 0
# method for alerts
def alert(self, msg):
# error message
error = QErrorMessage(self)
# setting text to the error message
error.showMessage(msg)
Can anyone give me a hint on how to put these two codes together?

Display text on another process' screen (overlay)

I have a question, its more an OS-based one.
I'm playing a video game and I want to be able to put a textual timer ontop of the game's screen as if it was a part of the game itself.
Now, I can write a program in any language that displays a TextBox with a timer on the screen, but if I run it, the game's process (lets call it game.exe) "loses" its focus and I get my TextBox focused and interactive by the OS.
Is there any option to display that text "ontop" of the game.exe that comes from an entire different process? as if there were "layers" to the screen. Also, this text shouldn't be intractable, clickable or make the game.exe process lose its focus.
Here's a very simple example I drew:
Thanks a lot!
Solved this using a window trick with python and tkinter with some windows api stuff.
The trick is to create a transparent non-clickable window and keep it always on top.
I've basically combined this answer with a bunch of simpler stuff like removing window's border and set to auto fullscreen.
from tkinter import *
import time
import win32gui
import win32api
from win32api import GetSystemMetrics
# WIDTH = 500
# HEIGHT = 500
WIDTH = GetSystemMetrics(0)
HEIGHT = GetSystemMetrics(1)
LINEWIDTH = 1
TRANSCOLOUR = 'gray'
title = 'Virtual whiteboard'
global old
old = ()
global HWND_t
HWND_t = 0
tk = Tk()
# tk.title(title)
tk.lift()
tk.wm_attributes("-topmost", True)
tk.wm_attributes("-transparentcolor", TRANSCOLOUR)
tk.attributes('-fullscreen', True)
state_left = win32api.GetKeyState(0x01) # Left button down = 0 or 1. Button up = -127 or -128
canvas = Canvas(tk, width=WIDTH, height=HEIGHT, highlightthickness=0)
canvas.pack()
canvas.config(cursor='tcross')
canvas.create_rectangle(0, 0, WIDTH, HEIGHT, fill=TRANSCOLOUR, outline=TRANSCOLOUR)
canvas.create_text(WIDTH/2,HEIGHT/2,fill="white",font="Arial 20", text="TEXT GOES HERE")
def putOnTop(event):
event.widget.unbind('<Visibility>')
event.widget.update()
event.widget.lift()
event.widget.bind('<Visibility>', putOnTop)
def drawline(data):
global old
if old !=():
canvas.create_line(old[0], old[1], data[0], data[1], width=LINEWIDTH)
old = (data[0], data[1])
def enumHandler(hwnd, lParam):
global HWND_t
if win32gui.IsWindowVisible(hwnd):
if title in win32gui.GetWindowText(hwnd):
HWND_t = hwnd
win32gui.EnumWindows(enumHandler, None)
tk.bind('<Visibility>', putOnTop)
tk.focus()
running = 1
while running == 1:
try:
tk.update()
time.sleep(0.01)
if HWND_t != 0:
windowborder = win32gui.GetWindowRect(HWND_t)
cur_pos = win32api.GetCursorPos()
state_left_new = win32api.GetKeyState(0x01)
if state_left_new != state_left:
if windowborder[0] < cur_pos[0] and windowborder[2] > cur_pos[0] and windowborder[1] < cur_pos[1] and windowborder[3] > cur_pos[1]:
drawline((cur_pos[0] - windowborder[0] - 5, cur_pos[1] - windowborder[1] - 30))
else:
old = ()
except Exception as e:
running = 0
print("error %r" % (e))

GUI plot refreshing incorrectly on wxpython panel

Ok, so I've been tasked with creating a VERY simple GUI at work (I'm an intern). The task is to eventually connect to a machine and process real data, but right now I'm working on randomly generated sine data with noise. I've chose to work in Python 3.0, and use wxpython to create my GUI components.
As I want everything to appear on the same window, I'm using panels (hence wx.lib.plot.PlotCanvas rather than something like matplotlib.pyplot)
The problem that I have is that over time, the plot seems to 'expand' off of the panel. This is temporarily solved when I manually resize the window, but resumes again immediately after (you need to run the code to see what I mean).
Expansion over time in panel
Another problem (that has bugged me since I have started writing the code) is that sometimes when I resize the window (manually) or minimize it and then maximize it again, the timer randomly starts and stops.
I have tried all sorts of things (changing padding in sizers, extra arguments, changing time between refreshes GetBestSize()) but I believe that I simply don't understand wxpython well enough to identify where the problem is
I would really appreciate any help you can shed on either of these problems (I don't know, they might even be linked to each other).
FYI: I am not an experienced coder, and my code is not finished (I have more functions to code, but I feel like I should resolve this first). I have constructed this code by looking at different techniques from various tutorials and websites like stackoverflow, so I know it's not formatted well and could definitely be made more efficient. Also, I have removed some parts just to be safe about confidentiality - nothing important, just strings in messages.
PS: If you do have an easier way to do the whole plot/update thing that doesn't have this problem (preferably still in wx) I would be thrilled to hear that as well
And here's my code:
EDIT: Solved the expanding problem by using self.p2.SetSize((W+0,L+0)) instead of (self.p2.GetBestSize())
EDIT: Made transitions much smoother by just regenerating data and redrawing it on existing canvas in the evt_timer function (instead of recreating the whole canvas, which gave a blink-y appearance if you know what I mean)
import wx
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import leastsq
import wx.lib.plot as plot
import time
import os
wildcard = "Text File (*.txt)|*.txt|"\
"Picture (*.png)|*.png|"\
"All files (*.*)|*.*"#This wildcard shows the options for file endings in the "SAVE" tab - see OnSave(self,event)
wildcard2 = "Picture (*.png)|*.png|"\
"Text File (*.txt)|*.txt|"\
"All files (*.*)|*.*"
class PlotCanvas(plot.PlotCanvas):
def __init__(self,parent,id,size,accepted):
"""
This randomly generates sine data (with noise) and plots it to a panel.
Incorporated as a separate class instead of instatiating it as a plot.PlotCanvas object
to overcome an issue of the size of the plot in the panel.
"""
plot.PlotCanvas.__init__(self,parent,id,style=wx.BORDER_SUNKEN,size = size)
N = 100 # number of data points
self.t = np.linspace(0, 4*np.pi, N)
f = 1.15247 # Optional!! Advised not to use
self.data = 3.0*np.sin(f*self.t+0.001) + 0.5 + np.random.randn(N) # create artificial data with noise
guess_mean = np.mean(self.data)
guess_phase = 0
guess_freq = 1
guess_amp = 1
optimize_func = lambda x: x[0]*np.sin(x[1]*self.t+x[2]) + x[3] - self.data
est_amp, est_freq, est_phase, est_mean = leastsq(optimize_func, [guess_amp, guess_freq, guess_phase, guess_mean])[0]
fine_t = np.arange(0,max(self.t),0.1)
data_fit=est_amp*np.sin(est_freq*fine_t+est_phase)+est_mean
multiplier = 1
dataset1 = [(x,[d for d in self.data][[td for td in self.t].index(x)])for x in [td for td in self.t]]
fitdata1 = [(x,[df for df in data_fit][[tf for tf in fine_t].index(x)]) for x in [tf for tf in fine_t]]
dataset =[(x,y*multiplier) for (x,y) in dataset1]
fitdata = [(x,y*multiplier) for (x,y) in fitdata1]
self.data = dataset
self.data2 = fitdata
line = plot.PolyLine(self.data,legend = 'random',colour = 'light blue', width =2)
line2 = plot.PolyLine(self.data2,legend = 'sineline',colour ='black',width =2)
a = []
if "D" in accepted:
a.append(line)
if "S" in accepted:
a.append(line2)
if "G" in accepted:
pass
if "L" in accepted:
pass
gc = plot.PlotGraphics(a,'Line Graph','X','Y')
xmin = self.t[0]-0.01*(self.t[-1]-self.t[0])
xmax = self.t[-1]+0.01*(self.t[-1]-self.t[0])
self.Draw(gc,xAxis=(xmin,xmax),yAxis=(min([x[1] for x in dataset])-0.01*(max([x[1] for x in dataset])-min([x[1] for x in dataset])),
max([x[1] for x in dataset])+0.01*(max([x[1] for x in dataset])-min([x[1] for x in dataset]))))
#self.showLegend = True
#self.enableZoom = True
def Dialog(self, parent, message, c):# Will be used to notify the user of errors/processes
if c == "W":
caption = "Warning!"
dlg = wx.MessageDialog(parent, message, caption, wx.OK | wx.ICON_WARNING)
elif c == "I":
caption = "Information"
dlg = wx.MessageDialog(parent, message, caption, wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy()#Destroys dialog on close
class Frame(wx.Frame):
"""
This is the main class. In it, we declare the separate panels, canvas, menubar, buttons and sizers.
"""
def __init__(self,parent,id,title):
wx.Frame.__init__(self, parent, id, title, wx.DefaultPosition)
self.CurrentDirectory = os.getcwd()
self.timer=wx.Timer(self)#Instantiating the timer
self.count=0
self.Bind(wx.EVT_TIMER,self.evt_timer)#Binding it to itself so that it is always triggered
self.Bind(wx.EVT_PAINT,self.paint)
menubar = wx.MenuBar()
fileMenu = wx.Menu() #Creating the Menubar at the top
#Creating 3 menus: fileMenu,fit,and help
save = wx.Menu()
z = wx.MenuItem(save,wx.ID_ANY,'Save Raw Data\tCtrl+D')
self.Bind(wx.EVT_MENU,self.OnSave,z)
save.Append(z)
z= wx.MenuItem(save,wx.ID_ANY,'Save Image\tCtrl+I')
self.Bind(wx.EVT_MENU,self.OnSaveImage,z)
save.Append(z)
fileMenu.AppendSubMenu(save,'&Save')
fileMenu.AppendSeparator()
z = wx.MenuItem(fileMenu, wx.ID_EXIT, '&Quit\tCtrl+W')
self.Bind(wx.EVT_MENU, self.OnQuit, z)
fileMenu.Append(z)
fit = wx.Menu()#Making a check menu
self.gaussian = fit.Append(wx.ID_ANY,'Gaussian',kind = wx.ITEM_CHECK)
#self.Bind(wx.EVT_MENU,self.ToggleGaussian,self.gaussian)
fit.Check(self.gaussian.GetId(),False)
self.sine = fit.Append(wx.ID_ANY,'Sine',kind = wx.ITEM_CHECK)
#self.Bind(wx.EVT_MENU,self.ToggleSine,self.sine)
fit.Check(self.sine.GetId(),False)
self.linear = fit.Append(wx.ID_ANY,'Linear',kind=wx.ITEM_CHECK)
#self.Bind(wx.EVT_MENU,self.ToggleLinear,self.linear)
fit.Check(self.linear.GetId(),False)
help = wx.Menu()
z = wx.MenuItem(help,wx.ID_ANY,'&About\tCtrl+H')
self.Bind(wx.EVT_MENU,self.OnHelp,z)
help.Append(z)
menubar.Append(fileMenu, '&File')
menubar.Append(fit, '&Fit')
menubar.Append(help, '&Help')#adding menus to menubar
self.SetMenuBar(menubar)#formatting the frame with menubar
self.sp = wx.SplitterWindow(self)#Splitting the window into 2 panels
self.p1 = wx.Panel(self.sp,style = wx.SUNKEN_BORDER)#For buttons and user events
self.p2 = wx.Panel(self.sp,style = wx.SUNKEN_BORDER)#For display of the plot
self.sp.SplitVertically(self.p1,self.p2,300)
sizer = wx.GridBagSizer(3, 3)#Versatile sizer for layout of first panel self.p1
bitmappath = self.CurrentDirectory + "\\BITMAPS"
bmp = wx.Bitmap(bitmappath+"\\SAVE.BMP",wx.BITMAP_TYPE_BMP)
self.saveBtn = wx.BitmapButton(self.p1,wx.ID_ANY,bitmap = bmp,size =(bmp.GetWidth()+10,bmp.GetHeight()+10))
self.Bind(wx.EVT_BUTTON,self.OnSave,self.saveBtn)
sizer.Add(self.saveBtn, (0, 0), wx.DefaultSpan, wx.ALL,5)
bmp = wx.Bitmap(bitmappath +"\\START.BMP",wx.BITMAP_TYPE_BMP)
self.startBtn = wx.BitmapButton(self.p1,-1,bitmap = bmp,size =(bmp.GetWidth()+10,bmp.GetHeight()+10))# A button that starts and stops the plotting
self.startBtn.startval = "START"
self.Bind(wx.EVT_BUTTON,self.paint,self.startBtn)
sizer.Add(self.startBtn, (0, 1), wx.DefaultSpan,wx.ALL,5)
sizer1 = wx.BoxSizer(wx.VERTICAL)
W,L = self.p2.GetSize()
self.p2.canvas = PlotCanvas(self.p2,wx.ID_ANY,(W,L),["D"])
sizer1.Add(self.p2.canvas,1,wx.ALL,0,0)
self.p2.SetSizerAndFit(sizer1)
self.p1.SetSizerAndFit(sizer)
self.p2.SetSizerAndFit(sizer1)
self.p2.SetSize(W,L)
self.Maximize(True)
self.Centre()
self.Show()
############### event methods ###########
def paint(self,event):
"""
Updates the canvas based on the value of the startbtn(not the image). Bound to self.timer.
"""
bitmappath = self.CurrentDirectory + "\\BITMAPS"
if self.startBtn.startval == "START":
self.timer.Start(1)# increase the value for more time
bmp = wx.Bitmap(bitmappath + "\\STOP.BMP",wx.BITMAP_TYPE_BMP)
self.startBtn.SetBitmap(bmp)
self.startBtn.startval = "STOP"
elif self.startBtn.startval == "STOP":
self.timer.Stop()
bmp = wx.Bitmap(bitmappath+ "\\START.BMP",wx.BITMAP_TYPE_BMP)
self.startBtn.SetBitmap(bmp)
self.startBtn.startval = "START"
def evt_timer(self,event):
self.count +=1
if self.count== 10:# By increasing count (or the number in self.timer.Start()) you can increase the interval between updates
#self.p2.canvas.Clear()
sizer1 = wx.BoxSizer(wx.VERTICAL)
W,L = self.p2.GetSize()
a = ["D"]
if self.sine.IsChecked():
a.append("S")
elif self.linear.IsChecked():
a.append("L")
elif self.gaussian.IsChecked():
a.append("G")
self.p2.canvas = PlotCanvas(self.p2,wx.ID_ANY,(W,L),a)
sizer1.Add(self.p2.canvas,1,wx.ALL,0,0)
self.p2.SetSizerAndFit(sizer1)
self.p2.SetSize(self.p2.GetBestSize())
self.count=0 # reset the count
def Dialog(self, parent, message, c):# Will be used to notify the user of errors/processes
if c == "W":
caption = "Warning!"
dlg = wx.MessageDialog(parent, message, caption, wx.OK | wx.ICON_WARNING)
elif c == "I":
caption = "Information"
dlg = wx.MessageDialog(parent, message, caption, wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy()#Destroys dialog on close
def OnSave(self,event):#Triggered by menubar and button
try:
rawdata = self.p2.canvas.data
raw_X =[x[0] for x in rawdata]
raw_Y =[x[1] for x in rawdata]
dlg = wx.FileDialog(#Code for this from http://www.blog.pythonlibrary.org
self, message="Save file as ...",
defaultDir=self.CurrentDirectory,
defaultFile=str(time.ctime()), wildcard=wildcard, style=wx.FD_SAVE
)
if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath()
dlg.Destroy()
f = open(path+".txt","w+")
for i in range(len(raw_X)):
f.write(str(raw_X[i])+"\t"+str(raw_Y[i])+"\n")
f.close()
self.Dialog(None,"File successfully saved","I")
except UnboundLocalError:#Catch error when user closes save window without selecting any directory or filename
pass
def OnSaveImage(self,event):
try:
rawdata = self.p2.canvas.data
raw_X = [x[0] for x in rawdata]
raw_Y = [x[1] for x in rawdata]
dlg = wx.FileDialog(
self, message="Save file as ...",
defaultDir=self.CurrentDirectory,
defaultFile=str(time.ctime()), wildcard=wildcard2, style=wx.FD_SAVE
)
if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath()
dlg.Destroy()
fig1 = plt.figure()
plt.plot(raw_X,raw_Y)
plt.title("Raw Data")
fig1.savefig(path+".png")
self.Dialog(None,"File successfully saved","I")
except UnboundLocalError:
pass
def OnMultiply(self,e):
try:
factor = self.x.GetValue()
factor = float(factor)
self.IntegrationTime = factor
except ValueError as e:
self.Dialog(None,str(e),"W")
def OnQuit(self, e):
self.Close()
def OnHelp(self,e):
self.Dialog(None,"N/A","I")
def ToggleSine(self,e):
pass
def ToggleLinear(self,e):
self.Dialog(None,"Not added yet","W")
def ToggleGaussian(self,e):
self.Dialog(None,"Not added yet","W")
if __name__ =="__main__":
app=wx.App()
Frame(None,-1,"N/A")
app.MainLoop()

Python GUI (tkinter.ttk) application slow

I've got (working) application done with ttk. It uses self-created module for showing a comport-related controls and a canvas which draws a few graphs on it. When I crate an instance of my object, it starts a thread in which processes serial input and appends this to a list (one list per graph). When I have 3-6 graphs, the application gets noticeably slow. It also has got a few bugs, but I will adress them when I'm done with the general concept.
Things that may help you help me:
comport is an instance of a self-written object that derives from
LabelFrame and Serial.Serial
coordinates for graphs are stored in a dictionary of lists:
self.graphs = {} self.graphs['name1']=[] number of coordinates stored
is up to the width of canvas, so about 1000-2000 per graph. Have six
graphs - please multiply by 6
With every new coordinate arriving I pop(0) from the list and
append() the new coordinate
I forgot, I also store timing of each new set of coordinates arriving
in a separate list
I use a preiodic call function to process the lists: self.after(100,
func=self.periodicCall)Thus every 100ms I delete(ALL) from the canvas
and I draw every graph with theset of lines. So if I have 1000 coords
in 6 graps, I draw 6000 small lines
Plus some service info of course such as a few rulers
So I guess the idea is clear. I want to figure out what would be the better approach. I'm just a started in python and in programming as well, so I'm asking for your excuse for the code that I'm going to post and for the pain in your eyes it's gonna cause. I don't have any programming style and I want to fix it. At least a bit. So any other comments on anything you'll se in the code are welcome.
#-------------------------------------------------------------------------------
# Name: dataVisualizer
# Purpose:
#
# Author: dccharacter
#
# Created: 23.03.2012
# Copyright: (c) dccharacter 2012
# Licence: <your licence>
#-------------------------------------------------------------------------------
#!/usr/bin/env python
from tkinter import *
from tkinter.ttk import *
from robowidgets.serialPortGui import *
import threading
import re
import atexit
import random
from datetime import datetime
import time
class dataVisualizer(LabelFrame):
def __init__(self, master, comport , cnf={}, **kw):
self.master = master
self.comport = comport
LabelFrame.__init__(self, *cnf, **kw)
self.messageVar = StringVar()
Label(self, text="Message format regexp:").pack()
self.messagePattern = Entry(self, width = 20, text = 234234, textvariable = self.messageVar);
self.messageVar.set(r'(-*\d+),(-*\d+),(-*\d+),(-*\d+),(-*\d+),(-*\d+)')
self.messagePattern.pack()
Button(self, text = "Pause", command = self.pause).pack()
self.pauseFlag = TRUE
self.canvWidth, self.canvHeight = 1000, 700
self.density = 1 ##width of pixel - the bigger, the wider graph
self.numOfDots = self.canvWidth//self.density
self.graphs = {}
self.graphs['name1']=[]
self.graphs['name2']=[]
self.graphs['name3']=[]
self.graphs['name4']=[]
self.graphs['name5']=[]
self.graphs['name6']=[]
self.timings = []
self.zeroTiming = datetime.now()
self.colors = ['red', 'blue', 'green', 'orange', 'violet', 'black', 'cyan']
self.canv = Canvas(self, width = self.canvWidth, height = self.canvHeight)
self.canv.pack()
self.thread = threading.Thread(target = self.workerThread)
self.thread.start()
self.serialData = []
self.periodicCall()
def pause(self):
self.pauseFlag = ~self.pauseFlag
def redraw(self):
self.canv.delete(ALL)
colorIndex = 0
for graphName in self.graphs:
runningAverage = sum(self.graphs[graphName][-10:])//10
text = str(runningAverage)
self.canv.create_text(self.canvWidth-60, 20*(colorIndex+1), text = text,
fill = self.colors[colorIndex], anchor = W)
prev_xxx, prev_yyy = 0, 0
for yyy in self.graphs[graphName]:
self.canv.create_line(prev_xxx, prev_yyy, prev_xxx+self.density, self.canvHeight//2 - yyy,
width = 1.4, fill = self.colors[colorIndex])
prev_xxx, prev_yyy = prev_xxx+self.density, self.canvHeight//2 - yyy
colorIndex = colorIndex + 1
self.drawMesh()
def drawMesh(self):
self.canv.create_rectangle(3, 3, self.canvWidth,
self.canvHeight, outline = 'black', width = 2)
self.canv.create_line(0, self.canvHeight/2, self.canvWidth,
self.canvHeight/2, fill="black", width = 1)
mouseX = self.canv.winfo_pointerx() - self.canv.winfo_rootx()
mouseY = self.canv.winfo_pointery() - self.canv.winfo_rooty()
if mouseY < 60: aaa = -1
else: aaa = 1
if mouseX > self.canvWidth - 200 : bbb = -12
else: bbb = 1
try:
self.canv.create_rectangle(mouseX + 10*bbb - 5, mouseY - 20*aaa +10,
mouseX + 10*bbb + 115, mouseY - 20*aaa - 30, outline = "black",
fill = "red")
self.canv.create_text(mouseX + 10*bbb, mouseY - 40*aaa,
text = "t="+str(self.timings[mouseX//self.density]),
anchor = W)
self.canv.create_text(mouseX + 10*bbb, mouseY - 20*aaa,
text = "value="+str(self.canvHeight//2 - mouseY),
anchor = W)
except IndexError:
pass
self.canv.create_line(mouseX, 0, mouseX,
self.canvHeight, fill="blue", dash = [4, 1, 2, 1], width = 1)
self.canv.create_line(0, mouseY, self.canvWidth,
mouseY, fill="blue", dash = [4, 1, 2, 1], width = 1)
def periodicCall(self):
self.redraw()
self.after(100, func=self.periodicCall)
def workerThread(self):
while (1):
try:
if self.comport.isOpen() and (self.pauseFlag == TRUE):
comLine = self.comport.readline()
if len(self.timings) == self.numOfDots:
self.timings.pop(0)
td = datetime.now() - self.zeroTiming
## b'271;-3:-50\r\n'
parsedLine = re.search(self.messagePattern.get(), str(comLine))
index = 1
if parsedLine:
self.timings.append(td)
for graphName in self.graphs:
if len(self.graphs[graphName]) == self.numOfDots:
self.graphs[graphName].pop(0)
try:
self.graphs[graphName].append(int(parsedLine.group(index)))
except IndexError:
self.graphs[graphName].append(0)
index = index + 1
else:
self.comport.flush();
time.sleep(1)
except TclError:
self.thread._stop()
def main():
root = Tk()
mainWindow = Frame(root)
mainWindow.pack()
port = comPortWidget(mainWindow)
port.pack()
dv = dataVisualizer(mainWindow, port)
dv.pack()
root.mainloop()
if __name__ == '__main__':
main()
And the serial part - may lag as well (used to lag when I used to reenumerate ports evey second or so...)
#-------------------------------------------------------------------------------
# Name: robowidgets
# Purpose:
#
# Author: dccharacter
#
# Created: 10.03.2012
# Copyright: (c) dccharacter 2012
# Licence: <your licence>
#-------------------------------------------------------------------------------
#!/usr/bin/env python
import serial
from serial.tools.list_ports_windows import comports
from tkinter import *
from tkinter.ttk import *
class comPortWidget(LabelFrame, serial.Serial):
commonComPortSpeeds = ["1200", "2400", "4800", "9600", "14400", "19200", "38400", "57600", "115200"]
def __init__(self, master=None, cnf={}, **kw):
"""Construct a comPortWidget widget with the parent MASTER.
STANDARD OPTIONS
borderwidth, cursor, font, foreground,
highlightbackground, highlightcolor,
highlightthickness, padx, pady, relief,
takefocus, text, background, class, colormap, container,
height, labelanchor, labelwidget,
visual, width
WIDGET-SPECIFIC OPTIONS
"""
self.master = master
LabelFrame.__init__(self, master, text="Serial settings", *cnf, **kw)
serial.Serial.__init__(self)
self.parent = master
self.draw()
def draw(self):
self.strVarComPort = StringVar()
self.comboComport = Combobox(self,
textvariable=self.strVarComPort)
self.comboComport.grid(row=0, column=1)
self.labelComportName = Label(self, text="Com port:")
self.labelComportName.grid(row=0, column=0)
self.strVarComSpeed = StringVar()
self.comboComSpeed = Combobox(self,
textvariable=self.strVarComSpeed, values=self.commonComPortSpeeds)
self.comboComSpeed.current(len(self.commonComPortSpeeds)-1)
self.comboComSpeed.grid(row=1, column=1)
self.labelComSpeed = Label(self, text="Com speed:")
self.labelComSpeed.grid(row=1, column=0)
self.buttonComOpen = Button(self, text="Open port", command=self.openPort)
self.buttonComOpen.grid(row=0, column=2)
self.buttonComClose = Button(self, text="Close port", command=self.closePort)
self.buttonComClose.grid(row=1, column=2)
self.buttonRefreshPorts = Button(self, text="Re", width=3, command=self.refreshComPortsCombo)
##self.buttonRefreshPorts.grid(row=0, column=2)
self.refreshComPortsCombo()
def refreshComPortsCombo(self):
listComs = self.enumerateComPorts()
if not listComs:
listComs.append("No com ports found")
self.disableControls(~self.isOpen())
self.buttonComClose.configure(state=DISABLED)
else:
self.disableControls(self.isOpen())
self.buttonRefreshPorts.configure(state=NORMAL)
self.comboComport.config(values=listComs)
self.comboComport.current(len(listComs)-1)
##self.after(500, func=self.refreshComPortsCombo)
def enumerateComPorts(self):
"""
Returns the list ofcom port names in the system or an empty list if
no ports found
"""
listComs = []
for port, desc, hwid in sorted(comports()):
listComs.append(port)
return listComs
def openPort(self):
if self.isOpen():
return
self.port = self.comboComport.get()
self.baudrate = int(self.comboComSpeed.get())
self.timeout = 1
try:
self.open()
self.disableControls(self.isOpen())
except IOError:
pass
def closePort(self):
if self.isOpen():
self.flush()
self.close()
self.disableControls(self.isOpen())
def disableControls(self, isConnected):
if isConnected:
self.labelComportName.configure(state=DISABLED)
self.labelComSpeed.configure(state=DISABLED)
self.comboComport.configure(state=DISABLED)
self.comboComSpeed.configure(state=DISABLED)
self.buttonComClose.configure(state=NORMAL)
self.buttonComOpen.configure(state=DISABLED)
self.buttonRefreshPorts.configure(state=DISABLED)
else:
self.labelComportName.configure(state=NORMAL)
self.labelComSpeed.configure(state=NORMAL)
self.comboComport.configure(state=NORMAL)
self.comboComSpeed.configure(state=NORMAL)
self.buttonComClose.configure(state=DISABLED)
self.buttonComOpen.configure(state=NORMAL)
self.buttonRefreshPorts.configure(state=NORMAL)
def main():
pass
if __name__ == '__main__':
main()
UPDATE: I did as Brian advised. Now I have two screen redraw functions. Difference between them is that first moves all the lines to the left adding new to the right and deleting those that fall off the canvas. The second one moves lines to the left and re-deploys elements that fall off the canvas to the right (without creating new ones). There's a huge improvement with any of these in respect to my initial variant, but I don't see big difference between the two wit the naked eye - mayme if I had more elements I would. The latter though works better specifically for my application as I don't have to track those who fall off the cliff.
Here the functions:
def drawGraph(self): ###needed for self.updateGraph2() only as it is creates the lines
for graphNum in range(0, self.numOfGraphs):
self.graphLines.append([])
self.graphData.append([0,]*self.numOfDots)
for iii in range(0,self.numOfDots):
self.graphLines[graphNum].append(
self.canv.create_line(0,0,0,0,fill=self.colors[graphNum],
width=1.2, tags=('graphLines', 'graph'+str(graphNum)))
)
def updateGraph2(self):
while not self.queue.empty():
iTuple = self.queue.get()
self.canv.move('graphLines', -self.density,0)
for graphNum in range(0, self.numOfGraphs):
try: self.graphData[graphNum].append(iTuple[graphNum])
except IndexError:
self.graphData[graphNum].append(0)
self.graphData[graphNum].pop(0)
self.graphLines[graphNum].append(self.graphLines[graphNum].pop(0))
self.canv.coords(self.graphLines[graphNum][-1],
self.canv.winfo_width()-self.density,
int(int(self.graphData[graphNum][-2])+int(self.canv.winfo_height()//2)),
self.canv.winfo_width(),
int(int(self.graphData[graphNum][-1])+int(self.canv.winfo_height()//2))
)
def updateGraph(self):
while not self.queue.empty():
self.timingIndex = self.timingIndex + 1
self.canv.move('graphLines', -self.density, 0)
iTuple = self.queue.get()
for iii in range(0, len(iTuple)):
yyy = int(iTuple[iii])+self.canv.winfo_height()//2
if yyy < 0: yyy = 0
if yyy > self.canv.winfo_height(): yyy = self.canv.winfo_height()
prev_yyy = int(self.prevTuple[iii])+self.canv.winfo_height()//2
if prev_yyy < 0: prev_yyy = 0
if prev_yyy > self.canv.winfo_height(): prev_yyy = self.canv.winfo_height()
self.canv.create_line(
self.canv.winfo_width()-self.density, prev_yyy,
self.canv.winfo_width(), yyy,
width = 1.4, fill = self.colors[iii], tags=('graphLines','graph'+str(iii)))
self.prevTuple = iTuple
self.canv.addtag_overlapping('todelete',-1,-1,-3,self.canv.winfo_height()+1)
self.canv.dtag('preserve','todelete')
self.canv.delete('todelete')
My understanding of the canvas is that the more element ids that have been allocated, the slower it gets. It can handle tens of thousands without much problem (and maybe even 100's of thousands), but if you're creating and deleting 6000 items every 100ms, that is likely your problem. Even though you are deleting the items, it still affects performance especially when you are creating 60,000 per second.
Instead of deleting all items every 100ms, simply move the items off screen and remember them, then reuse them by using the coords method to change their coordinates for the new graph.

PyQt QTableView not displaying icons after update to PyQt 4.5.1

I'll try to be as clear as possible, though this is all a bit muddled in my head.
I have a PyQt application that has been working for about a year now. After updating to PyQt 4.5.1 (from 4.3.3) none of my icons appear in the QTableView anymore (this update was concurrent with an update to python 2.6.5 from 2.5.1). Reverting to the older python and PyQt, everything works as expected.
The breakdown is this:
I am using the model-view methodology. My model, when requested via a Qt.DecorationRole in the data() method, will return a custom object (ColorSwatch) that is a subclass of the QIcon class. This has always worked (with the caveat that I, for reasons I don't understand, have to recast it as a QVariant first). After updating to PyQt 4.5.1 it appears to run correctly (i.e. I am not getting any errors), but the icon does not draw (though the space where it would be drawn is "reserved" i.e. the text has been shifted to the right to make way for this invisible icon).
Here are some things that I have tried:
I have verified that the ColorSwatch class does still function. This same class is used to draw icons into a contextual menu - and they appear correctly.
I have verified that the data() method is actually getting called and is returning this ColorSwatch object (recast into a QVariant <- though I have tested without this recasting as well).
Pouring snake blood onto my keyboard and lighting it afire.
Nothing so far has given me any clue as to what I should do. Any hints would be greatly appreciated. Thanks.
Here is some of the (potentially) relevant code (note that paramObj.get_icon() returns a ColorSwatch object):
#---------------------------------------------------------------------------
def data(self, index, role=QtCore.Qt.DisplayRole):
"""
Returns the text or formatting for a particular cell, depending on the
role supplied.
"""
blah
blah
blah
elif role == QtCore.Qt.DecorationRole:
if platform.system()=='Darwin':
return QtGui.QIcon(paramObj.get_icon())
else:
return QtCore.QVariant(paramObj.get_icon())
and
import os
import tempfile
import sys
import colorsys
import copy
import fnmatch
import time
from PyQt4 import QtGui
from PyQt4 import QtCore
################################################################################
class ColorSwatch(QtGui.QIcon):
"""
A subclass of QIcon, this class draws a colored paint chip with a border
The color and size are determined at construction time, and cannot
be changed later.
"""
#---------------------------------------------------------------------------
def __init__(self, r=1, g=1, b=1, br = 0, bg = 0, bb = 0, w=20, h=20):
"""
Constructor for the ColorSwatch class. Takes the passed arguments and
creates a square icon filled with the given color and with a border
color determined by br, bg, bb. All colors should be in floating point
format.
"""
QtGui.QIcon.__init__(self)
#normalize the color
r8, g8, b8 = self.normalize_color((r, g, b))
#convert the r, g, b values to 8 bit colors
r8, g8, b8 = self.fp_to_8b_color((r8, g8, b8))
#Create the pixmap and painter objects
paintChip = QtGui.QPixmap(w, h)
painter = QtGui.QPainter()
painter.begin(paintChip)
#fill the swatch
baseColor = QtGui.QColor(r8, g8, b8)
painter.fillRect(0, 0, w, h, baseColor)
#if any of the values were super brights (>1), draw a smaller, white
#box inset to make sure the user knows
if r > 1 or g > 1 or b > 1:
painter.fillRect(5, 5, w-10, h-10, QtGui.QColor(255, 255, 255))
#if all values are 0, put a faint x through the icon
# # # brush = QtGui.QBrush()
# # # brush.setColor(QtGui.QColor(30, 30, 30))
painter.setPen(QtGui.QColor(200, 200, 200))
if r ==0 and g == 0 and b == 0:
painter.drawLine(0, 0, w, h)
painter.drawLine(w-1, 0, -1, h)
# # #
# # # #normalize the color
# # # r8, g8, b8 = self.normalize_color((r8, g8, b8))
#now draw the border(s)
#convert the r, g, b values to 8 bit colors
r8, g8, b8 = self.fp_to_8b_color((br, bg, bb))
#draw the border
painter.setPen(QtGui.QColor(r8, g8, b8))
painter.drawRect(0,0,w-1,h-1)
#if any of the values were super brights (>1), draw a border around the
#inset box as well.
if r > 1 or g > 1 or b > 1:
painter.drawRect(5,5,w-11,h-11)
#done drawing
painter.end()
#add it (both to the normal and the selected modes)
self.addPixmap(paintChip, QtGui.QIcon.Normal)
self.addPixmap(paintChip, QtGui.QIcon.Selected)
#---------------------------------------------------------------------------
def fp_to_8b_color(self, color):
"""
Convert a floating point color value (passed in the form of a three
element tuple) to a regular 8-bit 0-255 value. Returns a 3 item tuple.
"""
r = max(min(int(color[0]*255),255),0)
g = max(min(int(color[1]*255),255),0)
b = max(min(int(color[2]*255),255),0)
return (r,g,b)
#---------------------------------------------------------------------------
def normalize_color(self, color):
"""
"normalizes" a color value so that if there are any super-whites, it
balances all the other floating point values so that we end up with a
"real" color. Negative values will result in undefined behavior.
Mainly used to make the color chip "look right" when using super whites.
"""
maxValue = max(color)
if maxValue > 1:
return (color[0]/maxValue, color[1]/maxValue, color[2]/maxValue)
else:
return color
Ivo answered my question above.
the actual code that works is:
#---------------------------------------------------------------------------
def data(self, index, role=QtCore.Qt.DisplayRole):
"""
Returns the text or formatting for a particular cell, depending on the
role supplied.
"""
blah
blah
blah
elif role == QtCore.Qt.DecorationRole:
if platform.system()=='Darwin':
return QtGui.QIcon(paramObj.get_icon())
else:
return QtCore.QVariant(QtGui.QIcon(paramObj.get_icon()))
#Note that it is first cast as a QIcon before
#being cast as a QVariant.
Thanks again Ivo.

Resources