When I draw a Matplotlib subplot on a QLabel, I get a different colormap than when I save it to file. How can I show the real colormap with QT? For example, in my code below, the image on disk has the good red-blue colormap, but the QT one is a blue-dark orange one. I tried other QImage formats but no good so far.
import sys
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel
from PyQt5.QtGui import QImage, QPixmap
class Window(QMainWindow):
def __init__(self):
# window design
super().__init__()
self.setFixedSize(1000, 400)
# create QT element
self.wid_labelGraph = QLabel(self)
self.wid_labelGraph.setStyleSheet("background-color : white;")
self.setCentralWidget(self.wid_labelGraph)
self.make_graphs()
def make_graphs(self):
# generate graph
heatmaps = [ np.ones((50, 50)), np.zeros((50, 50)), -1 * np.ones((50, 50)) ]
fig, axs = plt.subplots(1, 3, figsize = (10, 4))
for i in range(3):
im = axs[i].imshow(heatmaps[i], cmap = 'RdBu_r', clim = (-1, 1))
axs[i].set_axis_off()
fig.colorbar(im, ax = axs.ravel().tolist())
fig.savefig('temp.png')
canvas = FigureCanvas(fig)
canvas.draw()
width, height = fig.figbbox.width, fig.figbbox.height
img = QImage(canvas.buffer_rgba(), width, height, QImage.Format_ARGB32)
self.wid_labelGraph.setPixmap(QPixmap(img))
# free memory
plt.close()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Window()
window.show()
app.exec()
The format is wrong.
You're using Format_ARGB32:
The image is stored using a 32-bit ARGB format (0xAARRGGBB).
But, as the function name buffer_rgba() suggests, the format is rgba, so Format_RGBA8888:
The image is stored using a 32-bit byte-ordered RGBA format (8-8-8-8). Unlike ARGB32 this is a byte-ordered format, which means the 32bit encoding differs between big endian and little endian architectures, being respectively (0xRRGGBBAA) and (0xAABBGGRR). The order of the colors is the same on any architecture if read as bytes 0xRR,0xGG,0xBB,0xAA.
You got the wrong colors because the bits referring to the components are shifted: the red was interpreted as alpha, the green as red, the blue as green, and the alpha as blue.
Related
How to fix BarGraphItem height when zoom in or zoom out pyqtgraph plot, it should be similar with axis font, the font size is in pixel, please someone helps, thank you very much, basic code as below:
import numpy as np
import pyqtgraph as pg
pg.setConfigOption('leftButtonPan', False)
win = pg.plot()
win.setWindowTitle('pyqtgraph example: BarGraphItem')
x = np.arange(10)
bg1 = pg.BarGraphItem(x=x, height=5, width=1,brush='r')
win.addItem(bg1)
win.setYRange(0,110)
win.setLimits(yMin=0,yMax=110,xMin=-0.5,xMax=11)
if name == 'main':
pg.exec()
I have a windowed application that, when a button is pressed, is supposed to trigger an animation of a 3d scatter plot using matplotlib. Here's my code:
from __future__ import print_function
from PyQt5.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget, QListWidget, QPushButton, QMainWindow
from PyQt5.QtCore import QObject
from matplotlib.animation import FuncAnimation
from matplotlib import pyplot as plt
import platform
import sys
import six
import numpy as np
import mpl_toolkits.mplot3d.axes3d as p3
i = 1
def main():
global device_app, device_listbox, device_win_conf_button, device_win
device_app = QApplication(sys.argv) # Initialize QApp
device_win = QWidget()
device_win_layout = QVBoxLayout() # Layout
device_win_conf_button = QPushButton("Proceed")
device_app.aboutToQuit.connect(quit)
device_win_conf_button.clicked.connect(plotAnimator)
device_win_layout.addWidget(device_win_conf_button)
device_win.setLayout(device_win_layout) # Assign layout to window and resize
device_win.resize(500, 500)
device_win.show()
device_app.exec_() # Enter loop
def plotAnimator():
global sc_plot, x, y, z
fig = plt.figure() # Create matplotlib fig
ax = fig.add_subplot(111, projection='3d') # Add plot
ax.set_title("Example")
ax.set_xlim3d(-10, 10) # Set bounds
ax.set_ylim3d(-10, 10)
ax.set_zlim3d(-10, 10)
x = [0]
y = [0]
z = [0]
sc_plot = ax.scatter(x,y,z) # Start at 0, 0, 0
print(2)
animation = FuncAnimation(fig, updatePlot, frames=2, interval=100)
plt.show()
def updatePlot(frame):
global i
x.append(i)
y.append(i)
z.append(i)
sc_plot._offsets3d = (x, y, z)
i += 1
main()
So when I run this and click the button, the scatter plot shows up with the single point I've initialized it with at (0, 0, 0). I can interact with the plot, rotating it around and zooming in and out, yet there's no animation. Further, the following is printed to the console when I click the "Proceed" button:
QCoreApplication::exec: The event loop is already running.
I've researched this error and from what I understand it comes from trying to execute a QApplication that's already being executed. I don't see that happening in my code. The only thing I can think of is that maybe matplotlib uses PyQT to create its windows? Even if that was the case, though, I'm having trouble thinking about how I would fix it.
Any help is greatly appreciated, thanks!
I need to implement two graphs in Cartesian and polar coordinates. Everything is clear with Cartesian, but is it possible to make a polar coordinate system in pyqtgraph?
pyqtgraph does not provide by default the ability to make polar plots, I have requested the feature through the issue #452, in that discussion it is indicated that you can create that type plot easily by giving an example here.
The example is as follows:
import numpy as np
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
plot = pg.plot()
plot.setAspectLocked()
# Add polar grid lines
plot.addLine(x=0, pen=0.2)
plot.addLine(y=0, pen=0.2)
for r in range(2, 20, 2):
circle = pg.QtGui.QGraphicsEllipseItem(-r, -r, r * 2, r * 2)
circle.setPen(pg.mkPen(0.2))
plot.addItem(circle)
# make polar data
theta = np.linspace(0, 2 * np.pi, 100)
radius = np.random.normal(loc=10, size=100)
# Transform to cartesian and plot
x = radius * np.cos(theta)
y = radius * np.sin(theta)
plot.plot(x, y)
if __name__ == "__main__":
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, "PYQT_VERSION"):
QtGui.QApplication.instance().exec_()
Probably in future release pyqtgraph will offer that feature.
I can offer you to use the QPolarChart from PyQt5.QtChart. It's really easy. For example:
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtChart import QPolarChart, QChartView, QValueAxis, QScatterSeries
self.polar = QPolarChart()
chartView = QChartView(self.polar)
layout = QVBoxLayout()
layout.addWidget(chartView)
#Let's create container widget for our chart, for example QFrame
#Instead the MainWindow you should to substitute your own Widget or Main Form
self.MyFrame = QtWidgets.QFrame(MainWindow)
self.MyFrame.setGeometry(QtCore.QRect(0, 0, 1000, 1000))
self.MyFrame.setLayout(layout)
#setting axis
axisy = QValueAxis()
axisx = QValueAxis()
axisy.setRange(0,500)
axisy.setTickCount(4)
self.polar.setAxisY(axisy)
axisx.setRange(0,360)
axisx.setTickCount(5)
self.polar.setAxisX(axisx)
#Let's draw scatter series
self.polar_series = QScatterSeries()
self.polar_series.setMarkerSize(5.0)
self.polar_series.append(0, 0);
self.polar_series.append(360, 500);
#Why not draw archimedes spiral
for i in range(0,360,10):
self.polar_series.append(i, i)
self.polar.addSeries(self.polar_series)
I am trying to animate a scatter plot (it needs to be a scatter plot as I want to vary the circle sizes). I have gotten the matplotlib documentation tutorial matplotlib documentation tutorial to work in my PyQT application, but would like to introduce blitting into the equation as my application will likely run on slower machines where the animation may not be as smooth.
I have had a look at many examples of animations with blitting, but none ever use a scatter plot (they use plot or lines) and so I am really struggling to figure out how to initialise the animation (the bits that don't get re-rendered every time) and the ones that do. I have tried quite a few things, and seem to be getting nowhere (and I am sure they would cause more confusion than help!). I assume that I have missed something fairly fundamental. Has anyone done this before? Could anyone help me out splitting the figure into the parts that need to be initiated and the ones that get updates?
The code below works, but does not blit. Appending
blit=True
to the end of the animation call yields the following error:
RuntimeError: The animation function must return a sequence of Artist objects.
Any help would be great.
Regards
FP
import numpy as np
from PyQt4 import QtGui, uic
import sys
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setupAnim()
self.show()
def setupAnim(self):
self.fig = plt.figure(figsize=(7, 7))
self.ax = self.fig.add_axes([0, 0, 1, 1], frameon=False)
self.ax.set_xlim(0, 1), self.ax.set_xticks([])
self.ax.set_ylim(0, 1), self.ax.set_yticks([])
# Create rain data
self.n_drops = 50
self.rain_drops = np.zeros(self.n_drops, dtype=[('position', float, 2),
('size', float, 1),
('growth', float, 1),
('color', float, 4)])
# Initialize the raindrops in random positions and with
# random growth rates.
self.rain_drops['position'] = np.random.uniform(0, 1, (self.n_drops, 2))
self.rain_drops['growth'] = np.random.uniform(50, 200, self.n_drops)
# Construct the scatter which we will update during animation
# as the raindrops develop.
self.scat = self.ax.scatter(self.rain_drops['position'][:, 0], self.rain_drops['position'][:, 1],
s=self.rain_drops['size'], lw=0.5, edgecolors=self.rain_drops['color'],
facecolors='none')
self.animation = FuncAnimation(self.fig, self.update, interval=10)
plt.show()
def update(self, frame_number):
# Get an index which we can use to re-spawn the oldest raindrop.
self.current_index = frame_number % self.n_drops
# Make all colors more transparent as time progresses.
self.rain_drops['color'][:, 3] -= 1.0/len(self.rain_drops)
self.rain_drops['color'][:, 3] = np.clip(self.rain_drops['color'][:, 3], 0, 1)
# Make all circles bigger.
self.rain_drops['size'] += self.rain_drops['growth']
# Pick a new position for oldest rain drop, resetting its size,
# color and growth factor.
self.rain_drops['position'][self.current_index] = np.random.uniform(0, 1, 2)
self.rain_drops['size'][self.current_index] = 5
self.rain_drops['color'][self.current_index] = (0, 0, 0, 1)
self.rain_drops['growth'][self.current_index] = np.random.uniform(50, 200)
# Update the scatter collection, with the new colors, sizes and positions.
self.scat.set_edgecolors(self.rain_drops['color'])
self.scat.set_sizes(self.rain_drops['size'])
self.scat.set_offsets(self.rain_drops['position'])
if __name__== '__main__':
app = QtGui.QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())
You need to add return self.scat, at the end of the update method if you want to use FuncAnimation with blit=True. See also this nice StackOverflow post that presents an example of a scatter plot animation with matplotlib using blit.
As a side-note, if you wish to embed a mpl figure in a Qt application, it is better to avoid using the pyplot interface and to use instead the Object Oriented API of mpl as suggested in the matplotlib documentation.
This could be achieved, for example, as below, where mplWidget can be embedded as any other Qt widget in your main application. Note that I renamed the update method to update_plot to avoid conflict with the already existing method of the FigureCanvasQTAgg class.
import numpy as np
from PyQt4 import QtGui
import sys
import matplotlib as mpl
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
class mplWidget(FigureCanvasQTAgg):
def __init__(self):
super(mplWidget, self).__init__(mpl.figure.Figure(figsize=(7, 7)))
self.setupAnim()
self.show()
def setupAnim(self):
ax = self.figure.add_axes([0, 0, 1, 1], frameon=False)
ax.axis([0, 1, 0, 1])
ax.axis('off')
# Create rain data
self.n_drops = 50
self.rain_drops = np.zeros(self.n_drops, dtype=[('position', float, 2),
('size', float, 1),
('growth', float, 1),
('color', float, 4)
])
# Initialize the raindrops in random positions and with
# random growth rates.
self.rain_drops['position'] = np.random.uniform(0, 1, (self.n_drops, 2))
self.rain_drops['growth'] = np.random.uniform(50, 200, self.n_drops)
# Construct the scatter which we will update during animation
# as the raindrops develop.
self.scat = ax.scatter(self.rain_drops['position'][:, 0],
self.rain_drops['position'][:, 1],
s=self.rain_drops['size'],
lw=0.5, facecolors='none',
edgecolors=self.rain_drops['color'])
self.animation = FuncAnimation(self.figure, self.update_plot,
interval=10, blit=True)
def update_plot(self, frame_number):
# Get an index which we can use to re-spawn the oldest raindrop.
indx = frame_number % self.n_drops
# Make all colors more transparent as time progresses.
self.rain_drops['color'][:, 3] -= 1./len(self.rain_drops)
self.rain_drops['color'][:, 3] = np.clip(self.rain_drops['color'][:, 3], 0, 1)
# Make all circles bigger.
self.rain_drops['size'] += self.rain_drops['growth']
# Pick a new position for oldest rain drop, resetting its size,
# color and growth factor.
self.rain_drops['position'][indx] = np.random.uniform(0, 1, 2)
self.rain_drops['size'][indx] = 5
self.rain_drops['color'][indx] = (0, 0, 0, 1)
self.rain_drops['growth'][indx] = np.random.uniform(50, 200)
# Update the scatter collection, with the new colors,
# sizes and positions.
self.scat.set_edgecolors(self.rain_drops['color'])
self.scat.set_sizes(self.rain_drops['size'])
self.scat.set_offsets(self.rain_drops['position'])
return self.scat,
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = mplWidget()
sys.exit(app.exec_())
I cannot get pyplot to produce "cropped" images, that is, get rid of the grey left and right borders, as it is, it is not an accurate representation of the sound waveform : This sound file has no silence before and after.
My code :
import gtk
from matplotlib.figure import Figure
from numpy import arange, sin, pi
import scipy.io.wavfile as wavfile
from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
win = gtk.Window()
win.connect("destroy", lambda x: gtk.main_quit())
win.set_default_size(400,300)
win.set_title("Cropping figure")
rate, data = wavfile.read(open('/home/px/gare_du_nord-catchlak.wav', 'r'))
f = Figure()
a = f.add_subplot(111, axisbg=(0.1843, 0.3098, 0.3098))
a.plot(range(len(data)),data, color="OrangeRed", linewidth=0.5, linestyle="-")
a.axis('off')
a.autoscale_view('tight')
canvas = FigureCanvas(f) # a gtk.DrawingArea
win.add(canvas)
win.show_all()
gtk.main()
OK, I got my answer :
f.subplots_adjust(0, 0, 1, 1)
EDIT:
This also works, in fact it works even better:
a.margins(0, 0)