Setting individual pixels in Pyglet - python

Is there a way to set individual pixel values for image data in Pyglet?
I guess it can also be done by setting the OpenGL rendering mode to points, so if someone has insight on this, please share it as well.

The solution which ended up appearing the most suitable to me is to use the ImageData class, which comes with every AbstractImage subclass.
ImageData array Retrieval
The ImageData class represents an image as an array of bytes, or more precisely a String of which each character represents a coordinate in the desired color space.
To obtain that byte representation of a given image, you would call
image_data = image.get_image_data()
width = image_data.width
data = image_data.get_data('RGB', 3*width)
The second parameter we are passing to get_data here is the number of bytes we are expecting to get for each row of the image. Because we want to have all of the components R, G and B for each and every pixel, we would like to have 3 times the width of the image. If we would only be interested in, say, the intensity for each pixel, we would pass the parameters ('I', image.width). Here's some official documentation.
To get the values only for the very pixel you are interested in, e.g. the one at (x,y), you might want to do sth like this:
pos = (width*y + x) * 3
rgb = map(ord, data[pos:pos+3])
Instead of retrieving the ImageData for the entire image and picking the desired pixel's values afterwards, you can also call get_image_data() for a certain region, i.e. 1x1 pixel at the desired position:
image_data = image.get_region(x,y,1,1).get_image_data()
That's the way how it is done is this google groups post, where you can also find the useful information that it seems to be more efficient to get your image data as RGBA instead of RGB.
Setting individual pixels
Now, to finally answer question, there is also a setter method for the image byte data of ImageData, set_data, which works just the other way around.
image_data.set_data('RGB', image.width*3, data)
This probably works for regions, too, but I haven't tried. In case you want to set byte data that you have stored in an integer array, pass ''.join(map(chr, data)). I don't know if there's a way to set numerical values.
Hope this helps anybody stumbling upon this quite old question, which yet happens to be a prominent google result for certain search terms.

The following is purely from an OpenGL standpoint, I don't know of any pyglet tricks for this.
There is always glDrawPixels, or you could use an OpenGL texture that is the same resolution as your window (best if you can use non power of two texture sizes) and then map that texture on to a quad that takes up the entire window.
The latter I think is the fastest solution for when you don't have to change pixels on every refresh because all the data is already on the card.
You can keep a "local" (in cpu memory) cache of the texture data for easy modification and re-uploading. Or, for bonus points, you can modify the texture data directly via OpenCL ;)

You can set up a batch using batch = pyglet.graphics.Batch() and then add vertices to the batch by calling batch.add(npts, gl_type, group, pixel_data, color_data), where npts is the integer specifying how many points you will be listing as tuples in pixel_data, and gl_type in your case is pyglet.gl.GL_POINTS. batch.draw() then draws all the vertices that you've added to your batch. The example below draws a line by adding vertices (i.e. GL_POINTS) to the batch at each call of on_draw():
import pyglet
COLOR = (0,255,0)
FRAMERATE = 1.0/60
(x,y) = (50,50)
window = pyglet.window.Window()
batch = pyglet.graphics.Batch()
# the vertex_list will keep a handle on all the vertices we add to the batch
vertex_list = [batch.add(1, pyglet.gl.GL_POINTS, None,('v2i', (x,y)),('c3B', COLOR))]
#window.event
def on_draw():
window.clear()
# the next vertex will use the vertices of the vertex currently at the end of the current vertex_list
(x,y) = vertex_list[-1].vertices
vertex_list.append(batch.add(1, pyglet.gl.GL_POINTS, None,('v2i', (x+1,y+1)),('c3B', COLOR)))
batch.draw()
pyglet.clock.schedule_interval(lambda dt: None, FRAMERATE)
pyglet.app.run()
There is probably a more efficient way of drawing lines than the way above, but this way gives you a good idea of how the batch works. Check out the docs for more here.

Related

Get real GPS coordinates out of known edges values on python

I'm trying to find a way to convert pixels into a real coordinates. I have an image with known (GPS) edges values.
Top left = 43.51281, -70.46223
Top right = 43.51279, -70.46213
Bottom left = 43.51272, -70.46226
Bottom right = 43.51270, -70.46215
Image with known edges values
I have another script that prints the coordinates in pixels of an image. Is there any way that the value of each corner is declared, and that it prints the real coordinates of where I clicked?
For example: The next image shape is [460, 573] and when I click somewhere on it, the pixels of that click are shown, I want it to be real coordinates.
Example
An option is to use OpenCV's getPerspectiveTransform() function, see this for an intuitive explanation of how the function maps real world coordinates to coordinates on another image (which in your case would be mapping the GPS values to the pixel values within the image):
https://towardsdatascience.com/how-to-track-football-players-using-yolo-sort-and-opencv-6c58f71120b8
And these for an example of the function being used:
Python Open CV perspectiveTransform()
https://www.geeksforgeeks.org/perspective-transformation-python-opencv/

Change Color of Point Object Using QtGui.QPainter() in Python

I'm developing a map visualization program in Python using several modules from qtpy. There is a main window interface which displays a background map containing several geolocated points on the screen. The location of each point is determined by an external .csv file that has information regarding the latitude, longitude, and other text attribution. This file gets read-in by the program each time the map window is instantiated. The color of each point defaults to red when the map window is opened, but I would like to have each point change to a different color based on its metadata stored in the .csv file. For instance, there is a header in the file called "color", and each point has the text string "red", "green" or "blue" encoded. Here is the section of code I've been working on so far...
# Initialize all points to default color.
color = QtCore.Qt.red
for i, p in zip(range(len(self.points)), self.points):
if lb_lat <= stn_lat and stn_lat <= ub_lat and window_rect.contains(*self.transform.map(stn_x, stn_y)):
if p['color'] == 'green':
color = QtCore.Qt.green
elif p['color'] == 'blue':
color = QtCore.Qt.blue
elif p['color'] == 'red':
color = QtCore.Qt.red
else:
color = QtCore.Qt.white
qp.setPen(QtGui.QPen(color, self.scale))
qp.setBrush(QtGui.QBrush(color))
qp.drawEllipse(QtCore.QPointF(stn_x, stn_y), size, size)
The list of points is stored in the variable self.points and I'm trying to iterate through this list and apply the correct color to each point using QtGui.QPen and QBrush. What is happening is that if the color attribute in the .csv file for point 1 has the text string "green", then the entire array of points changes to green instead of just that one point. Looking at the code after the if...else statements, I haven't been able to find a way to "index" the setPen and setBrush commands for just the point in question. The coloring methods are acting on the entire array of points as one indivisible unit instead of working on each point separately as intended. Would anyone perhaps know of a way to do this using the Qt framework? Please let me know if supplying additional code might help clarify the problem or give better context as I'd be happy to do that.
I was able to solve the issue I had by removing the looping construct where I was iterating through the items in self.points. I had a higher-level "for" loop already in place and this was causing the incorrect array index to be referenced each time the points were being drawn to the screen. Each point is now changing to the appropriate color.

Corner Refine Method, CORNER_REFINE_SUBPIX, from AcUro with Python and OpenCV

Hi There
I want to increase the accuracy of the marker detection from aruco.detectMarkers. So, I want to use Corner Refine Method with CORNER_REFINE_SUBPIX, but I do not understand how it is implemented in python.
Sample code:
frame = cv.imread("test.png")
gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
para = aruco.DetectorParameters_create()
det_corners, ids, rejected = aruco.detectMarkers(gray,dictionary,parameters=para)
aruco.drawDetectedMarkers(frame,det_corners,ids)
Things I have tried:
para.cornerRefinementMethod()
para.cornerRefinementMethod(aruco.CORNER_REFINE_SUBPIX)
para.cornerRefinementMethod.CORNER_REFINE_SUBPIX
para = aruco.DetectorParameters_create(aruco.CORNER_REFINE_SUBPIX)
para = aruco.DetectorParameters_create(para.cornerRefinementMethod(aruco.CORNER_REFINE_SUBPIX))
They did not work, I’m pretty new to python ArUco so I hope that there is a simple and obvious solution.
I would also Like to implement enclosed markers like in the Documentation(Page 4). Do you happen to know if there is a way to generate these enclosed markers in python?
Concerning the first part of your question, you were pretty close: I assume your trouble is in switching and tweaking the "para" options. If so, you only need to set the corresponding values in the parameters object like
para.cornerRefinementMethod = aruco.CORNER_REFINE_SUBPIX
Note that "aruco.CORNER_REFINE_SUBPIX" is simply an integer. You can verify this by typing type(aruco.CORNER_REFINE_SUBPIX) in the console. Thus assigning values to the "para" object works like mentioned above.
You might also want to tweak the para.cornerRefinementWinSize which seems to be implemented in units of code pixels, not actual image pixel units.
Concerning the second part, you might have to write a function, that adds the boxes at the corner points, which you can get using the detectMarker function. Note that the corner points are always ordered clockwise, thus you can easily assign the correct offset values (like "up & left", "up & right" etc.).
para.cornerRefinementMethod = 1
may work.

How can I take a list of points that create a line and extend them into polygons inward of a certain length?

I have gps coordinates. They are just points that create an outline in a few different places, . I want to be able to have a script convert these points into polygons that extend X distance inwards, and also- a way for them to extend both inwards and outwards.
So if I had something like this:
(dots are points)
00000000000000000
00000000000000000
00............000
00.0000000000.000
00.0000000000.000
00.0000000000.000
00.0000000000.000
00.0000000000.000
00............000
00000000000000000
00000000000000000
I could run this program with a distance of 1 and "inwards", and I would end up with a polygon of # shape:
00000000000000000
00000000000000000
00&&&&&&&&&&&&000
00&&&&&&&&&&&&000
00&&00000000&&000
00&&00000000&&000
00&&00000000&&000
00&&&&&&&&&&&&000
00&&&&&&&&&&&&000
00000000000000000
00000000000000000
So far I have tried using circles and then reducing them but it seems wrong / not really feasible. This isn't being performed on a grid, actually it used floats for coordinates.
Any libraries that could do this as well are appreciated.
GDAL/OGR is another option. Ultimately what you want to do is a buffer. To expand your polygons shape outward use a buffer with a positive buffer distance, inwards it would be negative buffer distance. The following is a simple example using a shapefile. Not sure what format your data is in, but I would be surprised if GDAL/OGR can't read it.
import osgeo.ogr
# using ESRI Shape file in this example but there are a number
# of different files this lib supports: http://www.gdal.org/ogr/ogr_formats.html
driver = osgeo.ogr.GetDriverByName('ESRI Shapefile')
osgeo.ogr.UseExceptions()
# Create a data source using the driver...
dataSource = driver.Open("/home/user1/data.shp")
# Get the layer
lyr = dataSource.GetLayer()
# Select the feature in this case using an attribute query
lyr.SetAttributeFilter("column = 'value'")
# verify that you have a feature selected
print 'features in layer:', lyr.GetFeatureCount()
# get the firest feature from the layer
feature = lyr.GetNextFeature()
# get the geometry from the feature
geom = feature.GetGeometryRef()
# perform a 100 unit buffer, not sure what units the coordinates of the
# the data you have are in.
bufferGeom = geom.buffer(100)
# bufferGeom is a geometry object, which is described here:
# <http://cosmicproject.org/OGR/ogr_classes.html#Geometry>
The following is a fantastic resource for getting started with working with spatial data using GDAL/ORG: http://www.gis.usu.edu/~chrisg/python/2009/
Api docs: http://cosmicproject.org/OGR/ogr_classes.html
finally here is the link to the GDAL/OGR page. http://www.gdal.org/
https://pypi.python.org/pypi/Shapely
Shapely is a very good 2d computational geometry library; the way I understand it, it reduces your problem to a single line of code.

Image Gurus: Optimize my Python PNG transparency function

I need to replace all the white(ish) pixels in a PNG image with alpha transparency.
I'm using Python in AppEngine and so do not have access to libraries like PIL, imagemagick etc. AppEngine does have an image library, but is pitched mainly at image resizing.
I found the excellent little pyPNG module and managed to knock up a little function that does what I need:
make_transparent.py
pseudo-code for the main loop would be something like:
for each pixel:
if pixel looks "quite white":
set pixel values to transparent
otherwise:
keep existing pixel values
and (assuming 8bit values) "quite white" would be:
where each r,g,b value is greater than "240"
AND each r,g,b value is within "20" of each other
This is the first time I've worked with raw pixel data in this way, and although works, it also performs extremely poorly. It seems like there must be a more efficient way of processing the data without iterating over each pixel in this manner? (Matrices?)
I was hoping someone with more experience in dealing with these things might be able to point out some of my more obvious mistakes/improvements in my algorithm.
Thanks!
This still visits every pixel, but may be faster:
new_pixels = []
for row in pixels:
new_row = array('B', row)
i = 0
while i < len(new_row):
r = new_row[i]
g = new_row[i + 1]
b = new_row[i + 2]
if r>threshold and g>threshold and b>threshold:
m = int((r+g+b)/3)
if nearly_eq(r,m,tolerance) and nearly_eq(g,m,tolerance) and nearly_eq(b,m,tolerance):
new_row[i + 3] = 0
i += 4
new_pixels.append(new_row)
It avoids the slicen generator, which will be copying the entire row of pixels for every pixel (less one pixel each time).
It also pre-allocates the output row by directly copying the input row, and then only writes the alpha value of pixels which have changed.
Even faster would be to not allocate a new set of pixels at all, and just write directly over the pixels in the source image (assuming you don't need the source image for anything else).
Honestly, the only heuristic I could conceive is picking a few arbitrary, random points on your image and using a flood fill.
This only works well if your image as large contiguous white portions (if your image is an object with no or little holes in front of a background, then you're in luck -- you actually have a heuristic for which points to flood fill from).
(disclaimer: I am no image guru =/ )
I'm quite sure there is no short cut for this. You have to visit every single pixel.
The issue seems to have more to do with loops in Python than with images.
Python loops are extremely slow, it is best to avoid them and use built-ins loop operators instead.
Here, if you were willing to copy the image, you could use a list comprehension:
def make_transparent(pixel):
if pixel looks "quite white": return transparent
else: return pixel
newImage = [make_transparent(p) for p in oldImage]

Categories