I am a newbie in Python programming, I have started recently.
I am also discovering OOP, I was mostly programming embedded software based on ASM and C languages.
I have a small project where I need to read a file, get the data and "clean" them. These data are plot on a figure in Cartesian and in Polar coordinate. So far I achieved to grab examples of code here and there and to make the interface with pyQt5 and integrate both matplotlib canvases on them.
I have created a button called 'clean' when pressed it creates an event which is working, which clean the data but so far I didn't achieve to refresh the canvasses with the cleaned data.
I think that I got lost with the classes, functions and variables in the OOP of the pyQt integration.
Can somebody help me to see the main structure of the program and to pooint out my issue?
I also know that my python code is not that 'clean' and that I am not using the best of it and I verbose quite a lot... This come from C background.
See below code, I have removed the data treatment from the file. I need to plot the following list of list called LDTvalue[][], I have two graph on one plot which is the list of LDTvalue[0] and LDTvalue[6]:
I figure out that I cannot change any widget when calling the button even clicked. The window title change but not the widgets label for example. So this is where I have a strong suspicion of messing up with the classes and objects functions.
### UPDATE, I have copied the complete code, a bit dirty, but was a first try :) ###
import sys
import matplotlib
matplotlib.use("Qt5Agg")
from PyQt5 import QtCore
from PyQt5.QtWidgets import *
from numpy import arange, sin, cos, pi, radians
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
LDTheader = {'Company': 'xxx','Type_ind': 0,'Symmetry': 0, 'Mc': 0, 'Dc': 0 , 'Ng': 0, 'Dg': 0}
LDTline = list()
LDTCplane = list()
LDTCvalue = list()
LDTvalue = list()
LDTvalue2 = list()
LDTtemplist = list()
LDTfile = open('01.ldt')
for line in LDTfile :
LDTline.append(line.strip())
LDTheader['Company'] = LDTline[0];
LDTheader['Type_ind'] = LDTline[1];
LDTheader['Symmetry'] = LDTline[2];
LDTheader['Mc'] = int(LDTline[3]);
LDTheader['Dc'] = float(LDTline[4]);
LDTheader['Ng'] = int(LDTline[5]);
LDTheader['Dg'] = float(LDTline[6]);
print("Company name:", LDTheader['Company'])
print("Type indicator:", LDTheader['Type_ind'])
print("Symmetry:", LDTheader['Symmetry'])
print("Mc:", LDTheader['Mc'])
print("Dc", LDTheader['Dc'])
print("Ng:", LDTheader['Ng'])
print("Dg:", LDTheader['Dg'])
if LDTheader['Type_ind'] == '1' :
print('1 - point source with symmetry about the vertical axis')
elif LDTheader['Type_ind'] == '2' :
print('2 - linear luminaire')
elif LDTheader['Type_ind'] == '3' :
print('3 - point source with any other symmetry')
else: print('Error in source type indicator!')
if LDTheader['Symmetry'] == '0' :
print('0 - no symmetry')
elif LDTheader['Symmetry'] == '1' :
print('1 - symmetry about the vertical axis')
elif LDTheader['Symmetry'] == '2' :
print('2- symmetry to plane C0-C180')
elif LDTheader['Symmetry'] == '3' :
print('3- symmetry to plane C90-C270')
elif LDTheader['Symmetry'] == '4' :
print('4- symmetry to plane C0-C180 and to plane C90-C270')
else: print('Error in symmetry paramater!')
for i in range(42,42+LDTheader['Mc']) :
LDTCplane.append(LDTline[i])
for i in range(42+LDTheader['Mc'],42+LDTheader['Mc']+LDTheader['Ng']) :
LDTCvalue.append(float(LDTline[i]))
#Separate all the values of the different Cplane in a list of a list called LDTvalue
#print(int(LDTheader['Mc']/int(LDTheader['Symmetry']))+1)
for j in range(0,int(LDTheader['Mc']/int(LDTheader['Symmetry']))+1):
if LDTheader['Symmetry'] == '4':
for i in range(1,LDTheader['Ng']):
LDTtemplist.append(float(LDTline[42+LDTheader['Ng']*(j+1)+LDTheader['Mc']+(LDTheader['Ng']-i)]))
for i in range(0,LDTheader['Ng']-1):
LDTtemplist.append(float(LDTline[42+LDTheader['Ng']*(j+1)+LDTheader['Mc']+(i)]))
LDTvalue.append(list(LDTtemplist))
del LDTtemplist
LDTtemplist = list()
if LDTheader['Symmetry'] == '4':
for i in range(1,LDTheader['Ng']):
LDTtemplist.append(float(LDTline[42+LDTheader['Ng']+LDTheader['Mc']+(LDTheader['Ng']-i)]))
for i in range(0,LDTheader['Ng']-1):
LDTtemplist.append(float(LDTline[42+LDTheader['Ng']+LDTheader['Mc']+(i)]))
LDTvalue2 = LDTtemplist
del LDTtemplist
LDTtemplist = list()
class MyPDiagram(FigureCanvas):
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111,projection='polar')
# We want the axes cleared every time plot() is called
#self.axes.hold(False)
t = arange(-180,180,LDTheader['Dg'])
t = radians(t)
self.axes.plot(t, LDTvalue[0], color='r', linewidth=1)
self.axes.plot(t, LDTvalue[6], color='b', linewidth=1)
self.axes.set_theta_zero_location("S")
self.axes.grid(True)
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self,
QSizePolicy.Expanding,
QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
class MyCDiagram(FigureCanvas):
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
# We want the axes cleared every time plot() is called
#self.axes.hold(False)
x = arange(-180,180,LDTheader['Dg'])
self.axes.plot(x, LDTvalue[0], color='r', linewidth=1)
self.axes.plot(x, LDTvalue[6], color='b', linewidth=1)
self.axes.grid(True)
#
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self,
QSizePolicy.Expanding,
QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
buttonOK = QPushButton('OK')
buttonClean = QPushButton('Clean')
pd_label = QLabel('Polar Diagram')
pd = MyPDiagram(QWidget(self), width=5, height=4, dpi=100)
cd_label = QLabel('Carthesian Diagram')
cd = MyCDiagram(QWidget(self), width=5, height=4, dpi=100)
buttonClean.clicked.connect(self.handleButtonClean)
grid = QGridLayout()
grid.setSpacing(10)
grid.addWidget(buttonOK, 1, 0)
grid.addWidget(buttonClean, 1, 1)
grid.addWidget(pd_label, 2, 0)
grid.addWidget(pd, 3, 0, 5, 1)
grid.addWidget(cd_label, 2, 1)
grid.addWidget(cd, 3, 1, 5, 1)
grid.update()
self.setLayout(grid)
self.setGeometry(300, 300, 1000, 600)
self.setWindowTitle('W&D LDTcleaner')
def handleButtonClean(self):
#1. Clean the value above 90 degrees
for j in range(0,int(LDTheader['Mc']/int(LDTheader['Symmetry']))+1):
for i in range(180,LDTheader['Ng']-1):
LDTvalue[j][i] = 0
#2. Clean when center value is not the max
for j in range(0,int(LDTheader['Mc']/int(LDTheader['Symmetry']))+1):
for i in range(0,LDTheader['Ng']-1):
if LDTvalue[j][0] < max(LDTvalue[j]):
LDTvalue[j][0] = max(LDTvalue[j])
self.initUI() #-----> This is where I am blocked. I tried to recall initUI()
print('Cleaned!')
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MainWindow()
ex.show()
sys.exit(app.exec_())
Related
I am trying to tag x-spans of a data trace and populate a table with tagNames, starting x value, and the ending x value. I am using a dictionary of 'highlight' objects to keep track of the x-spans in case they need to be edited (increased or decreased) later. The dictionary maps the x Start value to the highlight object, as the x start values are expected to be unique (there is no overlap of x-spans for the tagging).
In order to do this, I am emitting a signal when the user beings to edit a cell on the table. The function that the first signal connects to emits another signal (ideally for whether the xStart is changed vs. the xEnd, but I have only implemented the xStart thus far), which actually changes the appearance of the span to match the edit.
I asked a similar question a few weeks ago but wasn't able to get an answer. The old question is here: PyQT5 slot parameters not updating after first call. In response to the tips given there, I wrote the following example:
import sys
from PyQt5.QtWidgets import *
from PyQt5 import QtCore
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.widgets as mwidgets
from functools import partial
class Window(QMainWindow):
def __init__(self, parent = None):
super(Window, self).__init__(parent)
self.resize(1600, 800)
self.MyUI()
def MyUI(self):
canvas = Canvas(self, width=14, height=12, dpi=100)
canvas.move(0,0)
#use this object in the dictionary to hold onto all the spans.
class highlight:
def __init__(self, tag, xStart, xEnd, highlightObj):
self.tag = tag
self.xStart = xStart
self.xEnd = xEnd
self.highlightObj = highlightObj
class Canvas(FigureCanvas):
def __init__(self, parent, width = 14, height = 12, dpi = 100):
Plot = Figure(figsize=(width, height), dpi=dpi)
self.Axes = Plot.add_subplot(111)
self.Axes.set_position([0.05, 0.58, 0.66, 0.55])
self.rowCount = 0
super().__init__(Plot)
self.setParent(parent)
##add all relevant lines to plot
self.Axes.plot([0,1,2,3,4], [3, 4, 5, 6, 7])
self.Axes.set_xlabel('Frame', fontsize = 10)
self.Axes.grid()
self.Axes.set_aspect(1)
Plot.canvas.draw()
self.highlights = {} #empty dictionary to store all the tags.
##define a table to hold the values postselection
self.taggingTable = QTableWidget(self)
self.taggingTable.setColumnCount(3)
self.taggingTable.setRowCount(100)
self.taggingTable.setGeometry(QRect(1005,85, 330, 310))
self.taggingTable.setHorizontalHeaderLabels(['Behavior','Start Frame', 'End Frame'])
Canvas.span = mwidgets.SpanSelector(self.Axes, self.onHighlight, "horizontal",
interactive = True, useblit=True, props=dict(alpha=0.5, facecolor="blue"),)
self.draw_idle()
self.taggingTable.selectionModel().selectionChanged.connect(self.onCellSelect)
self.draw_idle()
##highlighting adds a highlight item to the directory.
def onHighlight(self, xStart, xEnd):
tagName = "No Tag"
self.taggingTable.setItem(self.rowCount, 0, QTableWidgetItem(tagName))
self.taggingTable.setItem(self.rowCount, 1, QTableWidgetItem(str(int(xStart))))
self.taggingTable.setItem(self.rowCount, 2, QTableWidgetItem(str(int(xEnd))))
self.rowCount = self.rowCount + 1
highlightObj = self.Axes.axvspan(xStart, xEnd, color = 'blue', alpha = 0.5)
self.highlights[int(xStart)] = highlight(tagName, xStart, xEnd, highlightObj)
self.draw_idle()
def xStartChanged(self, xStart, rowVal):
if self.inCounter == 0:
print("xStart in slot: ", xStart)
xEnd = self.highlights[xStart].xEnd
xStartNew = int(self.taggingTable.item(rowVal, 1).text())
self.highlights[xStart].highlightObj.remove() #remove old from the plot
del self.highlights[xStart] #remove old from directory
highlightObj = self.Axes.axvspan(xStartNew, xEnd, color = 'blue', alpha = 0.5) #add new to plot
self.highlights[xStartNew] = highlight("No tagName", xStartNew, xEnd, highlightObj) #add new to directory
self.taggingTable.clearSelection() #deselect value from table
self.draw_idle()
self.inCounter = self.inCounter + 1
def onCellSelect(self):
index = self.taggingTable.selectedIndexes()
if len(index) != 0:
rowVal = index[0].row()
if not (self.taggingTable.item(rowVal, 1) is None):
xStart = int(self.taggingTable.item(rowVal, 1).text())
print("--------------")
print("xStart in signal: ", xStart)
self.inCounter = 0
self.taggingTable.itemChanged.connect(lambda: self.xStartChanged(xStart, rowVal))
app = QApplication(sys.argv)
window = Window()
window.show()
app.exec()
A test I run is when I highlight two traces:
and then I successfully change a first trace:
However, when I attempt to edit the second trace, the program crashes:
To debug, I tried to check what the signals were emitting and receiving. it produces the following output:
--------------
xStart in signal: 0
xStart in slot: 0 ##First slot call gets correct signal
--------------
xStart in signal: 3
xStart in slot: 0 ## Second slot gets the first signal instead of the second
Traceback (most recent call last):
File "//Volumes/path/file.py", line 105, in <lambda>
self.taggingTable.itemChanged.connect(lambda: self.xStartChanged(xStart, rowVal))
File "//Volumes/path/file.py", line 86, in xStartChanged
xEnd = self.highlights[xStart].xEnd
KeyError: 0
zsh: abort python Volumes/path file.py
I tried to use information online on unique connections but I am not sure how to implement them. Thank you in advance for any help.
It seems that what you need is table-widget signal that emits an item and its old text value whenever a change is made to a specific column. Unfortunately, the itemChanged signal isn't really suitable, because it doesn't indicate what changed, and it doesn't supply the previous value. So, to work around this limitation, one solution would be to subclass QTableWidget / QTableWidgetItem and emit a custom signal with the required parameters. This will completely side-step the issue with multiple signal-slot connections.
The implementation of the subclasses is quite simple:
class TableWidgetItem(QTableWidgetItem):
def setData(self, role, value):
oldval = self.text()
super().setData(role, value)
if role == Qt.EditRole and self.text() != oldval:
table = self.tableWidget()
if table is not None:
table.itemTextChanged.emit(self, oldval)
class TableWidget(QTableWidget):
itemTextChanged = pyqtSignal(TableWidgetItem, str)
Below is basic a demo based on your example that shows how to use them. (Note that I have made no attempt to handle xEnd as well, as that would go beyond the scope of the immediate issue).
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.widgets as mwidgets
class Window(QMainWindow):
def __init__(self, parent = None):
super(Window, self).__init__(parent)
self.resize(1600, 800)
self.MyUI()
def MyUI(self):
canvas = Canvas(self, width=14, height=12, dpi=100)
canvas.move(0,0)
# CUSTOM SUBCLASSES
class TableWidgetItem(QTableWidgetItem):
def setData(self, role, value):
oldval = self.text()
super().setData(role, value)
if role == Qt.EditRole and self.text() != oldval:
table = self.tableWidget()
if table is not None:
table.itemTextChanged.emit(self, oldval)
class TableWidget(QTableWidget):
itemTextChanged = pyqtSignal(TableWidgetItem, str)
#use this object in the dictionary to hold onto all the spans.
class highlight:
def __init__(self, tag, xStart, xEnd, highlightObj):
self.tag = tag
self.xStart = xStart
self.xEnd = xEnd
self.highlightObj = highlightObj
class Canvas(FigureCanvas):
def __init__(self, parent, width = 14, height = 12, dpi = 100):
Plot = Figure(figsize=(width, height), dpi=dpi)
self.Axes = Plot.add_subplot(111)
self.Axes.set_position([0.05, 0.58, 0.66, 0.55])
self.rowCount = 0
super().__init__(Plot)
self.setParent(parent)
##add all relevant lines to plot
self.Axes.plot([0,1,2,3,4], [3, 4, 5, 6, 7])
self.Axes.set_xlabel('Frame', fontsize = 10)
self.Axes.grid()
self.Axes.set_aspect(1)
Plot.canvas.draw()
self.highlights = {} #empty dictionary to store all the tags.
##define a table to hold the values postselection
# USE CUSTOM TABLE SUBCLASS
self.taggingTable = TableWidget(self)
self.taggingTable.setColumnCount(3)
self.taggingTable.setRowCount(100)
self.taggingTable.setGeometry(QRect(1005,85, 330, 310))
self.taggingTable.setHorizontalHeaderLabels(['Behavior','Start Frame', 'End Frame'])
# CONNECT TO CUSTOM SIGNAL
self.taggingTable.itemTextChanged.connect(self.xStartChanged)
Canvas.span = mwidgets.SpanSelector(self.Axes, self.onHighlight, "horizontal",
interactive = True, useblit=True, props=dict(alpha=0.5, facecolor="blue"),)
self.draw_idle()
##highlighting adds a highlight item to the directory.
def onHighlight(self, xStart, xEnd):
tagName = "No Tag"
self.taggingTable.setItem(self.rowCount, 0, QTableWidgetItem(tagName))
# USE CUSTOM ITEM SUBCLASS
self.taggingTable.setItem(self.rowCount, 1, TableWidgetItem(str(int(xStart))))
self.taggingTable.setItem(self.rowCount, 2, QTableWidgetItem(str(int(xEnd))))
self.rowCount = self.rowCount + 1
highlightObj = self.Axes.axvspan(xStart, xEnd, color = 'blue', alpha = 0.5)
self.highlights[int(xStart)] = highlight(tagName, xStart, xEnd, highlightObj)
self.draw_idle()
def xStartChanged(self, item, oldVal):
try:
# VALIDATE NEW VALUES
xStart = int(oldVal)
xStartNew = int(item.text())
except ValueError:
pass
else:
print("xStart in slot: ", xStart)
xEnd = self.highlights[xStart].xEnd
self.highlights[xStart].highlightObj.remove() #remove old from the plot
del self.highlights[xStart] #remove old from directory
highlightObj = self.Axes.axvspan(xStartNew, xEnd, color = 'blue', alpha = 0.5) #add new to plot
self.highlights[xStartNew] = highlight("No tagName", xStartNew, xEnd, highlightObj) #add new to directory
self.taggingTable.clearSelection() #deselect value from table
self.draw_idle()
app = QApplication(sys.argv)
window = Window()
window.show()
app.exec()
I have a program where I would like to swap between different axes in the same figure in order to plot different datasets against eachother, either in a polar or rectilinear coordinate system depending on the dataset.
The change_axes method implements this functionality, while the swap_axes, clf_swap, hide_swap and new_subplot_swap methods show different ways of trying to swap between axes, although swap_axes is the only one that produces the desired result. The code is based on this post.
Is there a better way of doing this? Using axes.set_visible or axes.set_alpha does nothing for me and even produces an Error for some reason (AttributeError: 'NoneType' object has no attribute 'get_points'). I don't understand why this doesn't work, but it would have been a much easier way of 'adding' and 'removing' the axes.
import numpy as np
import sys
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import \
FigureCanvasQTAgg as FigureCanvas
from PyQt5.QtWidgets import (
QApplication, QDialog, QGridLayout, QComboBox, QPushButton
)
class Test(QDialog):
def __init__(self):
super().__init__()
self.lay = QGridLayout(self)
self.fig, self.ax = plt.subplots(constrained_layout=True)
self.ax2 = self.fig.add_subplot(111, projection='polar')
# self.ax2 = plt.subplot(111, projection='polar')
# uncomment this for 'hide_swap'
# self.ax2.set_visible(False)
self.canvas = FigureCanvas(self.fig)
self.lay.addWidget(self.canvas)
self.data = {
'Dataset 1': np.random.normal(2, 0.5, 10000),
'Dataset 2': np.random.binomial(10000, 0.3, 10000),
'Dataset 3': np.random.uniform(0, 2*np.pi, 10000)
}
_, _, self.artists = self.ax.hist(self.data['Dataset 1'], 100)
self.swapBtn = QPushButton('Swap')
self.lay.addWidget(self.swapBtn, 1, 0)
self.swapBtn.clicked.connect(self.swap_axes)
self.clfSwapBtn = QPushButton('fig.clf() swap')
self.clfSwapBtn.clicked.connect(self.clf_swap)
self.lay.addWidget(self.clfSwapBtn, 2, 0)
self.hideSwapBtn = QPushButton('Hide swap')
self.hideSwapBtn.clicked.connect(self.hide_swap)
self.lay.addWidget(self.hideSwapBtn, 3, 0)
self.subplotSwapBtn = QPushButton('New subplot swap')
self.subplotSwapBtn.clicked.connect(self.new_subplot_swap)
self.lay.addWidget(self.subplotSwapBtn, 4, 0)
self.xParam = QComboBox()
self.xParam.addItem('Dataset 1')
self.xParam.addItem('Dataset 2')
self.xParam.addItem('Dataset 3')
self.xParam.currentTextChanged.connect(self.change_axes)
self.lay.addWidget(self.xParam)
self.yParam = QComboBox()
self.yParam.addItem('Distribution')
self.yParam.addItem('Dataset 1')
self.yParam.addItem('Dataset 2')
self.yParam.addItem('Dataset 3')
self.yParam.currentTextChanged.connect(self.change_axes)
self.lay.addWidget(self.yParam)
self.canvas.draw()
# this is neccessary for
# "self.ax2 = self.fig.add_subplot(111, projection='polar')", and for
# some reason has to be called after 'canvas.draw', otherwise,
# the constrained layout cannot be applied. comment this if using
# "self.ax2 = plt.subplot(111, projection='polar')" or "hide_swap"
self.ax2.remove()
def change_axes(self):
if self.yParam.currentText() == 'Distribution':
if self.xParam.currentText() == 'Dataset 3':
if self.fig.axes[0] == self.ax:
self.ax.remove()
self.ax2.figure = self.fig
self.fig.axes.append(self.ax2)
self.fig.add_axes(self.ax2)
radii, theta = np.histogram(self.data['Dataset 3'], 100)
width = np.diff(theta)
self.fig.axes[0].cla()
self.artists = self.ax2.bar(theta[:-1], radii, width=width)
else:
if self.fig.axes[0] == self.ax2:
self.ax2.remove()
self.ax.figure = self.fig
self.fig.axes.append(self.ax)
self.fig.add_axes(self.ax)
self.fig.axes[0].cla()
_, _, self.artists = self.ax.hist(
self.data[self.xParam.currentText()], 100
)
else:
if (
self.xParam.currentText() == 'Dataset 3'
and self.fig.axes[0] == self.ax
):
self.ax.remove()
self.ax2.figure = self.fig
self.fig.axes.append(self.ax2)
self.fig.add_axes(self.ax2)
elif (
self.xParam.currentText() != 'Dataset 3'
and self.fig.axes[0] == self.ax2
):
self.ax2.remove()
self.ax.figure = self.fig
self.fig.axes.append(self.ax)
self.fig.add_axes(self.ax)
self.fig.axes[0].cla()
self.artists = self.fig.axes[0].plot(
self.data[self.xParam.currentText()],
self.data[self.yParam.currentText()], 'o'
)
self.canvas.draw()
def swap_axes(self):
if self.fig.axes[0] == self.ax:
self.ax.remove()
self.ax2.figure = self.fig
self.fig.axes.append(self.ax2)
self.fig.add_axes(self.ax2)
else:
self.ax2.remove()
self.ax.figure = self.fig
self.fig.axes.append(self.ax)
self.fig.add_axes(self.ax)
self.canvas.draw()
def clf_swap(self):
if self.fig.axes[0] == self.ax:
self.fig.clf()
self.ax2.figure = self.fig
self.fig.add_axes(self.ax2)
else:
self.fig.clf()
self.ax.figure = self.fig
_, _, self.artists = self.ax.hist(self.data['Dataset 1'], 100)
self.fig.add_axes(self.ax)
self.canvas.draw()
def hide_swap(self):
if self.ax.get_visible():
self.ax.set_visible(False)
self.ax2.set_visible(True)
# self.ax.set_alpha(0)
# self.ax2.set_alpha(1)
else:
self.ax.set_visible(True)
self.ax2.set_visible(False)
# self.ax.set_alpha(1)
# self.ax2.set_alpha(0)
self.canvas.draw()
def new_subplot_swap(self):
if self.fig.axes[0].name == 'rectilinear':
self.ax.remove()
self.ax2 = self.fig.add_subplot(111, projection='polar')
else:
self.ax2.remove()
self.ax = self.fig.add_subplot(111)
_, _, self.artists = self.ax.hist(self.data['Dataset 1'], 100)
self.canvas.draw()
if __name__ == '__main__':
app = QApplication(sys.argv)
test = Test()
test.show()
sys.exit(app.exec_())
Using the current development version of matplotlib the error would not appear. But one would need to make sure to exclude the respective axes from the constrained layout mechanism.
import matplotlib.pyplot as plt
fig, ax = plt.subplots(constrained_layout=True)
ax2 = fig.add_subplot(111, projection="polar")
ax2.set_visible(False)
ax2.set_in_layout(False)
def swap(evt):
if evt.key == "h":
b = ax.get_visible()
ax.set_visible(not b)
ax.set_in_layout(not b)
ax2.set_visible(b)
ax2.set_in_layout(b)
fig.canvas.draw_idle()
cid = fig.canvas.mpl_connect("key_press_event", swap)
plt.show()
With any current matplotlib version, one could use tight_layout instead of constrained layout, and call it manually at each resize event.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax2 = fig.add_subplot(111, projection="polar")
ax2.set_visible(False)
def swap(evt):
if evt.key == "h":
b = ax.get_visible()
ax.set_visible(not b)
ax2.set_visible(b)
fig.tight_layout()
fig.canvas.draw_idle()
def onresize(evt):
fig.tight_layout()
cid = fig.canvas.mpl_connect("key_press_event", swap)
cid = fig.canvas.mpl_connect("resize_event", onresize)
fig.tight_layout()
plt.show()
I'm trying to do something pretty straight forward in Matplotlib, but hitting a ton of hurdles; after googling for hours, I'm thinking this takes some very specific magic. I just want my GUI to start on a blank screen and then import a file that needs a colorbar - if the file is good, a colorbar is added. If the file is added again (or a different one), the colorbar is removed and a new one is plotted. If the file is bad, we reset to initial conditions (no colorbars or plot). I really hope the below code makes sense for what I'm getting at here, thank you so much for your help and time in advance:
import sys
import os
import random
import matplotlib
matplotlib.use('Qt5Agg')
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QGridLayout, QFileDialog, QPushButton
import numpy as np
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
class MyMplCanvas(FigureCanvas):
def __init__(self, parent=None, width=5, height=4, dpi=100):
MyMplCanvas.fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = MyMplCanvas.fig.add_subplot(111)
self.compute_initial_figure()
FigureCanvas.__init__(self, MyMplCanvas.fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self,
QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
def compute_initial_figure(self):
pass
class MyDynamicMplCanvas(MyMplCanvas):
def __init__(self, *args, **kwargs):
MyMplCanvas.__init__(self, *args, **kwargs)
def compute_initial_figure(self):
C=np.random.rand(500).reshape((20,25))
S=np.random.rand(500).reshape((20,25))
dc = self.function(S,C)
im = self.axes.imshow(dc, alpha = 0)
MyMplCanvas.fig.patch.set_facecolor('k')
self.axes.patch.set_facecolor('k')
# don't want to show the cb initially (imagine GUI starts on a blank screen)
#cb = MyMplCanvas.fig.colorbar(im, ax=self.axes,
#orientation='vertical')#,use_gridspec=True)??
def update_figure(self):
# need something like the below to delete subsequent colorbars if they exist:
#if MyMplCanvas.fig.axes[1] is not None:
# MyMplCanvas.fig.delaxes(MyMplCanvas.fig.axes[1]) #remove colorbar
self.axes.cla()
if P1.df: #datafile is good
MyMplCanvas.fig.patch.set_facecolor('w')
self.axes.patch.set_facecolor('w')
C=np.random.rand(500).reshape((20,25))
S=np.random.rand(500).reshape((20,25))
dc = self.function(S,C)
im = self.axes.imshow(dc)
cb = MyMplCanvas.fig.colorbar(im, ax=self.axes,
orientation='vertical')#,use_gridspec=True)??
else: #datafile was bad, or they pressed cancel
C=np.random.rand(500).reshape((20,25))
S=np.random.rand(500).reshape((20,25))
dc = self.function(S,C)
im = self.axes.imshow(dc, alpha = 0)
MyMplCanvas.fig.patch.set_facecolor('k')
self.axes.patch.set_facecolor('k')
self.show()
self.draw()
def function(self,s,c):
return s*2+c
class P1(QtWidgets.QWidget):
df = False
def __init__(self, parent=None):
super(P1, self).__init__(parent)
layout = QGridLayout(self)
self.button_browse1 = QPushButton('Browse Good', self)
self.button_browse1.clicked.connect(self.browseFile1)
layout.addWidget(self.button_browse1, 1, 1, 1, 1)
self.button_browse1.show()
self.button_browse2 = QPushButton('Browse Bad', self)
self.button_browse2.clicked.connect(self.browseFile2)
layout.addWidget(self.button_browse2, 2, 1, 1, 1)
self.button_browse2.show()
self.dc = MyDynamicMplCanvas(self, width=5, height=4, dpi=100)
layout.addWidget(self.dc, 3, 1, 1, 1)
def browseFile1(self):
P1.df = True
self.dc.update_figure()
def browseFile2(self):
P1.df = False
self.dc.update_figure()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.stack = QtWidgets.QStackedWidget(self)
P1f = P1(self)
self.stack.addWidget(P1f)
self.setCentralWidget(self.stack)
if __name__ == '__main__':
qApp = QtWidgets.QApplication(sys.argv)
aw = MainWindow()
aw.show()
sys.exit(qApp.exec_())
Okay, so for anyone else stumbling on this, the trick is to first define your colorbar the very first time you plot (initial figure) via the top Class its in, i.e., MyMplCanvas.cbar = MyMplCanvas.fig.colorbar(im). Its very important to define it up front even if you didn't want to even use it yet.
Then at the top of the update plot function, simply call MyMplCanvas.cbar.remove().
Matplotlib does not respond to key presses while embedded in PyQt5. I have verified that key presses work with Tkinter. This is my first PyQt5 experience. I am not sure what I'm doing wrong here. I have tried to set the focus on canvas by doing. All this is to display 3D volumetric data, which you can think of as a stack of images. Together, they describe a 3D structure. For example, magnetic resonance imaging (MRI)
self.canvas.setFocusPolicy( QtCore.Qt.ClickFocus )
self.canvas.setFocus()
however that returns an error:
self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus)
AttributeError: 'InputCanvas' object has no attribute 'canvas'
This is the code I have:
import sys
from PyQt5 import QtCore
from skimage import io
from PyQt5.QtWidgets import QMainWindow, QSizePolicy
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
from PyQt5.QtWidgets import QApplication
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as
FigureCanvas
class App(QMainWindow):
def __init__(self):
super().__init__()
self.left = 10
self.top = 10
self.title = 'PYQT'
self.width = 600
self.height = 400
self.initialize_ui()
def initialize_ui(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
m = InputCanvas(self, width=3, height=3)
m.move(200, 20)
self.show()
class InputCanvas(FigureCanvas):
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus)
self.canvas.setFocus()
fig.canvas.mpl_connect('key_press_event', self.process_key)
self.input_plot()
def input_plot(self):
volume = io.imread("14.mha", plugin='simpleitk')
ax = self.figure.add_subplot(111)
ax.volume = volume
ax.index = volume.shape[0] // 2
ax.set_title('Ground Truth Image')
plt.set_cmap("gray")
plt.axis('off')
ax.imshow(volume[ax.index])
self.draw()
def process_key(self, event):
fig = event.canvas.figure
ax = fig.axes[0]
if event.key == 'j':
self.previous_slice(ax)
elif event.key == 'k':
self.ext_slice(ax)
self.draw()
def previous_slice(self, ax):
volume = ax.volume
ax.index = (ax.index - 1) % volume.shape[0] # wrap around using %
ax.images[0].set_array(volume[ax.index])
def next_slice(self, ax):
volume = ax.volume
ax.index = (ax.index + 1) % volume.shape[0]
ax.images[0].set_array(volume[ax.index])
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
You forgot to tell the canvas that it should do something when a key is pressed. You may use matplotlib to do so,
self.cid = self.mpl_connect("key_press_event", self.process_key)
To get rid of the error, consider the error message. "canvas" has no attribute canvas is telling you that the canvas does not have itself as attribute. Hence
self.setFocusPolicy(QtCore.Qt.ClickFocus)
self.setFocus()
I have been trying unsuccessfully to embed a matplotlib into PyQt for the past few days and before you say anything, I have been to every site possible to help me.
When I run the code it produces a 1 by 1 graph but doesn't actually graph anything
from __future__ import unicode_literals
import sys
import os
import random
from matplotlib.backends import qt4_compat
use_pyside = qt4_compat.QT_API == qt4_compat.QT_API_PYSIDE
if use_pyside:
from PySide import QtGui, QtCore
else:
from PyQt4 import QtGui, QtCore
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import numpy as np
import scipy.io
import os
import pandas as pd
progname = os.path.basename(sys.argv[0])
progversion = "0.1"
class MyMplCanvas(FigureCanvas):
"""Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
# We want the axes cleared every time plot() is called
self.axes.hold(False)
self.compute_initial_figure()
#
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self,
QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
def compute_initial_figure(self):
pass
class MyStaticMplCanvas(MyMplCanvas):
def extract_data(self, name):
#setting up lists
Stim_trig = []
Stim_order = []
Sch_wav = []
data = scipy.io.loadmat(name)
for k,v in data.items():
#Sends Sch_wav data to a list
if "Sch_wav" in k:
for d in (((v[0])[0])[4]):
Sch_wav.append(d[0])
#Sends StimTrig to a list
if k=="StimTrig":
for g in (((v[0])[0])[4]):
Stim_trig.append(g[0])
Stim_trig.append(Stim_trig[-1]+1)
#Sends Stim order to a list
for w in (((v[0])[0])[5]):
Stim_order.append(w[0])
superdata = []
#Prepares grouping stimuli and trigger
for i in range(len(Stim_trig)-1):
fire = []
for p in Sch_wav:
if p > Stim_trig[i] and p < Stim_trig[i+1]:
fire.append(p - Stim_trig[i])
superdata.append([Stim_order[i],fire])
#sorts all the data
superdata.sort()
alladdedup = [[1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[62]]
count = 0
for d in superdata:
if d[0] == (alladdedup[count])[0]:
for j in d[1]:
((alladdedup)[count]).append(j)
else:
count += 1
#places time stamps of triggers in lists for each trigger
for l in alladdedup:
l.pop(0)
l.sort()
#removes title and sorts data
ffmsb = []
#finds number of firings for each milisecond bin
for v in alladdedup:
fmsb = []
for b in range(100):
msbc = b/100
msb = []
for t in v:
if t > msbc and t < msbc + 0.01:
msb.append(t)
fmsb.append(len(msb))
ffmsb.append(fmsb)
#returns list of stimuli firings per milisecond bin
return ffmsb
#End of Sorting Code
#Start of Graphing Code
def stimuli_graph(self):
#Set file to use and the stimuli wanted. In this case stimuli 1 is being used
filename = ("654508_rec02_all.mat"[1])
self.extract_data(filename)
#Creates parameters for index
numberlist = []
for i in range(100):
numberlist.append(i/100)
#Adjusts the y-axis max according to the y-axis of a graphed stimuli e.g. If plotted graph has y-axis of 10
# then it'll be adjusted by 1.3. So it'll change to 13
x = 0
for i in filename:
if i > x:
x = i*1.3
#Dataframes the data from extract_data
c = pd.Series(filename, index = numberlist)
neurons = pd.DataFrame((c))
#This is where the data gets graphed. I know this is where the problem occurs, I just don't know where
#This code is a mess and for that I am sorry
times = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
ax = neurons.plot(
kind = 'bar',
title = 'Number of neurons firing for stimuli '+str(1),
legend = False,
xlim = (0, 100),
ylim = (0, x),
width = 1,
position = 0,
edgecolor = 'white',
xticks = (np.arange(min(times), max(times), 10)) #Makes the x-axis label in increments of 0.1 instead of 0.01
)
ax.set_xlabel('Time (s)')
ax.set_ylabel('Neurons Firing')
t = arange(0, 100, 1)
self.axes.plot(t, neurons)
class ApplicationWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.setWindowTitle("application main window")
self.file_menu = QtGui.QMenu('&File', self)
self.file_menu.addAction('&Quit', self.fileQuit,
QtCore.Qt.CTRL + QtCore.Qt.Key_Q)
self.menuBar().addMenu(self.file_menu)
self.help_menu = QtGui.QMenu('&Help', self)
self.menuBar().addSeparator()
self.menuBar().addMenu(self.help_menu)
self.main_widget = QtGui.QWidget(self)
l = QtGui.QVBoxLayout(self.main_widget)
sc = MyStaticMplCanvas(self.main_widget, width=5, height=4, dpi=100)
l.addWidget(sc)
self.main_widget.setFocus()
self.setCentralWidget(self.main_widget)
self.statusBar().showMessage("All hail matplotlib!", 2000)
def fileQuit(self):
self.close()
def closeEvent(self, ce):
self.fileQuit()
qApp = QtGui.QApplication(sys.argv)
aw = ApplicationWindow()
aw.setWindowTitle("%s" % progname)
aw.show()
sys.exit(qApp.exec_())
#qApp.exec_()
The data used in the graph is extracted from an external file