Suppose you have a rectangular pyqtgraph roi instance with some data:
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
data = np.random.random(size=(50,50,50))
app = QtGui.QApplication([])
w = pg.ImageView()
roi = pg.RectROI([20, 20], [20, 20], pen=(0,9))
roi.addRotateHandle([1, 0], [0.5, 0.5])
w.setImage(data)
w.addItem(roi)
w.show()
How can I extract the 4 corner coordinates of the roi after scaling/rotating it? It think it is possible to calculate them trigonometrically after calling
pos_x, pos_y = roi.pos()
angle = roi.angle()
size_x, size_y = roi.size()
However, it is not that straight forward since the angle can take values >360° etc. I feel like I have missed some build-in solution.
smiet
i am looking for something similar, but after looking into the documentation, source code and web, i think you are indeed left with your trigonometrical solution. nevertheless you could save two lines of code by calling
roi.getState()
which holds your wanted information in a dictionary.
regarding your problem with angles over 360° - shouldn't the modulo operator do the trick?
angle = 365 % 360
..or did i get your problem wrong?
Related
I have a gray scale image that I want to rotate. However, I need to do optimization on it. Therefore, I cannot use pillow or opencv.
I want to reshape this image using python with numpy.reshape into an one dimensional vector (where I use the default settings C-style reshape).
And thereafter, I want to rotate this image around a point using matrix multiplication and addition, i.e. it should be something like
rotated_image_vector = A # vector + b # (or the equivalent in homogenious coordinates).
After this operation I want to reshape the outcome back to two dimensions and have the rotated image.
It would be best if it would as well use linear interpolation between the pixels that do not fit exactly to an other pixel.
The mathematical theory tells it is possible, and I believe there is a very elegant solution to this problem, but I do not see how to create this matrix. Did anyone already have this problem or sees an immediate solution?
Thanks a lot,
Eike
I like your approach but there is a slight misconception in it. What you want to transform are not the pixel values themselves but the coordinates. So you don't reshape your image but rather do a np.indices on it to obtain coordinates to each pixel. For those a rotation around a point looks like
rotation_matrix#(coordinates-fixed_point)+fixed_point
except that I have to transpose a bit to get the dimensions to align. The cove below is a slight adoption of my code in this answer.
As an example I am going to use the Wikipedia-logo-v2 by Nohat. It is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported license.
First I read in the picture, swap x and y axis to not get mad and rotate the coordinates as described above.
import numpy as np
import matplotlib.pyplot as plt
import itertools
image = plt.imread('wikipedia.jpg')
image = np.swapaxes(image,0,1)/255
fixed_point = np.array(image.shape[:2], dtype='float')/2
points = np.moveaxis(np.indices(image.shape[:2]),0,-1).reshape(-1,2)
a = 2*np.pi/8
A = np.array([[np.cos(a),-np.sin(a)],[np.sin(a),np.cos(a)]])
rotated_coordinates = (A#(points-fixed_point.reshape(1,2)).T).T+fixed_point.reshape(1,2)
Now I set up a little class to interpolate between the pixels that do not fit exactly to an other pixel. And finally I swap the axis back and plot it.
class Image_knn():
def fit(self, image):
self.image = image.astype('float')
def predict(self, x, y):
image = self.image
weights_x = [(1-(x % 1)).reshape(*x.shape,1), (x % 1).reshape(*x.shape,1)]
weights_y = [(1-(y % 1)).reshape(*x.shape,1), (y % 1).reshape(*x.shape,1)]
start_x = np.floor(x)
start_y = np.floor(y)
return sum([image[np.clip(np.floor(start_x + x), 0, image.shape[0]-1).astype('int'),
np.clip(np.floor(start_y + y), 0, image.shape[1]-1).astype('int')] * weights_x[x]*weights_y[y]
for x,y in itertools.product(range(2),range(2))])
image_model = Image_knn()
image_model.fit(image)
transformed_image = image_model.predict(*rotated_coordinates.T).reshape(*image.shape)
plt.imshow(np.swapaxes(transformed_image,0,1))
And I get a result like this
Possible Issue
The artifact in the bottom left that looks like one needs to clean the screen comes from the following problem: When we rotate it can happen that we don't have enough pixels to paint the lower left. What we do by default in image_knn is to clip the coordinates to an area where we have information. That means when we ask image knn for pixels coming from outside the image it gives us the pixels at the boundary of the image. This looks good if there is a background but if an object touches the edge of the picture it looks odd like here. Just something to keep in mind when using this.
Thank you for your answer!
But actually it is not a misconception that you could let this roation be represented by a matrix multiplication with the reshaped vector.
I used your code to generate such a matrix (its surely not the most efficient way but it works, most likely you see a more efficient implementation immediately XD. You see I really need it as a matix multiplication :-D).
What I basically did is to generate the representation matrix of the linear transformation, by computing how every of the 100*100 basis images (i.e. the image with zeros everywhere und a one) is mapped by your transformation.
import sys
import numpy as np
import matplotlib.pyplot as plt
import itertools
angle = 2*np.pi/6
image_expl = plt.imread('wikipedia.jpg')
image_expl = image_expl[:,:,0]
plt.imshow(image_expl)
plt.title("Image")
plt.show()
image_shape = image_expl.shape
pixel_number = image_shape[0]*image_shape[1]
rot_mat = np.zeros((pixel_number,pixel_number))
for i in range(pixel_number):
vector = np.zeros(pixel_number)
vector[i] = 1
image = vector.reshape(*image_shape)
fixed_point = np.array(image.shape, dtype='float')/2
points = np.moveaxis(np.indices(image.shape),0,-1).reshape(-1,2)
a = -angle
A = np.array([[np.cos(a),-np.sin(a)],[np.sin(a),np.cos(a)]])
rotated_coordinates = (A#(points-fixed_point.reshape(1,2)).T).T+fixed_point.reshape(1,2)
x,y = rotated_coordinates.T
image = image.astype('float')
weights_x = [(1-(x % 1)).reshape(*x.shape), (x % 1).reshape(*x.shape)]
weights_y = [(1-(y % 1)).reshape(*x.shape), (y % 1).reshape(*x.shape)]
start_x = np.floor(x)
start_y = np.floor(y)
transformed_image_returned = sum([image[np.clip(np.floor(start_x + x), 0, image.shape[0]-1).astype('int'),
np.clip(np.floor(start_y + y), 0, image.shape[1]-1).astype('int')] * weights_x[x]*weights_y[y]
for x,y in itertools.product(range(2),range(2))])
rot_mat[:,i] = transformed_image_returned
if i%100 == 0: print(int(100*i/pixel_number), "% finisched")
plt.imshow((rot_mat # image_expl.reshape(-1)).reshape(image_shape))
Thank you again :-)
I'm trying to limit how much a ViewBox can zoom in/out and how much it can be moved.
I know that I must use setLimits() and I've read the documentation here
https://pyqtgraph.readthedocs.io/en/latest/graphicsItems/viewbox.html#pyqtgraph.ViewBox.setLimits
While the panning limits are pretty self evident, I can't really understand how the scaling limits work.
What's the unit of measure? Is it pixels? Percentage?
I've reached a usable point with these values, but not understanding why is bugging me!
view.setLimits(xMin=-image.shape[0]*0.05, xMax=image.shape[0]*1.05,
minXRange=100, maxXRange=2000,
yMin=-image.shape[1]*0.05, yMax=image.shape[1]*1.05,
minYRange=100, maxYRange=2000)
I think it's a more theoretical question than anything else, but in case you want to try some code, here it is
# import the necessary packages
from pyqtgraph.graphicsItems.ImageItem import ImageItem
from pyqtgraph.graphicsItems.LinearRegionItem import LinearRegionItem
import requests
import numpy as np
import cv2
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
image = cv2.imread('aggraffatura.jpg') # Change the picture here!
image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
app = QtGui.QApplication([])
## Create window with GraphicsView widget
w = pg.GraphicsView()
w.show()
w.resize(image.shape[0]/2, image.shape[1]/2) # Depending on the picture you may not need to resize
w.setWindowTitle('Test')
view = pg.ViewBox()
view.setLimits(xMin=-image.shape[0]*0.05, xMax=image.shape[0]*1.05,
minXRange=100, maxXRange=2000,
yMin=-image.shape[1]*0.05, yMax=image.shape[1]*1.05,
minYRange=100, maxYRange=2000)
w.setCentralItem(view)
## lock the aspect ratio
view.setAspectLocked(True)
## Add image item
item = ImageItem(image)
view.addItem(item)
## Add line item
line = LinearRegionItem()
view.addItem(line)
def mouseClicked(evt):
pos = evt[0]
print(pos)
proxyClicked = pg.SignalProxy(w.scene().sigMouseClicked, rateLimit=60, slot=mouseClicked)
## Start Qt event loop unless running in interactive mode.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
What's the unit of measure? Is it pixels? Percentage?
Short answer: The unit of measure of scaling limits is the same as the panning limits.
Long answer:
In class ViewBox, method setLimits calls method updateViewRange, which update view range to match the target view range as closely as possible, given aspect ratio constraints. Inside updateViewRange method, there is a section which loop through both axis and set the max view range to the smaller of max view range (the max scaling limit) and the absolute difference of lower and upper bounds (i.e. max-min, the difference of panning limits) (If scaling limit is not given, than it will be set to the difference of panning limits). Since the two limits can be interchangeable, they should have the same unit of measure.
Only by checking the source code one can see that max range cannot be larger than bounds, if they are given. This piece of information should be added to the document.
Note: when you zoom in to the limit you are actually setting the view range to the minRange of scaling limit.
Example: Here I will use op's example to illustrate the concept. Download this image and rename it to '500x500' to test the example.. On start you should see that the view range is set to maxRange(400px) which is the diameter of the green circle. By zooming in, you should see that the view range can never be smaller than the red circle, which is 100px in diameter. The panning limit is set to the shape of the image, i.e. 500 X 500px.
# import the necessary packages
from pyqtgraph.graphicsItems.ImageItem import ImageItem
from pyqtgraph.graphicsItems.LinearRegionItem import LinearRegionItem
import requests
import numpy as np
import cv2
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
# Name the image to 500x500
image = cv2.imread('500x500.jpg') # Change the picture here!
image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
app = QtGui.QApplication([])
## Create window with GraphicsView widget
w = pg.GraphicsView()
w.show()
w.resize(image.shape[0], image.shape[1]) # Depending on the picture you may not need to resize
w.setWindowTitle('Test')
view = pg.ViewBox()
view.setLimits(xMin=0, xMax=image.shape[0],
minXRange=100, maxXRange=400,
yMin=0, yMax=image.shape[1],
minYRange=100, maxYRange=400)
w.setCentralItem(view)
## lock the aspect ratio
view.setAspectLocked(True)
## Add image item
item = ImageItem(image)
view.addItem(item)
## Add line item
line = LinearRegionItem()
view.addItem(line)
def mouseClicked(evt):
pos = evt[0]
print(pos)
proxyClicked = pg.SignalProxy(w.scene().sigMouseClicked, rateLimit=60, slot=mouseClicked)
## Start Qt event loop unless running in interactive mode.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
I try to implement some kind of mesh visualiser for Finite-Element-Programms in Python. For this, I want to use PyQtGraph. I was able to implement a first version of the Visualiser which is able to plot a 3D mesh as shown in the picture below.
However, operations such as zooming and rotating take quite long for larger meshes. I plot the mesh using GLLinePlotItem. I guess the performance is poor due to the huge amount of lines generated with large meshes.
I am wondering whether there is an efficient way to display my mesh rather than using the GLLineItem. I had a look at GLMeshItem, however, this represents the mesh using triangles and not quads.
Here is my code for the visualiser:
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl
import pyqtgraph as pg
import numpy as np
import sys
class Visualizer(object):
def __init__(self):
self.app = QtGui.QApplication(sys.argv)
self.w = gl.GLViewWidget()
self.w.opts['distance'] = 400
self.w.setWindowTitle('Mesh Visualiser')
self.w.setGeometry(0, 110, 1920, 1080)
self.w.show()
def start(self):
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
def add_line_item(self,pts,width):
item = gl.GLLinePlotItem(pos=pts, width=width, antialias=False)
self.w.addItem(item)
if __name__ == '__main__':
v = Visualizer()
# Code which generates the GlLineItems is called here ...
# ...
v.start()
I also have a code which is called at the position of the comment in the code and generates the GlLinePlotItems from the nodes of the mesh. The GlLinePlotItems are added to the GlViewWidget using the `add_line_item() method.
I am new to pyqtGraph and using LinearRegionItem for selection. Is there a way i can get data for curves only for selection ?
For me getting data which lies between selection is important to process.
Any help of pointer in right direction will be helpful
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
#
app = QtGui.QApplication([])
#
win = pg.GraphicsWindow()
win.resize(1000, 600)
#
p1 = win.addPlot(title="Multiple curves")
p1.plot(np.random.normal(size=100), pen=(255, 0, 0), name="Red curve")
p1.plot(np.random.normal(size=110) + 5, pen=(0, 255, 0), name="Blue curve")
# LinearRegionItem
#
def updateRegion(window, viewRange):
region = lr.getRegion()
print region
#
lr = pg.LinearRegionItem([10, 40])
lr.setZValue(-10)
p1.addItem(lr)
p1.sigXRangeChanged.connect(updateRegion)
#
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
Pyqtgraphs linearregionitem has a signal called sigRegionChanged.
With this signal the regionItem emits itself when the user drags it or when it is changed programatically. Using getRegion() you can then get the low and high of the linearregionitem.
def regionUpdated(regionItem):
lo,hi = regionItem.getRegion()
print lo,hi
lr.sigRegionChanged.connect(regionUpdated)
This will output the position low and high when dragged, e.g.
9.50787175868 13.9172032101
If you have your red curve as a numpy array then you can slice it using the lo and hi.
red = np.random.normal(size=100)
red[9.50787175868:13.9172032101]
Which gives
[ 0.13231953 -0.5609786 -0.13632821 0.79973 ]
Slicing an index array with floats can feel a bit weird, but numpy runs int() on the indices first, basically making the call red[9:13]. See the question "Why ndarray allow floating point index?" for more about this.
The slicing can be done in regionUpdated and then you can do anything you want with the slice, e.g. print it!
def regionUpdated(regionItem):
lo,hi = regionItem.getRegion()
print red[lo:hi]
I'm trying to make a special kind of grating called a Gabor patch, an example of which can be found at the bottom of this tutorial whose code I ported to python.
Using matplotlib's imshow function, I obtain the following patch.
While the coloring is different, I suspect that this has to do with how matplotlib displays numerical values. In essence, this image is a 2D, 100-by-100 pixel array containing values from -1.0 to 1.0 (inclusive). If anybody would like to try manipulating the array in question, I've saved it as a pickle object here.
My question is as follows: How can I transfer this array to a pygame surface while ensuring that the following conditions are met?
The coloring is converted to grayscale coloring (c.f.: the last image in the first link)
The solution must employ pygame version 1.9.1release. For some inexplicable reason, I can't find a way to install 1.9.2 on my OS (Ubuntu 13.04). There appear to be no PPAs and pygame is evidently not on PIP.
Thank you very much in advance, and please let me know if I can provide additional information!
Edit
Regarding #Veedrac's solution (which is remarkably similar to my own), here is what my patch looks like when using the grayscale colormap in matplotlib's imshow. This is what I would like to have:
from matplotlib.pyplot import *
import matplotlib.cm as cm
figure()
imshow(g, cm=cm.Greys_r)
show()
import numpy
import pickle
import pygame
surface = pygame.Surface((100, 100))
Get the pixels, convert to RGBA. Using Joe Kington's reminder that the data ranges from -1 to 1:
base = (pickle.load(open("g.pickle"))+1)/2 * 255
base = base[..., numpy.newaxis].repeat(4, -1).astype("uint8")
Copy the data across
numpy_surface = numpy.frombuffer(surface.get_buffer())
numpy_surface[...] = numpy.frombuffer(base)
del numpy_surface
Show it with:
screen = pygame.display.set_mode((100, 100))
screen.blit(surface, (0, 0))
pygame.display.flip()
and you get
And simplified, once again thanks to Joe Kington's input, using make_surface:
import numpy
import pickle
import pygame
base = (pickle.load(open("g.pickle"))+1) * 128
base = base[..., None].repeat(3, -1).astype("uint8")
surface = pygame.surfarray.make_surface(base)
screen = pygame.display.set_mode((100, 100))
screen.blit(surface, (0, 0))
pygame.display.flip()
The base[..., None] is normally spelt base[..., numpy.newaxis], but seeing as that was the only instance of numpy I just "expanded the constant" so as to not need numpy. It didn't work, though, as the code breaks if you don't import numpy with a IndexError: bytes to write exceed buffer size. Thanks, numpy.
The ... means "the whole of all of the axis before this point", so you can replace [3:2], [:, 3:2] and [:, :, :, 3:2] with [..., 3:2]. In fact, ... was introduced to Python for this very reason.
The None, or numpy.newaxis, slices a new axis (duh). This will transform [a, b, c] into [[a], [b], [c]], for example. This is needed because we then repeat along this new axis.
Basically, looking at one row, we have
114, 202, 143, ...
and we want
[114, 114, 114], [202, 202, 202], [143, 143, 143], ...
so our [..., None] got us to
[114], [202], [143], ...
and we just repeat 3 times in axis -1. Axis -1 is, of course, the last axis, which is the numpy.newaxis.