Update matplotlib plot with Gtk+ button event - python-3.x

I've encapsulated a matplotlib plot in a Gtk+ window and I'm trying to update that plot when a button is clicked (it's Gauss' circle problem). Trouble is, I'm not exactly sure how to get the plot to update with an event. So far I have the following.
#! /usr/bin/env python3.4
# -*- coding: utf-8 -*-
""" Main application--embed Matplotlib figure in window with UI """
import gi
gi.require_version('Gtk', '3.0')
import numpy as np
from gi.repository import Gtk, GObject
from matplotlib.figure import Figure
# make sure cairocffi is installed, pycairo doesn't support FigureCanvasGTK3Agg
from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg \
as FigureCanvas
from matplotlib.patches import Ellipse
from typing import List, Tuple
from math import sqrt
class Main(Gtk.Window):
""" Main window UI """
SIGMA = 10
def __init__(self):
Gtk.Window.__init__(self, title='Gauss\' Circle Problem')
self.connect('destroy', lambda _: Gtk.main_quit())
self.set_border_width(10)
self.set_default_size(600, 450)
# Set up the l/r box layout
self.box = Gtk.Box(spacing=10)
self.add(self.box)
# Set up the right column
self.rcolumn = Gtk.Grid()
self.box.pack_end(self.rcolumn, False, False, 1)
# Set up spin button
adjustment = Gtk.Adjustment(10, 3, 100, 1, 0, 0)
self.spinbutton = Gtk.SpinButton()
self.spinbutton.set_adjustment(adjustment)
self.rcolumn.attach(self.spinbutton, 0, 0, 1, 1)
# Set up update button
self.update_plot_button = Gtk.Button(label='Update')
self.update_plot_button.connect('clicked', self.update_sigma_event)
self.rcolumn.attach_next_to(self.update_plot_button,
self.spinbutton, Gtk.PackDirection.BTT, 1, 1)
self._add_plot()
def update_sigma_event(self, button) -> None:
""" Update sigma and replot """
self.SIGMA = self.spinbutton.get_value()
self._add_plot()
def _add_plot(self) -> None:
""" Add the plot to the window """
fig = Figure(figsize=(5, 4))
ax = fig.add_subplot(111, aspect='equal')
arr = np.zeros([self.SIGMA * 2 + 1] * 2)
points = self.collect(int(self.SIGMA), int(self.SIGMA), self.SIGMA)
# flip pixel value if it lies inside (or on) the circle
for p in points:
arr[p] = 1
# plot ellipse on top of boxes to show their centroids lie inside
circ = Ellipse(\
xy=(int(self.SIGMA), int(self.SIGMA)),
width=2 * self.SIGMA,
height=2 * self.SIGMA,
angle=0.0
)
ax.add_artist(circ)
circ.set_clip_box(ax.bbox)
circ.set_alpha(0.2)
circ.set_facecolor((1, 1, 1))
ax.set_xlim(-0.5, 2 * self.SIGMA + 0.5)
ax.set_ylim(-0.5, 2 * self.SIGMA + 0.5)
# Plot the pixel centers
ax.scatter(*zip(*points), marker='.', color='white')
# now plot the array that's been created
ax.imshow(-arr, interpolation='none', cmap='gray')
# add it to the window
canvas = FigureCanvas(fig)
self.box.pack_start(canvas, True, True, 0)
#staticmethod
def collect(x: int, y: int, sigma: float =3.0) -> List[Tuple[int, int]]:
""" create a small collection of points in a neighborhood of some
point
"""
neighborhood = []
X = int(sigma)
for i in range(-X, X + 1):
Y = int(pow(sigma * sigma - i * i, 1/2))
for j in range(-Y, Y + 1):
neighborhood.append((x + i, y + j))
return neighborhood
if __name__ == '__main__':
window = Main()
window.show_all()
Gtk.main()
I'm not exactly sure where to go from here, I just know that updating the SpinButton indeed adjusts self.SIGMA, but I don't know how to tell matplotlib to update the plot in the window.
Also, this is what it looks like currently if you aren't able to run it (I'm also trying to vertically center the two button widgets in the right column :P):

This is a solution I've found to my problem:
#! /usr/bin/env python3.4
# -*- coding: utf-8 -*-
""" Main application--embed Matplotlib figure in window with UI """
import gi
gi.require_version('Gtk', '3.0')
import numpy as np
from gi.repository import Gtk, GObject
from matplotlib.figure import Figure
# make sure cairocffi is installed, pycairo doesn't support FigureCanvasGTK3Agg
from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg \
as FigureCanvas
from matplotlib.patches import Ellipse
from typing import List, Tuple, Union
from math import sqrt
class Main(Gtk.Window):
""" Main window UI """
SIGMA = 10
INVERT = -1
def __init__(self) -> None:
Gtk.Window.__init__(self, title='Gauss\' Circle Problem')
self.connect('destroy', lambda _: Gtk.main_quit())
self.set_border_width(10)
self.set_default_size(650, 500)
# Set up the l/r box layout
self.box = Gtk.Box(spacing=10)
self.add(self.box)
# Set up the right column
self.rcolumn = Gtk.VBox(spacing=0)
self.rcolumn.set_spacing(10)
self.box.pack_end(self.rcolumn, False, False, 20)
# Set up spin button
adjustment = Gtk.Adjustment(self.SIGMA, 1, 30, 1, 0, 0)
self.spinbutton = Gtk.SpinButton()
self.spinbutton.set_adjustment(adjustment)
self.rcolumn.pack_start(self.spinbutton, False, False, 0)
# Set up invert checkbox
self.invertbutton = Gtk.CheckButton('Invert')
self.invertbutton.set_active(True)
self.invertbutton.connect('toggled', self.switch_toggle_parity, 'invert')
self.rcolumn.add(self.invertbutton)
# Set up update button
self.update_plot_button = Gtk.Button(label='Update')
self.update_plot_button.connect('clicked', self.update_sigma_event)
self.rcolumn.add(self.update_plot_button)
self.initial_plot()
def calculate(self) -> None:
""" Re-calculate using the formula """
arr = np.zeros([self.SIGMA * 2 + 1] * 2)
points = self.collect(int(self.SIGMA), int(self.SIGMA), self.SIGMA)
# flip pixel value if it lies inside (or on) the circle
for p in points:
arr[p] = 1
# plot ellipse on top of boxes to show their centroids lie inside
circ = Ellipse(
xy=(int(self.SIGMA), int(self.SIGMA)),
width=2 * self.SIGMA,
height=2 * self.SIGMA,
angle=0.0
)
self.ax.clear()
self.ax.add_artist(circ)
circ.set_clip_box(self.ax.bbox)
circ.set_alpha(0.2)
circ.set_facecolor((1, 1, 1))
self.ax.set_xlim(-0.5, 2 * self.SIGMA + 0.5)
self.ax.set_ylim(-0.5, 2 * self.SIGMA + 0.5)
# Plot the pixel centers
self.ax.scatter(*zip(*points), marker='.',
color='white' if self.INVERT == -1 else 'black')
# now plot the array that's been created
self.ax.imshow(self.INVERT * arr, interpolation='none', cmap='gray')
def initial_plot(self) -> None:
""" Set up the initial plot; only called once """
self.fig = Figure(figsize=(5, 4))
self.canvas = FigureCanvas(self.fig)
self.box.pack_start(self.canvas, True, True, 0)
self.ax = self.fig.add_subplot(111, aspect='equal')
self.calculate()
self.draw_plot()
def update_sigma_event(self, button: Union[Gtk.Button, None] =None) -> None:
""" Update sigma and trigger a replot """
self.SIGMA = int(self.spinbutton.get_value())
self.calculate()
self.draw_plot()
def switch_toggle_parity(self, button: Union[Gtk.CheckButton, None] =None,
name: str ='') -> None:
""" Switch the parity of the plot before update """
self.INVERT *= -1
def draw_plot(self) -> None:
""" Draw or update the current plot """
self.fig.canvas.draw()
#staticmethod
def collect(x: int, y: int, sigma: float =3.0) -> List[Tuple[int, int]]:
""" create a small collection of points in a neighborhood of some
point
"""
neighborhood = []
X = int(sigma)
for i in range(-X, X + 1):
Y = int(pow(sigma * sigma - i * i, 1/2))
for j in range(-Y, Y + 1):
neighborhood.append((x + i, y + j))
return neighborhood
if __name__ == '__main__':
window = Main()
window.show_all()
Gtk.main()
I've also added a button that swaps the parity of the binary image plot and re-structured the method calls.
It's a slow/simple start, but I suppose we all have to start somewhere! Comments and suggestions welcome.

Might be not entirely adequate to what you're doing, but there's a similarly simple yet faster algorithm for Gauss circle problem (with some Java source code and an ugly but handy illustration): https://stackoverflow.com/a/42373448/5298879
It's around 3.4x faster than counting points in one of the quarters, plus the center, plus the ones on the axis, that you're doing now, while taking just one more line of code.
You simply imagine an inscribed square and count only one-eighth of what's outside that square inside that circle.

Related

Select points from a scattr plot using mouse cross hair - PyQt

I am new to PyQt and Im developing a utility where a user can import data from an excel file and plot its X and Y in a 2d scatter plot using below code:
def plot_2d_scatter(graphWidget,x,z,color=(66, 245, 72)):
graphWidget.clear()
brush = pg.mkBrush(color)
scatter = pg.ScatterPlotItem(size=5, brush=brush)
scatter.addPoints(x,z)
graphWidget.addItem(scatter)
Now I want a functionality which will allow the user to move his mouse over the scatter plot points using a cross hair / pointer / etc and select points on the scatter plot.
Whenever the user does a left click on the crosshair / marker on the scatter plot, I want its x,y coordinates to be saved for further use.
I have already tried the below snippet from somewhere on internet for using mouse events and getting my scatter points , but this didnt give me a cross hair that falls on my scatter points
def mouseMoved(self, evt):
pos = evt
if self.plotWidget.sceneBoundingRect().contains(pos):
mousePoint = self.plotWidget.plotItem.vb.mapSceneToView(pos)
mx = np.array([abs(float(i) - float(mousePoint.x())) for i in self.plotx])
index = mx.argmin()
if index >= 0 and index < len(self.plotx):
self.cursorlabel.setHtml(
"<span style='font-size: 12pt'>x={:0.1f}, \
<span style='color: red'>y={:0.1f}</span>".format(
self.plotx[index], self.ploty[index])
)
self.vLine.setPos(self.plotx[index])
self.hLine.setPos(self.ploty[index])
Any guidance is thankfully appreciated
my best fast effort, never used pg untill today:
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QDesktopWidget, QWidget
from PyQt5.QtCore import Qt
from PyQt5 import QtGui, QtCore, QtWidgets
import pyqtgraph as pg
import numpy as np
class MyApp(QMainWindow):
def __init__(self, parent=None):
super(MyApp, self).__init__(parent)
self.resize(781, 523)
self.graphWidget = pg.PlotWidget()
self.setCentralWidget(self.graphWidget)
self.show()
self.x = [1,2,3,4,5,6,7,8,9,5,6,7,8]
self.y = [1,2,3,4,5,6,7,8,9,5,6,7,8]
# self.y.reverse()
self.plot_2d_scatter(self.graphWidget, self.x, self.y)
self.cursor = Qt.CrossCursor
# self.cursor = Qt.BlankCursor
self.graphWidget.setCursor(self.cursor)
# Add crosshair lines.
self.crosshair_v = pg.InfiniteLine(angle=90, movable=False)
self.crosshair_h = pg.InfiniteLine(angle=0, movable=False)
self.graphWidget.addItem(self.crosshair_v, ignoreBounds=True)
self.graphWidget.addItem(self.crosshair_h, ignoreBounds=True)
self.cursorlabel = pg.TextItem()
self.graphWidget.addItem(self.cursorlabel)
self.proxy = pg.SignalProxy(self.graphWidget.scene().sigMouseMoved, rateLimit=60, slot=self.update_crosshair)
self.mouse_x = None
self.mouse_y = None
def plot_2d_scatter(self,graphWidget,x,z,color=(66, 245, 72)):
# graphWidget.clear()
brush = pg.mkBrush(color)
scatter = pg.ScatterPlotItem(size=5, brush=brush)
scatter.addPoints(x,z)
graphWidget.addItem(scatter)
def update_crosshair(self, e):
pos = e[0]
if self.graphWidget.sceneBoundingRect().contains(pos):
mousePoint = self.graphWidget.plotItem.vb.mapSceneToView(pos)
mx = np.array([abs(float(i) - float(mousePoint.x())) for i in self.x])
index = mx.argmin()
if index >= 0 and index < len(self.x):
self.cursorlabel.setText(
str((self.x[index], self.y[index])))
self.crosshair_v.setPos(self.x[index])
self.crosshair_h.setPos(self.y[index])
self.mouse_x = self.crosshair_v.setPos(self.x[index])
self.mouse_y = self.crosshair_h.setPos(self.y[index])
self.mouse_x = (self.x[index])
self.mouse_y = (self.y[index])
def mousePressEvent(self, e):
if e.buttons() & QtCore.Qt.LeftButton:
print('pressed')
# if self.mouse_x in self.x and self.mouse_y in self.y:
print(self.mouse_x, self.mouse_y)
if __name__ == '__main__':
app = QApplication(sys.argv)
myapp = MyApp()
# myapp.show()
try:
sys.exit(app.exec_())
except SystemExit:
print('Closing Window...')
it just prints out the coordinate of pressed point in graph
copied from https://www.pythonguis.com/faq/pyqt-show-custom-cursor-pyqtgraph/ and your piece of code result looks like:
there are other examples on SO like Trying to get cursor with coordinate display within a pyqtgraph plotwidget in PyQt5 and others

Python plots graph into button instead of figure

I'm a total beginner to python (started some days ago).
This little program is supposed to draw sinus waves, and the buttons at the bottom should increase / decrease the frequency of the plotted sinus. But my program plots the sinus into the button, for whatever reason. Where is the mistake? I've tried so much already... Thanks in advance ❤
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
class Function:
def __init__(self, amplitude, frequency):
self.amplitude = amplitude
self.frequency = frequency
self.time = np.arange(0.0, 1.0, 0.001)
self.signal= amplitude*np.sin(2*np.pi*frequency*self.time)
def status(self):
print("The frequency is: ", self.frequency)
print("The amplitude is: ", self.amplitude)
def setFrequency(self, frequency):
self.frequency = frequency
def setAmplitue(self, amplitude):
self.amplitude = amplitude
def show(self):
plt.plot(self.time, self.signal, linewidth = 2)
plt.ylabel("Sinus")
plt.xlabel("x")
pass
func = Function(1, 20)
func.show()
def IncreaseFrequency(event):
global func
if (func.frequency < 100):
func.setFrequency(func.frequency + 10)
else:
func.setFrequency(10)
func.show()
plt.draw()
pass
def LowerFrequency(event):
global func
if (func.frequency > 10):
func.setFrequency(func.frequency - 10)
else:
func.setFrequency(100)
func.show()
plt.draw()
pass
buttonIncrSize = plt.axes([0.7, 0.01, 0.1, 0.05])
buttonLowerSize = plt.axes([0.81, 0.01, 0.1, 0.05])
buttonIncrFreq = Button(buttonIncrSize, 'Next')
buttonLowerFreq = Button(buttonLowerSize, 'Previous')
buttonIncrFreq.on_clicked(IncreaseFrequency)
buttonLowerFreq.on_clicked(LowerFrequency)
plt.show()
First what looks kind of weird is that after lowering or increasing your frequency, you never update the self.signal, which should change if your frequency changes.
Secondly, from what I could see on this link, plt.plot returns an Line2D object which you can use to set the y data on your plot using line2D.set_ydata(new_ydata).
After changing the y data, just update the plot using plt.draw() and that should work.
EDIT: Indeed I didn't have access to my PC but ran this now and didn't work. After some search:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
import matplotlib
class Function:
def __init__(self, amplitude, frequency):
self.amplitude = amplitude
self.frequency = frequency
self.time = np.arange(0.0, 1.0, 0.001)
self.signal = amplitude * np.sin(2 * np.pi * frequency * self.time)
def status(self):
print("The frequency is: ", self.frequency)
print("The amplitude is: ", self.amplitude)
def setFrequency(self, frequency):
self.frequency = frequency
self.signal = self.amplitude * np.sin(2 * np.pi * frequency * self.time)
def setAmplitude(self, amplitude):
self.amplitude = amplitude
self.signal = self.amplitude * np.sin(2 * np.pi * frequency * self.time)
def show(self):
plt.cla()
plt.plot(self.time,self.signal,lw=2)
plt.draw()
plt.ylabel("Sinus")
plt.xlabel("x")
def IncreaseFrequency(event):
global func
if (func.frequency < 100):
func.setFrequency(func.frequency + 10)
else:
func.setFrequency(10)
func.show()
def LowerFrequency(event):
global func
if (func.frequency > 10):
func.setFrequency(func.frequency - 10)
else:
func.setFrequency(100)
func.show()
fig, ax = plt.subplots()
func = Function(1, 20)
func.show()
buttonIncrSize = plt.axes([0.7, 0.01, 0.1, 0.05])
buttonLowerSize = plt.axes([0.81, 0.01, 0.1, 0.05])
buttonIncrFreq = Button(buttonIncrSize, 'Next')
buttonLowerFreq = Button(buttonLowerSize, 'Previous')
buttonIncrFreq.on_clicked(IncreaseFrequency)
buttonLowerFreq.on_clicked(LowerFrequency)
plt.sca(fig.axes[0])
plt.show()
Main change being the plt.sca(fig.axes[0]) which allows to select the current axis for plt. When you run plt.axes() it sets the current axes of plt to the button, hence subsequently plotting your graph in that button.
Also added plt.cla() which clears the current plotting area.

Plot text in 3d-plot that does not scale or move

Hello Pyqtgraph community,
I want to be able to create a "fixed" text window in a 3D interactive plot generated in PyQtGraph.
This text window will contain simulation-related information and should be visible at all times, regardless if you zoom in/out or pan to the left or right; and the location of the window should not change.
So far all the solutions I have found, create a text object that moves as the scaling of the axes changes. For example, the code below prints text on 3D axis, but once you zoom in/out the text moves all over the place. Any ideas would be greatly appreciated.
Thanks in advance
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl
from pyqtgraph.opengl.GLGraphicsItem import GLGraphicsItem
class GLTextItem(GLGraphicsItem):
"""
Class for plotting text on a GLWidget
"""
def __init__(self, X=None, Y=None, Z=None, text=None):
GLGraphicsItem.__init__(self)
self.setGLOptions('translucent')
self.text = text
self.X = X
self.Y = Y
self.Z = Z
def setGLViewWidget(self, GLViewWidget):
self.GLViewWidget = GLViewWidget
def setText(self, text):
self.text = text
self.update()
def setX(self, X):
self.X = X
self.update()
def setY(self, Y):
self.Y = Y
self.update()
def setZ(self, Z):
self.Z = Z
self.update()
def paint(self):
self.GLViewWidget.qglColor(QtCore.Qt.white)
self.GLViewWidget.renderText(self.X, self.Y, self.Z, self.text)
if __name__ == '__main__':
# Create app
app = QtGui.QApplication([])
w1 = gl.GLViewWidget()
w1.resize(800, 800)
w1.show()
w1.setWindowTitle('Earth 3D')
gl_txt = GLTextItem(10, 10, 10, 'Sample test')
gl_txt.setGLViewWidget(w1)
w1.addItem(gl_txt)
while w1.isVisible():
app.processEvents()
So I was finally able to find a solution. What needs to be done is the following:
Subclass the GLViewWidget
From the derived class, overload the paintGL() so that it uses the member function renderText() to render text on the screen every time the paingGL() is called.
renderText() is overloaded to support both absolute screen coordinates, as well as axis-based coordinates:
i) renderText(int x, int y, const QString &str, const QFont &font = QFont()): plot based on (x, y) window coordinates
ii) renderText(double x, double y, double z, const QString &str, const QFont &font = QFont()): plot on (x, y, z) scene coordinates
You might want to use the QtGui.QFontMetrics() class to get the dimensions of the rendered text so you can place it in a location that makes sense for your application, as indicated in the code below.
from pyqtgraph.opengl import GLViewWidget
import pyqtgraph.opengl as gl
from PyQt5.QtGui import QColor
from pyqtgraph.Qt import QtCore, QtGui
class GLView(GLViewWidget):
"""
I have implemented my own GLViewWidget
"""
def __init__(self, parent=None):
super().__init__(parent)
def paintGL(self, *args, **kwds):
# Call parent's paintGL()
GLViewWidget.paintGL(self, *args, **kwds)
# select font
font = QtGui.QFont()
font.setFamily("Tahoma")
font.setPixelSize(21)
font.setBold(True)
title_str = 'Screen Coordinates'
metrics = QtGui.QFontMetrics(font)
m = metrics.boundingRect(title_str)
width = m.width()
height = m.height()
# Get window dimensions to center text
scrn_sz_width = self.size().width()
scrn_sz_height = self.size().height()
# Render text with screen based coordinates
self.qglColor(QColor(255,255,0,255))
self.renderText((scrn_sz_width-width)/2, height+5, title_str, font)
# Render text using Axis-based coordinates
self.qglColor(QColor(255, 0, 0, 255))
self.renderText(0, 0, 0, 'Axis-Based Coordinates')
if __name__ == '__main__':
# Create app
app = QtGui.QApplication([])
w = GLView()
w.resize(800, 800)
w.show()
w.setWindowTitle('Earth 3D')
w.setCameraPosition(distance=20)
g = gl.GLGridItem()
w.addItem(g)
while w.isVisible():
app.processEvents()

Control imbedded figure size on Tkinter canvas?

I dont seem to be able to fully control the figure size on my embedded figure on a Tkinter canvas.
Heres what i want to do. Maybe you have another suggestion, than using the embedded figure.
Im trying to make a simple script to make some visual content. Right now its just a pixel mapping of falling squares in random colors.
My problem is that i need it to be fullscreen, and i can for my life not figure out how.
It is mainly about this piece of code, i think:
fig = plt.figure(figsize=(40,40))
im = plt.imshow(top.img) # later use a.set_data(new_data)
plt.tick_params(
axis='both', # changes apply to the x-axis
which='both', # both major and minor ticks are affected
bottom='off', # ticks along the bottom edge are off
top='off', # ticks along the top edge are off
left='off',
right='off',
labelleft='off',
labelbottom='off') # labels along the bottom edge are off
# a tk.DrawingArea
canvas = FigureCanvasTkAgg(fig, master=top)
canvas.show()
canvas.get_tk_widget().pack(side=gui.TOP , fill=gui.BOTH, expand=1)
It seems that figsize has a limit to how big it goes.
Heres all the code:
import matplotlib
import numpy as np
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib import figure
import matplotlib.pyplot as plt
import tkinter as gui
from math import floor
import time
class FullScreenApp(object):
def __init__(self, master, **kwargs):
self.master=master
pad=3
self._geom='200x200+0+0'
master.geometry("{0}x{1}+0+0".format(
master.winfo_screenwidth()-pad, master.winfo_screenheight()-pad))
master.bind('<Escape>',self.toggle_geom)
def toggle_geom(self,event):
geom=self.master.winfo_geometry()
print(geom,self._geom)
self.master.geometry(self._geom)
self._geom=geom
def flashBox(color,oy,ox):
global j1, j2
top.img[0+oy:j2+oy,0+ox:j1+ox] = color
im.set_data(top.img)
canvas.draw();
time.sleep(t)
top.img[0+oy:j2+oy,0+ox:j1+ox] = [0,0,0]
im.set_data(top.img)
canvas.draw();
return top.img
def drawBox(color,oy,ox):
global j1, j2
top.img[0+oy:j2+oy,0+ox:j1+ox] = color
im.set_data(top.img)
canvas.draw();
time.sleep(t)
return top.img
def resetBox(oy,ox):
global j1, j2
top.img[0+oy:j2+oy,0+ox:j1+ox] = [0,0,0]
im.set_data(top.img)
canvas.draw();
return top.img
def drawColumn(color,u):
global gridsize, j1, j2
for l in range(gridsize):
im.set_data(flashBox(color,j2*l,j1*u))
time.sleep(t2)
top = gui.Tk()
t = 0.1
t2 = 0.00001
x = 40
y = 40
gridsize = 10
j1 = floor(x // gridsize)
j2 = floor(y // gridsize)
top.img = np.zeros([y,x,3],dtype=np.uint8)
top.img.fill(0) # or img[:] = 255
fig = plt.figure(figsize=(40,40))
im = plt.imshow(top.img) # later use a.set_data(new_data)
plt.tick_params(
axis='both', # changes apply to the x-axis
which='both', # both major and minor ticks are affected
bottom='off', # ticks along the bottom edge are off
top='off', # ticks along the top edge are off
left='off',
right='off',
labelleft='off',
labelbottom='off') # labels along the bottom edge are off
# a tk.DrawingArea
canvas = FigureCanvasTkAgg(fig, master=top)
canvas.show()
canvas.get_tk_widget().pack(side=gui.TOP , fill=gui.BOTH, expand=1)
#app=FullScreenApp(top)
while True:
for n in range(gridsize):
top.update()
p = np.random.randint(0,99)
#drawColumn([np.random.random_integers(0,255),np.random.random_integers(0,255),np.random.random_integers(0,255)],np.random.random_integers(0,gridsize-1))
if p > 10:
flashBox([np.random.random_integers(0,255),np.random.random_integers(0,255),np.random.random_integers(0,255)],j1*np.random.random_integers(0,gridsize-1),j2*np.random.random_integers(0,gridsize-1))
else:
flashBox([0,0,0],0,0)

Draw line: retrieve the covered pixels

I want to draw a line on a widget:
from PyQt4 import QtGui, QtCore
class LineLabel(QtGui.QLabel):
def __init__(self,parent=None):
super(LineLabel,self).__init__(parent)
self.setMinimumSize(100,100)
self.setMaximumSize(100,100)
def paintEvent(self,e):
painter=QtGui.QPainter(self)
pen = QtGui.QPen()
pen.setWidth(5)
painter.setPen(pen)
painter.drawLine(10,10,90,90)
painter.end()
def test():
form = QtGui.QWidget()
label = LineLabel(form)
form.show()
return form
import sys
app = QtGui.QApplication(sys.argv)
window =test()
sys.exit(app.exec_())
What is the best way to get a list of the pixels that are covered by the line?
Update from the comments:
I don't need to know the pixels directly in between the start and end point but all those pixels that are changed to black (which are more pixels because the line has a certain width).
My overall goal is a fast way to know which pixels on the widget are black. Iterating over the pixels of the image and querying the color is much slower than reading the color value from a list in which the colors are stored: For me 1.9 seconds for an image with 1 million pixels to 0.23 seconds for a list with 1 million entries. Therefore I must update that list after every change of the image on the widget such as by drawing a line.
Answers that refer to a QGraphicsItem in a QGraphicsScene are also helpful.
You may use a linear equation to find the point you want in the line. I think that there is no reference to a draw line.
from PyQt4 import QtGui
from PyQt4.QtGui import QColor, QPaintEvent
m_nInitialX = 0.0
m_nInitialY = 0.0
# my line abstraction
class MyLine:
x1, y1, x2, y2 = .0, .0, .0, .0
width = .0
px, py = (.0, .0)
draw_point = False
def __init__(self, x1, y1, x2, y2, width):
self.x1, self.y1, self.x2, self.y2 = (x1, y1, x2, y2)
self.width = width
def is_in_line(self, x, y):
# mark a position in the line
m = (self.y2 - self.y1) / (self.x2 - self.x1)
print(m*(x-self.x1)-(y-self.y1))
if abs((m*(x-self.x1) - (y-self.y1))) <= self.width/2:
self.draw_point = True
return True
else:
return False
def add_red_point(self, x, y):
self.px, self.py = (x, y)
def draw(self, widget):
painter = QtGui.QPainter(widget)
pen = QtGui.QPen()
pen.setWidth(self.width)
painter.setPen(pen)
painter.drawLine(self.x1, self.y1, self.y2, self.y2)
if self.draw_point:
pen.setColor(QColor(255, 0, 0))
painter.setPen(pen)
painter.drawPoint(self.px, self.py)
painter.end()
line = MyLine(10, 10, 90, 90, width=10) # <-- my line abstraction
class LineLabel(QtGui.QLabel):
def __init__(self, parent=None):
super(LineLabel, self).__init__(parent)
self.setMinimumSize(100, 100)
self.setMaximumSize(100, 100)
# always redraw when needed
def paintEvent(self, e):
print("draw!")
line.draw(self)
def mousePressEvent(self, event):
# mark clicked position in line
m_nInitialX = event.pos().x()
m_nInitialY = event.pos().y()
if line.is_in_line(m_nInitialX, m_nInitialY):
line.add_red_point(m_nInitialX, m_nInitialY)
self.repaint()
def test():
form = QtGui.QWidget()
label = LineLabel(form)
form.show()
return form
import sys
app = QtGui.QApplication(sys.argv)
window = test()
sys.exit(app.exec_())

Resources