I have written a script that draws rectangles around features of an image according to their x/y/r pixel coordinates, and this is all functioning well. The functioning code is as follows:
ss = pd.read_excel(xeno_data)
fandc = []
for index, row in ss.head().iterrows():
filename = row['filename']
coords = row['xyr_coords']
# Use RegEx to find anything that looks like a group of digits, possibly seperated by decimal point.
x, y, r = re.findall(r'[0-9.]+',coords)
print(f'DEBUG: filename={filename}, x={x}, y={y}, r={r}')
fandc.append({'filename': filename, 'x':x, 'y':y, 'r':r})
#Draw a transparent rectangle:
im = im.convert('RGBA')
overlay = Image.new('RGBA', im.size)
draw = ImageDraw.Draw(overlay)
#The x,y,r coordinates are centre of sponge (x,y) and radius (r).
draw.rectangle(((float(fandc[0]['x'])-float(fandc[0]['r']), float(fandc[0]['y'])-float(fandc[0]['r'])), (float(fandc[0]['x'])+float(fandc[0]['r']), float(fandc[0]['y'])+float(fandc[0]['r']))), fill=(255,0,0,55))
img = Image.alpha_composite(im, overlay)
img = img.convert("RGB")
# Remove alpha for saving in jpg format.
img.show()
This code produces the desired result, and you can see from that it has succesfully drawn a faded red rectangle over a feature in the centre-bottom of the image.
However this is tailored to the first row of the data ( 'fandc[0]' ). How do I adjust this code to automatically iterate or loop through each row of my spreadsheet (xeno_data), i.e. 'fandc1', 'fandc[2]', 'fandc[3]', etc, etc.....
Thanks all!
Without having access to the same data, you initially plot based on fandc[0] and want to go through all other rectangles fandc[1], fandc[2], etc. You could then try:
for i in range(len(fandc)):
draw.rectangle(((float(fandc[i]['x'])-float(fandc[i]['r']), float(fandc[i]['y'])-float(fandc[i]['r'])), (float(fandc[i]['x'])+float(fandc[i]['r']), float(fandc[i]['y'])+float(fandc[i]['r']))), fill=(255,0,0,55))
See how we replace our initial index 0 with our iterating index i.
If you are struggling getting for loops to work it is probably wise to do an online tutorial on them, and practicing them with simpler code. See https://www.w3schools.com/python/python_for_loops.asp for more info
Related
I am trying to extract quite a few tables from a PDF file. These tables are sort of conveniently "highlighted" with different colors, which makes it easy for eyes to catch (see the example screenshot).
I think it would be good to detect the position/coordinates of those colored blocks, and use the coordinates to extract tables.
I have figured out the table extraction part (using tabula-py). So it is the first step stopping me. From what I gathered minecart is the best tool for color and shapes in PDF files, except full scale imaging processing with OpenCV. But I have no luck with detecting colored box/block coordinates.
Would appreciate any help!!
I think I got a solution:
import minecart
pdffile = open(fn, 'rb')
doc = minecart.Document(pdffile)
page = doc.get_page(page_num) # page_num is 0-based
for shape in page.shapes.iter_in_bbox((0, 0, 612, 792 )):
if shape.fill:
shape_bbox = shape.get_bbox()
shape_color = shape.fill.color.as_rgb()
print(shape_bbox, shape_color)
I would then need to filter the color or the shape size...
My earlier failure was due to having used a wrong page number :(
PyMuPDF lets you extract so-called "line art": the vector drawings on a page.
This is a list of dictionaries of "paths" (as PDF calls interconnected drawings) from which you can sub-select ones of interest for you.
E.g. the following identifies drawings that represent filled rectangles, not too small:
page = doc[0] # load some page (here page 0)
paths = page.get_drawings() # extract all vector graphics
filled_rects = [] # filled rectangles without border land here
for path in paths:
if path["type"] != "f" # only consider paths with a fill color
continue
rect = path["rect"]
if rect.width < 20 or rect.height < 20: # only consider sizable rects
continue
filled_rects.append(rect) # hopefully an area coloring a table
# make a visible border around the hits to see success:
for rect in filled_rects:
page.draw_rect(rect, color=fitz.pdfcolor["red"])
doc.save("debug.pdf")
This may sound confusing but I will demonstrate what my goal is. I want to crop the extra space around a image using python. You can see in image two the outside border is cropped off until it cuts out the extra space around the colored bars in the center. I am not sure if this is possible. Sorry for the vague question as it is very hard to explain what I am trying to do.
(Before) Image Before Cropping ----------> (After) What I am trying to achieve.
You can notice the extra black space around the colored thing in the center is cutout on the after image.
I want to be able to cut the extra space out without me manually typing in where to crop out. How could this be done?
Sure!
With a bit of Pillow and Numpy magic, you can do something like this – not sure if this is optimal, since I whipped it up in about 15 minutes and I'm not the Numpyiest of Numpyistas:
from PIL import Image
import numpy as np
def get_first_last(mask, axis: int):
""" Find the first and last index of non-zero values along an axis in `mask` """
mask_axis = np.argmax(mask, axis=axis) > 0
a = np.argmax(mask_axis)
b = len(mask_axis) - np.argmax(mask_axis[::-1])
return int(a), int(b)
def crop_borders(img, crop_color):
np_img = np.array(img)
mask = (np_img != crop_color)[..., 0] # compute a mask
x0, x1 = get_first_last(mask, 0) # find boundaries along x axis
y0, y1 = get_first_last(mask, 1) # find boundaries along y axis
return img.crop((x0, y0, x1, y1))
def main():
img = Image.open("0d34A.png").convert("RGB")
img = crop_borders(img, crop_color=(0, 0, 0))
img.save("0d34A_cropped.png")
if __name__ == "__main__":
main()
If you need a different condition (e.g. all pixels dark enough, you can change how mask is defined.
As I understand, your problem is how to identify extra space, rather than which library/framework/tool to use to edit images. If so, then I've solved a similar problem about 4 years ago. I'm sorry, I don't have sample code to show (I left that organisation); but the logic was as follows:
Decide a colour for background. Never use this colour in any of the bars in the graph/image.
Read image data as RGB matrices (3 x 2D-array).
Repeat following on first row, last row, first column, last column:
Simultaneously iterate all 3 matrices
If all values in the row or column from index=0 to index=len(row or column) are equal to corresponding background colour RGB component, then this is extra space. Remove this row or column from all RGB matrices.
Write the remaining RGB matrices as image.
Following are some helpful links in this regard:
Read image as RGB matrix
Iterating through RGB matrices
Write RGB matrix as image
I am new to python so I really need help with this one.
I have an image greyscaled and thresholded so that the only colors present are black and white.
I'm not sure how to go about writing an algorithm that will give me a list of coordinates (x,y) on the image array that correspond to the white pixels only.
Any help is appreciated!
Surely you must already have the image data in the form of a list of intensity values? If you're using Anaconda, you can use the PIL Image module and call getdata() to obtain this intensity information. Some people advise to use NumPy methods, or others, instead, which may improve performance. If you want to look into that then go for it, my answer can apply to any of them.
If you have already a function to convert a greyscale image to B&W, then you should have the intensity information on that output image, a list of 0's and 1's , starting from the top left corner to the bottom right. If you have that, you already have your location data, it just isnt in (x,y) form. To do that, use something like this:
data = image.getdata()
height = image.getHeight()
width = image.getWidth()
pixelList = []
for i in range(height):
for j in range(width):
stride = (width*i) + j
pixelList.append((j, i, data[stride]))
Where data is a list of 0's and 1's (B&W), and I assume you have written getWidth() and getHeight() Don't just copy what I've written, understand what the loops are doing. That will result in a list, pixelList, of tuples, each tuple containing intensity and location information, in the form (x, y, intensity). That may be a messy form for what you are doing, but that's the idea. It would be much cleaner and accessible to instead of making a list of tuples, pass the three values (x, y, intensity) to a Pixel object or something. Then you can get any of those values from anywhere. I would encourage you to do that, for better organization and so you can write the code on your own.
In either case, having the intensity and location stored together makes sorting out the white pixels very easy. Here it is using the list of tuples:
whites = []
for pixel in pixelList:
if pixel[2] == 1:
whites.append(pixel[0:2])
Then you have a list of white pixel coordinates.
You can usePIL and np.where to get the results efficiently and concisely
from PIL import Image
import numpy as np
img = Image.open('/your_pic.png')
pixel_mat = np.array(img.getdata())
width = img.size[0]
pixel_ind = np.where((pixel_mat[:, :3] > 0).any(axis=1))[0]
coordinate = np.concatenate(
[
(pixel_ind % width).reshape(-1, 1),
(pixel_ind // width).reshape(-1, 1),
],
axis=1,
)
Pick the required pixels and get their index, then calculate the coordinates based on it. Without using Loop expressions, this algorithm may be faster.
PIL is only used to get the pixel matrix and image width, you can use any library you are familiar with to replace it.
My goal is to dither an image in JES/Jython using the Floyd-Steinberg method. Here is what I have so far:
def Dither_RGB (Canvas):
for Y in range(getHeight(Canvas)):
for X in range(getWidth(Canvas)):
P = getColor(Canvas,X,Y)
E = getColor(Canvas,X+1,Y)
SW = getColor(Canvas,X-1,Y+1)
S = getColor(Canvas,X,Y+1)
SE = getColor(Canvas,X+1,Y+1)
return
The goal of the above code is to scan through the image's pixels and process the neighboring pixels needed for Floyd-Steinberg.
What I'm having trouble understanding is how to go about calculating and distributing the differences in R,G,B between the old pixel and the new pixel.
Anything that could point me in the right direction would be greatly appreciated.
I don't know anything about the method you are trying to implement, but for the rest: Assuming Canvas is of type Picture, you can't get directly the color that way. The color of a pixel can be obtained from a variable of type Pixel:
Example: Here is the procedure to get the color of each pixels from an image and assign them at the exact same position in a new picture:
def copy(old_picture):
# Create a picture to be returned, of the exact same size than the source one
new_picture = makeEmptyPicture(old_picture.getWidth(), old_picture.getHeight())
# Process copy pixel by pixel
for x in xrange(old_picture.getWidth()):
for y in xrange(old_picture.getHeight()):
# Get the source pixel at (x,y)
old_pixel = getPixel(old_picture, x, y)
# Get the pixel at (x,y) from the resulting new picture
# which remains blank until you assign it a color
new_pixel = getPixel(new_picture, x, y)
# Grab the color of the previously selected source pixel
# and assign it to the resulting new picture
setColor(new_pixel, getColor(old_pixel))
return new_picture
file = pickAFile()
old_pic = makePicture(file)
new_pic = copy(old_pic)
Note: The example above applies only if you want to work on a new picture without modifying the old one. If your algorithm requires to modify the old picture on the fly while performing the algorithm, the final setColor would have been applied directly to the original pixel (no need for a new picture, neither the return statement).
Starting from here, you can compute anything you want by manipulating the RGB values of a pixel (using setRed(), setGreen() and setBlue() functions applied to a Pixel, or col = makeColor(red_val, green_val, blue_val) and apply the returned color to a pixel using setColor(a_pixel, col)).
Example of RGB manipulations here.
Some others here and especially here.
I'm trying to take a screenshot of a game, bejeweled (an 8x8 board), and extract the board position from the screenshot. I've tried Image/Imagestat, autopy, and grabbing individual pixels from the middle of the slots but these haven't worked. So I'm thinking taking the average value for each square of the 8x8 grid would identify each piece - but I've been unable to do so with Image/Imagestat and autopy.
Anyone know a way to get the pixel or color values for a region of an image? Or a better way to identify segments of an image with a dominant color?
I've found a way to do this with PIL using Imagegrab and ImageStat. Here's to grab the screen and crop to the game window:
def getScreen():
# Grab image and crop it to the desired window. Find pixel borders manually.
box = (left, top, right, bottom)
im = ImageGrab.grab().crop(box)
#im.save('testcrop.jpg') # optionally save your crop
for y in reversed(range(8)):
for x in reversed(range(8)):
#sqh,sqw are the height and width of each piece.
#each pieceim is one of the game piece squares
piecebox = ( sqw*(x), sqh*(y), sqw*(x+1), sqh*(y+1))
pieceim = im.crop(piecebox)
#pieceim.save('piececrop_xy_'+ str(x) + str(y) + '.jpg')
stats = ImageStat.Stat(pieceim)
statsmean = stats.mean
Rows[x][y] = whichpiece(statsmean)
The above creates an image for all 64 pieces, identifies piecetype, and stores that in the array of arrays 'Rows'. I then grabbed the average RGB values with stats.mean for each piecetype and stored them in a dictionary (rgbdict). Copy all the outputs into Excel and filter by color type to get those averages. Then I used an RSS method and that dictionary to statistically match the images to the known piecetypes. (RSS ref: http://www.charlesrcook.com/archive/2010/09/05/creating-a-bejeweled-blitz-bot-in-c.aspx)
rgbdict = {
'blue':[65.48478993, 149.0030965, 179.4636593], #1
'red':[105.3613444,55.95710092, 36.07481793], #2
......
}
def whichpiece(statsmean):
bestScore = 100
curScore= 0
pieceColor = 'empty'
for key in rgbdict.keys():
curScore = (math.pow( (statsmean[0]/255) - (rgbdict[key][0]/255), 2)
+ math.pow( (statsmean[1]/255) - (rgbdict[key][1]/255), 2)
+ math.pow( (statsmean[2]/255) - (rgbdict[key][2]/255), 2) )
if curScore < bestScore:
pieceColor = key
bestScore = curScore
return piececolor
With these two functions the screen can be scraped, and the board state transferred into an array upon which moves can be decided. Best of luck if this helps anyone, and let me know if you fine tune a move picker.