Get pixel colors of tkinter canvas - python-3.x

I'd like to be able to create and interact with a Tkinter Canvas and, at any time, be able to iterate over each of its pixels and get their RGB values.
Setting pixel by pixel is not necessary, just getting. However, methods analogous to Canvas's create_polygon(), create_line(), create_text(), and create_oval() must be available as well for interacting with the image overall.
There are a number of restraints:
Must work with Python 3
Must work with Linux, Mac, and Windows
Must work with libraries that come with Python (no downloads)
The second restraint is mainly the reason I've posted this question when getting the color of pixels on the screen in Python3.x and several other similar questions already exist.
If this is impossible, what is the closest I can get?

Try it. But is slow :/
from util.color import Color
class ImageUtils:
#staticmethod
def get_pixels_of(canvas):
width = int(canvas["width"])
height = int(canvas["height"])
colors = []
for x in range(width):
column = []
for y in range(height):
column.append(ImageUtils.get_pixel_color(canvas, x, y))
colors.append(column)
return colors
#staticmethod
def get_pixel_color(canvas, x, y):
ids = canvas.find_overlapping(x, y, x, y)
if len(ids) > 0:
index = ids[-1]
color = canvas.itemcget(index, "fill")
color = color.upper()
if color != '':
return Color[color.upper()]
return "WHITE"

It's not possible. The canvas doesn't work that way.
If you're not interested in setting, you can use an image rather than a canvas. You can get the value of individual pixels in a PhotoImage.

Related

Pyqtgraph ROI Mirrors the Displayed Text

I want to display some text close to the handles of crosshair ROI. The text is mirrored and I don't know why or how to fix it.
The following code runs, where the class CrossHair is a slight modification of the CrosshairROI given at https://pyqtgraph.readthedocs.io/en/latest/_modules/pyqtgraph/graphicsItems/ROI.html#ROI. More precisely, all I did was setting lock aspect to be False and making another handle to deal with another direction.
import pyqtgraph as pg
from PyQt5.QtWidgets import*
from PyQt5.QtCore import*
from PyQt5.QtGui import*
class MainWindow(pg.GraphicsLayoutWidget):
def __init__(self):
super().__init__()
layout = self.addLayout()
self.viewbox = layout.addViewBox(lockAspect=True)
self.viewbox.setLimits(minXRange = 200, minYRange = 200,maxXRange = 200,maxYRange = 200)
self.crosshair = CrossHair()
self.crosshair.setPen(pg.mkPen("w", width=5))
self.viewbox.addItem(self.crosshair)
class CrossHair(pg.graphicsItems.ROI.ROI):
def __init__(self, pos=None, size=None, **kargs):
if size is None:
size=[50,50]
if pos is None:
pos = [0,0]
self._shape = None
pg.graphicsItems.ROI.ROI.__init__(self, pos, size, **kargs)
self.sigRegionChanged.connect(self.invalidate)
self.addScaleRotateHandle(pos = pg.Point(1,0), center = pg.Point(0, 0))
self.addScaleRotateHandle(pos = pg.Point(0,1), center = pg.Point(0,0))
def invalidate(self):
self._shape = None
self.prepareGeometryChange()
def boundingRect(self):
return self.shape().boundingRect()
def shape(self):
if self._shape is None:
x_radius, y_radius = self.getState()['size'][0],self.getState()['size'][1]
p = QPainterPath()
p.moveTo(pg.Point(-x_radius, 0))
p.lineTo(pg.Point(x_radius, 0))
p.moveTo(pg.Point(0, -y_radius))
p.lineTo(pg.Point(0, y_radius))
p = self.mapToDevice(p)
stroker = QPainterPathStroker()
stroker.setWidth(10)
outline = stroker.createStroke(p)
self._shape = self.mapFromDevice(outline)
return self._shape
def paint(self, p, *args):
x_radius, y_radius = self.getState()['size'][0],self.getState()['size'][1]
p.setRenderHint(QPainter.RenderHint.Antialiasing)
p.setPen(self.currentPen)
p.drawLine(pg.Point(0, -y_radius), pg.Point(0, y_radius))
p.drawLine(pg.Point(-x_radius, 0), pg.Point(x_radius, 0))
x_pos, y_pos = self.handles[0]['item'].pos(), self.handles[1]['item'].pos()
x_length, y_length = 2*x_radius, 2*y_radius
x_text, y_text = str(round(x_length,2)) + "TEXT",str(round(y_length,2)) + "TEXT"
p.drawText(QRectF(x_pos.x()-50, x_pos.y()-50, 100, 100), Qt.AlignmentFlag.AlignLeft, x_text)
p.drawText(QRectF(y_pos.x()-50, y_pos.y()-50, 100, 100), Qt.AlignmentFlag.AlignBottom, y_text)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
main = MainWindow()
main.show()
app.exec()
We see that:
The objective is to fix the above code such that:
It displays texts dependent on the length of the line (2*radius) close to each handle without reflecting.
The text is aligned close to the handle such that no matter how the user rotates the handle the text is readable (i.e. not upside down).
I am having great deal of trouble with the first part. The second part can probably be fixed by changing aligning policies but I don't know which one to choose .
The reason of the inversion is because the coordinate system of pyqtgraph is always vertically inverted: similarly to the standard convention of computer coordinates, the reference point in Qt is always considered at the top left of positive coordinates, with y > 0 going down instead of up.
While, for general computer based imaging this is fine, it clearly doesn't work well for data imaging that is commonly based on standard Cartesian references (positive values of y are always "above"). And that's what pyqtgraph does by default.
The result is that, for obvious reasons, basic drawing that is directly done on an active QPainter will always be vertically inverted ("mirrored"). What you show in the image is the result of a composition of vertical mirroring and rotation, which is exactly the same as horizontal mirroring.
To simplify: when p is vertically mirrored, it becomes b, which, when rotated by 180°, results in q.
There's also another issue: all pyqtgraph items are actually QGraphicsItem subclasses, and one of the most important aspects of QGraphicsItems is that their painting is and shall always be restricted by its boundingRect():
[...] all painting must be restricted to inside an item's bounding rect. QGraphicsView uses this to determine whether the item requires redrawing.
If you try to move the handles very fast, you'll probably see some drawing artifacts ("ghosts") in the text caused by the painting buffer that is used to improve drawing performance, and that's because you didn't consider those elements in the boundingRect() override: the painting engine didn't know that the bounding rect was actually bigger, and didn't consider that the previously drawn regions required repainting in order to "clear up" the previous content.
Now, since those are text displaying objects, I doubt that you're actually interested in having them always aligned to their respective axis (which is not impossible, but much more difficult). You will probably want to always display the values of those handles to the user in an easy, readable way: horizontally.
Considering the above, the preferred solution is to use child items for the text instead of manually drawing it. While, at first sight, it might seem a risk for performance and further complication, it actually ensures 2 aspects:
the text items will always be properly repainted, and without any "ghost residue" caused by the wrong bounding rect;
the performance loss is practically little to none, since item management (including painting) is completely done on the C++ side;
For that, I'd suggest the pg.TextItem class, which will also completely ignore any kind of transformation, ensuring that the text will always be visible no matter of the scale factor.
Note that "mirroring" is actually the result of a transformation matrix that uses negative scaling: a scaling of (0, -1) means that the coordinates are vertically mirrored. If you think about it, it's quite obvious: if you have a positive y value in a cartesian system (shown "above" the horizontal axis) and multiply it by -1, that result is then shown "below".
Given the above, what you need to do is to add the two "labels" as children of the handle items, and just worry about painting the two crosshair lines.
Finally, due to the general performance requirements of pyqtgraph (and QGraphicsView in general), in the following example I took the liberty to make some modifications to the original code in order to improve responsiveness:
class CrossHair(pg.graphicsItems.ROI.ROI):
_shape = None
def __init__(self, pos=None, size=None, **kargs):
if size is None:
size = [50, 50]
if pos is None:
pos = [0, 0]
super().__init__(pos, size, **kargs)
self.sigRegionChanged.connect(self.invalidate)
font = QFont()
font.setPointSize(font.pointSize() * 2)
self.handleLabels = []
for refPoint in (QPoint(1, 0), QPoint(0, 1)):
handle = self.addScaleRotateHandle(pos=refPoint, center=pg.Point())
handle.xChanged.connect(self.updateHandleLabels)
handle.yChanged.connect(self.updateHandleLabels)
handleLabel = pg.TextItem(color=self.currentPen.color())
handleLabel.setParentItem(handle)
handleLabel.setFont(font)
self.handleLabels.append(handleLabel)
self.updateHandleLabels()
def updateHandleLabels(self):
for label, value in zip(self.handleLabels, self.state['size']):
label.setText(format(value * 2, '.2f'))
def invalidate(self):
self._shape = None
self.prepareGeometryChange()
def boundingRect(self):
return self.shape().boundingRect()
def shape(self):
if self._shape is None:
x_radius, y_radius = self.state['size']
p = QPainterPath(QPointF(-x_radius, 0))
p.lineTo(QPointF(x_radius, 0))
p.moveTo(QPointF(0, -y_radius))
p.lineTo(QPointF(0, y_radius))
p = self.mapToDevice(p)
stroker = QPainterPathStroker()
stroker.setWidth(10)
outline = stroker.createStroke(p)
self._shape = self.mapFromDevice(outline)
return self._shape
def paint(self, p, *args):
p.setRenderHint(QPainter.Antialiasing)
p.setPen(self.currentPen)
x_radius, y_radius = self.state['size']
p.drawLine(QPointF(0, -y_radius), QPointF(0, y_radius))
p.drawLine(QPointF(-x_radius, 0), QPointF(x_radius, 0))
Notes:
pg.Point is actually a subclass of QPointF; unlike helper functions like mkColor() that can be actually necessary for pg objects and are effective in their simplicity/readability, there is really no point (pun intended) to use those subclasses for basic Qt functions, like you're doing for paintEvent(); just use the basic class;
considering the point above, always try to leave object conversion on the C++ side; QPainterPath's moveTo and lineTo always accept floating point values (they are overloaded functions that internally transform the values to QPointF objects); on the other hand, QPainter functions like drawLine only accept individual numeric values as integers (that's why I used QPointF in paintEvent()), so in that case you cannot directly use the coordinate values; always look for the C++ implementation and the accepted value types;
self.getState()['size'] is already a two-item tuple (width and height), retrieving it twice is unnecessary; also, since getState() actually recalls the internal self.state dict, you can avoid the function call (as I did above) as long as getState() is not overridden by a custom subclass;

To generate grids within a square area in python

I have a square layout of size 100*100. I want to divide this layout into grids of size 5*5.How can I do so using python?
I am still not sure this is what you are looking for, so correct me if this is not it.
I assumed the best way to deal with the problem you described is by creating a Grid class. That makes it easier to create mulitple diffrent Grids at the same time without making a mess with the data. Ive come up with this solution:
class Grid():
def __init__(self,x,y,grid_len):
self.total_x = x
self.total_y = y
self.grid_len = grid_len
self.node_list=[]
for y in range(self.total_y//self.grid_len):
for x in range(self.total_x//self.grid_len):
self.node_list.append((x*self.grid_len,y*self.grid_len))
def return_node_at_given_x_y(self,x,y):
node_index = y*(self.total_y//self.grid_len) + x
print(self.node_list[node_index])
my_grid = Grid(100,100,5)
print(my_grid.return_node_at_given_x_y(8,6))
#print(my_grid.node_list)
I hope this is what you were looking for. U can change the size of your base square or of the grids when creating the object. Grid(200,200,2) will have a grid_size of 2 and a square size of 200x200.

Any way to refactor this in a neater way? Specifically lines 7-11 in relation to line 39? Using Pygame

I am teaching this to 5th graders so it needs to be as simple as possible. However, as you see inline 39 I am using stored variables for the function parameters. However is there an easier way? Let's say I wanted to put in a 2nd character I would have to put that long list of variables again?
Should I create a character class? If so, how would I plug that into a parameter?
import pygame
pygame.init()
win = pygame.display.set_mode((1000, 1000))
#Character Details
x = 100
y = 5
width = 10
height = 10
vel = 20
run = True
while run:
pygame.time.delay(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
#Movement Details
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
x -= vel #
if keys[pygame.K_RIGHT]:
x += vel
if keys[pygame.K_UP]:
y -= vel
if keys[pygame.K_DOWN]:
y += vel
#Without this character movement is additive.
win.fill((0, 0, 0))
pygame.draw.rect(win, (245, 100, 50), (x, y, width, height))
pygame.display.update()
```
You could make the variables a Rect object.
player1 = pygame.Rect(100,5,10,10) #x,y,w,h
player2 = pygame.Rect(200,20,10,10)
...
pygame.draw.rect(win,(245,100,50),player1)
pygame.draw.rect(win,(245,100,50),player2)
you could also use a list and it would work just as fine, but with the Rect, it calculates extra attributes for you like
player1.center #get center of the player, both x and y
player.right #get right side of player, same as x position + width
you can also move using these
player1.center = (200,200) #moves the center to (200,200)
But you would need to have a velocity variable for each player, which you could have a list
#The Big Kahuna has already suggested that you can replace one of the parameters to the pygame.draw.rect() with a rect representing the player. That is specifically answering your question and you should do that.
Since you are teaching this to others I wanted to suggest some other things as well.
The first is a tweak to what #The Big Kahuna said. He correctly suggested that you should use the the pygame rect class, which you should. Instead of passing the four parameters like that, Rect allows you you create rect's by calling it with the position and size parameters grouped together. I.e you can group the two position parameters and the two size parameters. Like this:
size = (10, 10) # width, height
player1 = pygame.Rect((100, 5), size)
player2 = pygame.Rect((200, 20), size)
You could separate the position out as well if desired.
The other thing that you can do (and the real reason that I am actually commenting here) to make it cleaner is to look at the other parameter to pygame.draw.rect(). The (245,100,50) is the color you are drawing (see the docs here). Instead of just using numbers like that, it can be made more clear by assigning that color to a variable somewhere near the top of your program and using the variable in the draw call. Like this:
orangey = (245, 100, 50)
...
pygame.draw.rect(win, orangey, player1)
In this case I called the color orangey, because when I displayed it it looked kind of orangey to me. but obviously you can name it whatever is appropriately descriptive.
You can use multiple colors to distinguish the player rect's like this:
orange = pygame.Color("orange")
purple = pygame.Color("purple")
...
pygame.draw.rect(win, orange, player1)
pygame.draw.rect(win, purple, player2)
You can see a good way to find defined colors like the ones I showed by looking at the answer to the question here.

Looping program causes index # is out of bounds for axis #

I'm pretty new to python and Opencv, but I have a few pieces from cv2 and random in mind for a simple test program to make sure I understood how these libraries worked.
I'm trying to create a program that effectively generates colored "snow", similar to what an old fashioned television shows when it has no signal.
Basically I generate a random color with random.randint(-1,256) to get a value between 0 and 255. I do it three times and store each in a different variable, randB/G/R. Then I do it twice more for coordinates randX/Y, using img.shape to get variables for width and height for the max number.
I don't think my variables are being interpreted as strings. If I quickly break the loop and print my variables, no errors are shown. If I remove the randX and randY variables and specify fixed coordinates or a range of [X1:Y1, X2:Y2] it doesn't crash.
import cv2
import numpy as np
import random
img = cv2.imread('jake_twitch.png', cv2.IMREAD_COLOR)
height, width, channels = img.shape
while True:
randB = (random.randint(-1,256))
randG = (random.randint(-1,256))
randR = (random.randint(0,256))
randX = (random.randint(0,width))
randY = (random.randint(0,height))
img[randX,randY] = [randB,randG,randR]
cv2.imshow('Snow', img)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cv2.imwrite('Snow.png', img)
cv2.destroyAllWindows
I would expect my code to run indefinitely coloring pixels random colors within a specified "box" defined by the width and height variables from img.shape.
it seems to start doing that, but If the program runs for more than about a second it crashes and spits out this error
"IndexError: index 702 is out of bounds for axis 1 with size 702"
Your image is width and height pixels wide - but the corresponding indexes run from 0..width-1 and 0..height-1
The randint function returns inclusive limits - so
random.randint(0,width)
might give you width ... which is 1 too big:
random.randint(a, b)
Return a random integer N such that a <= N <= b. Alias for randrange(a, b+1).
Use
randX = (random.randint(0,width-1))
randY = (random.randint(0,height-1))
instead.
Or change it to use random.randrange(0, width) or random.choice(range(width)) - both omit the upper limit value.

Python 3: Find Index of Square in grid, based on Mouse Coordinates

This may have been asked before, but for my lack of correct English terms end up leaving me here. (I'm Finnish) This may be asked before, but what else could I have done?
But I have pygame code, which renders partion of bigger 'map'. I want to have behaviour to 'click' a squre and 'select' it.
The broblem is, how do I find the index of image I am currently overlapping with mouse?
Codelike close to what I have now
#...setup code...
map = [[0,0,0,0], [0,1,0,0], [0,0,0,0]]
while:
render()
#render completely fills the screen with images based on map's objects
mousepos=pyagem.mouse.get_pos()
selectedMapSquare=???
You just have to divide the absolute (screen) coordinates with the size of your squares. So, if the size of your squares is e.g. 32, you can use something like
x, y = pygame.mouse.get_pos()
# TODO: use a constant
w_x, w_y = x / 32, y /32
Now w_x is the index of the x axis, and w_y is the index of the y axis:
# TODO: bound/error checking
tile_under_mouse = map[w_y][w_x]

Resources