First of all, I am at a complete beginner level of python, with just under a month of experience.
I am writing a simple program as part of a project whereby my two primary objectives are create a function from scratch that flips and rotates an image, and another function that essentially changes the rgb value of the image (e.g. make the image greyscale). The user will be given an choice where they choose one of the effects to apply to the image. I already have pillow installed, do I need any other libraries? I want to know how I can go about creating these from scratch.
Any help would be appreciated
Thanks
EDIT: To clarify, I will be using pillow, but I will be creating the rotate and greyscale functions myself
Pillow provides access to the individual pixels of an image, which can help you achieve what you want. Of course, the library functions like rotate() are faster ways to do this, but you just want to explore and learn, which is half the fun of programming.
You can create a new image and then get the pixel at a particular coordinate.
im = Image.new('RGBA', (250, 250))
im.getpixel((0, 0))
getpixel() will return a tuple of the color information, containing (red, green, blue, alpha)
You can also loop through an image and 'put' a new pixel using the same tuple of color values.
for x in range(200):
for y in range(30):
im.putpixel((x, y), (250, 0, 250))
You can save your image when you are done.
im.save('myImage.png')
Rotation in 90 degree increments is very simple, you can just swap the x and y values in the pixels.
for x in range(200):
for y in range(200):
p = sourceImage.getpixel(x,y) # copy a pixel
targetImage.getpixel(y,x,p) # put it in the new image, rotated 90 deg
Your next visit will be to look up computer graphics techniques.
You will need to analyze the image as a matrix, and then swap the columns and rows. This requires understanding of linear algebra for optimizations. And if you try to brute force it, you will be waiting about 30 minutes to rotate each image (been there, done that).
Here is a look at inplace rotating. The gist of the program is:
# Python3 program to rotate a matrix by 90 degrees
N = 4
# An Inplace function to rotate
# N x N matrix by 90 degrees in
# anti-clockwise direction
def rotateMatrix(mat):
# Consider all squares one by one
for x in range(0, int(N/2)):
# Consider elements in group
# of 4 in current square
for y in range(x, N-x-1):
# store current cell in temp variable
temp = mat[x][y]
# move values from right to top
mat[x][y] = mat[y][N-1-x]
# move values from bottom to right
mat[y][N-1-x] = mat[N-1-x][N-1-y]
# move values from left to bottom
mat[N-1-x][N-1-y] = mat[N-1-y][x]
# assign temp to left
mat[N-1-y][x] = temp
# Function to pr the matrix
def displayMatrix( mat ):
for i in range(0, N):
for j in range(0, N):
print (mat[i][j], end = ' ')
print ("")
mat = [[0 for x in range(N)] for y in range(N)] # Driver Code
# Test case 1
mat = [ [1, 2, 3, 4 ],
[5, 6, 7, 8 ],
[9, 10, 11, 12 ],
[13, 14, 15, 16 ] ]
'''
# Test case 2
mat = [ [1, 2, 3 ],
[4, 5, 6 ],
[7, 8, 9 ] ]
# Test case 3
mat = [ [1, 2 ],
[4, 5 ] ]
'''
rotateMatrix(mat)
displayMatrix(mat) # Print rotated matrix
# This code is contributed by saloni1297
Related
this is a long one. I am currently (from way too much time) trying to differentiate between crossed out square, completely blacked out square, blank square, crossed out/blacked out circle and blank circle gather from a scanned image like the one you can see. My current approach (link to the repo here, the focus is on the function detect_answers in evaluator.py, sorry for the occasional italian comments/names) is:
Find the outer black borders;
Align the image to compensate for scanning misalignment;
Retrieve ID barcode;
Divide the whole image in small square such that each one contains either a circle or a square;
Classify each square based on the category mentioned above (crossed out square in green, completely blacked out square in blue, blank square can be left unprocessed, crossed out/blacked out circle in red and blank circle).
def detect_answers(bgr_image: np.array, bgr_img_for_debug: np.array,
x_cut_positions: List[int], y_cut_positions: Tuple[int],
is_60_question_sim, debug: str):
question_multiplier: int = 15 if is_60_question_sim else 20
letter: Tuple[str, ...] = ("L", "", "A", "B", "C", "D", "E")
user_answer_dict: Dict[int, str] = {i: "" for i in range(1, 61 - 20 * int(not is_60_question_sim))}
gr_image: np.array = cv2.cvtColor(bgr_image, cv2.COLOR_BGR2GRAY)
# load SVM model
load_path = os.getcwd()
clf = load(os.path.join(load_path, "reduced.joblib"))
for y_index in range(len(y_cut_positions) - 1):
for x_index in range(len(x_cut_positions) - 1):
# if you are on a column with only numbers, skip it
if not (x_index - 1) % 7:
continue
x_top_left = int(not x_index % 7) * 7 + x_cut_positions[x_index]
x_bottom_right = int(not x_index % 7) * 2 + x_cut_positions[x_index + 1]
y_top_left: int = y_cut_positions[y_index]
y_bottom_right: int = y_cut_positions[y_index + 1]
crop_for_prediction: np.array = gr_image[y_top_left:y_bottom_right, x_top_left:x_bottom_right]
crop_for_prediction: np.array = cv2.resize(crop_for_prediction, (18, 18))
# category = ("QB", "QS", "QA", "CB", "CA")
# 0 1 2 3 4
crop_for_prediction: np.array = np.append(crop_for_prediction,
[x_index % 7, int(np.mean(crop_for_prediction))])
predicted_category_index: int = clf.predict([crop_for_prediction])[0]
return user_answer_dict
I am expecting to have a list with the position of each relevant coloured square and its category. Currently this is done via a trained model used previous manually labeled data (file can be found in the github repo). I have tried many different approach (mean of each square, counting black pixels, ...) but no method seems to be reliable enough to handle all the variation that handwriting provides. Furthermore, the algorithm should be quite fast since each time it runs it needs to evaluate 500-800 tests.
Sample input
Expected output
Correct Classification, ignore red numbers
WrongClassification
In particular, question 1D should be rounded by red contours (since it's a blacked out square) and almost everything from the last column didn't get classified correctly.
At your disposal for any question, clarification or discussion. Cheers
I Want to mark the pixels,
Mark=[2, 455, 6, 556, 12, 654, 22, 23, 4,86,.....]
in such a way that it will not mark the 1st 2 pixels and then mark next 455 pixels by a color, again for next 6 pixels it will not mark and again mark the next 556 pixels by the same color and so on.
The size of the image is 500x500x3. How do I calculate these steps?
Img=np.zeros((500,500,3),dtype=np.uint8)
Your algorithm is actually in your question. By 500x500x3 I guess that you mean your image is 500 (width) on 500 (height) with 3 color channel?
It could be implemented as follows, without any optimizations:
color = (128, 50, 30)
x, y = 0, 0
for (skip, count) in [Mark[n:n+2] for n in range(len(Mark) // 2)]:
x += skip
y += x // 500 # keep track of the lines, when x > 500,
# it means we are on a new line
x %= 500 # keep the x in bounds
# colorize `count` pixels in the image
for i in range(0, count):
Img[x, y, 0] = color[0]
Img[x, y, 1] = color[1]
Img[x, y, 2] = color[2]
x += 1
y += x // 500
x %= 500 # keep the x in bounds
The zip([a for i, a in enumerate(Mark) if i % 2 == 0], [a for i, a in enumerate(Mark) if i % 2 != 0]) is a just a way to group the pairs (skip, pixel to colorize). It could definitely be improved though, I'm no Python expert.
EDIT: modified the zip() to use [Mark[n:n+2] for n in range(len(Mark) // 2)] as suggested by Peter, much simpler and easier to understand.
The easiest way is probably to convert the image to a Numpy array:
import numpy as np
na = np.array(Img)
And then use Numpy ravel() to give you a flattened (1-D) view of the array
flat = np.ravel(na)
You can now see the shape of your flat view:
print(flat.shape)
Then you can do your colouring by iterating over your array of offsets from your question. Then the good news is that, because ravel() gives you a view into your original data all the changes you make to the view will be reflected in your original data.
So, to get back to a PIL Image, all you need is:
RecolouredImg = Image.fromarray(na)
Try it out by just colouring the first ten pixels before worrying about your long list.
If you like working with Python lists (I don't), you can achieve a similar effect by using PIL getdata() to get a flattened list of pixels and then process the list against your requirements and putdata() to put them all back. The effect will be the same, just choose a method that fits how your brain works.
I have an array representing a light source in space made as such:
source2D = np.zeros((256, 256))
with any amount of the pixels = 1. An example I have is a point source which is generated by:
source2D[126:128, 126:128] = 1
And I am running a monte carlo simulation which shoots a ray from each part of the array where the value = 1. Currently I am iterating over the entire array but I would save a lot of time by only picking out the elements where array = 1 and iterating over them. I should add that this function should be made to accept a generic 256x256 where any elements could be set to 1, so cropping the array is not an option. What is the fastest way to do this? I am also using tensorflow so if there is an implementation using that, that would also be an option
Right now my code looks somethinglike this:
while pc < 1000000:
pc+=1
# Randomize x and y as coordinates on source
x = np.random.randint(0, source2D.shape[0]) # 0 to 255 for this example
y = np.random.randint(0, source2D.shape[1]) # 0 to 255 for this example
# Shoot raycast from x,y to point on detector
Solved with hpaulj's comment:
source2D = np.zeros((256, 256)) # Testing with point source
source2D[126:128, 126:128] = 1
nonzero_entries = np.nonzero(source2D)
i = np.random.choice(nonzero_entries[0])
j = np.random.choice(nonzero_entries[1])
I'm using cv2.goodFeaturesToTrack function to find feature points in an image. The end goal is to extract square blocks of certain size, with feature points being the centers of those blocks.
However, lots of the feature points are close to each other, so the blocks are overlapping, which is not what I want.
This is an example of all feature points (centers):
array([[3536., 1419.],
[2976., 1024.],
[3504., 1400.],
[3574., 1505.],
[3672., 1453.],
[3671., 1442.],
[3489., 1429.],
[3108., 737.]])
Let's say I want to find the first n blocks with a blockRadius = 400 which are not overlapping. Any ideas on how to achieve this?
You could get closer with scipy.spatial.KDTree - though it doesn't support querying blocks that consists of distinct amounts of points in blocks. So it can be used in conjunction with another library python-igraph that allows to find connected components of close points in a fast manner:
from scipy.spatial import KDTree
import igraph as ig
data = np.array([[3536., 1419.],
[2976., 1024.],
[3504., 1400.],
[3574., 1505.],
[3672., 1453.],
[3671., 1442.],
[3489., 1429.],
[3108., 737.]])
edges1 = KDTree(data[:,:1]).query_pairs(r=400)
edges2 = KDTree(data[:,1:]).query_pairs(r=400)
g = ig.Graph(n = len(data), edges=edges1 & edges2)
i = g.clusters()
So clusters corresponds to sequences of indices of block points of some kind of internal type igraph. There's a quick preview:
>>> print(i)
Clustering with 8 elements and 2 clusters
[0] 0, 2, 3, 4, 5, 6
[1] 1, 7
>>> pal = ig.drawing.colors.ClusterColoringPalette(len(i)) #number of colors used
color = pal.get_many(i.membership) #list of color tags
ig.plot(g, bbox = (200, 100), layout=g.layout('circle'), vertex_label=g.vs.indices,
vertex_color = color, vertex_size = 12, vertex_label_size = 8)
Example of usage:
>>> [data[n] for n in i] #or list(i)
[array([[3536., 1419.],
[3504., 1400.],
[3574., 1505.],
[3672., 1453.],
[3671., 1442.],
[3489., 1429.]]),
array([[2976., 1024.],
[3108., 737.]])]
Remark: this method allows to work with pairs of close points instead of n*n matrix which is more efficient in memory in some cases.
You'll need something iterative to do that, as recurrent dropouts like this aren't vectorizable. Something like this will work, I think
from scipy.spatial.distance import pdist, squareform
c = np.array([[3536., 1419.],
[2976., 1024.],
[3504., 1400.],
[3574., 1505.],
[3672., 1453.],
[3671., 1442.],
[3489., 1429.],
[3108., 737.]])
dists = squareform(pdist(c, metric = 'chebyshev')) # distance matrix, chebyshev here since you seem to want blocks
indices = np.arange(c.shape[0]) # indices that haven't been dropped (all to start)
out = [0] # always want the first index
while True:
try:
indices = indices[dists[indices[0], indices] > 400] #drop indices that are inside threshhold
out.append(indices[0]) # add the next index that hasn't been dropped to the output
except:
break # once you run out of indices, you'll get an IndexError and you're done
print(out)
[0, 1]
let's try with a whole bunch of points:
np.random.seed(42)
c = np.random.rand(10000, 2) * 800
dists = squareform(pdist(c, metric = 'chebyshev')) # distance matrix, checbyshev here since you seem to want squares
indices = np.arange(c.shape[0]) # indices that haven't been dropped (all to start)
out = [0] # always want the first index
while True:
try:
indices = indices[dists[indices[0], indices] > 400] #drop indices that are inside threshhold
out.append(indices[0]) # add the next index that hasn't been dropped to the output
except:
break # once you run out of indices, you'll get an IndexError and you're done
print(out, pdist(c[out], metric = 'chebyshev'))
[0, 2, 6, 17] [635.77582886 590.70015659 472.87353138 541.13920029 647.69071411
476.84658995]
So, 4 points (makes sense since 4 400x400 blocks tile a 800x800 space with 4 tiles), mostly low values (17 << 10000) and distance between kept points is always > 400
I wrote a python script for satellite image processing. Basically, what the code does is look over every window around every pixel from an image and compare it to a specific region of interest from the same image. The window that has the most similar information is stored and converted into a geospatial vector.
Allow me to further explain: I have monthly satellite images ranging from 2013 to 2020 of a specific location, summing up to 90 images (90 months). I also have a vector file (.shp) with 52 features, my regions of interest (ROI's). For each month, i.e., each image, I have to look over my ROI's, collect the digital value of all the pixels within the ROI, and calculate its average digital value. I do the same thing for all possible 3x3 pixels (window) from that image and compare their means. In the end, every ROI has a counterpart window, which has the smaller Euclidian Distance between their average digital values. The outputs are 90 vector files(.shp, each one corresponding to an image/month), with 52 windows by vector file, each kernel corresponding to a window that has the closest information to the ROI.
Not every pixel in an image is allowed to be part of the kernel, so I also have 90 masks that allow me to test if that window is electable.
The problem is that each monthly vector is taking about 8 hours to be generated. In total, it took me about 30 days to generate all the vectors, although I have a fairly good machine (w10, 64bit, intelCorei7-4700HQ CPU # 2.40GHz, gtx8520m, 4GB ram ...). I understand that nested for loops are not the best practice when coding, and also that python does not supports parallel computation very well (which I suspect would be the best approach since the windows are independent).
I tried different approaches, like the #jit, tried to use the GPU for the computation and also tried to use all 4 cores with the multiprocessing. The first two approaches didn't work, probably because my function is not suitable to be translated, as far as I understand. The last one ran without problems, but it took the double amount of time to finish it.
My question is: what changes should I apply to my code in order to make it faster, not only concerning "good writing", but also considering that I want to use all 4 cores and possibly my GPU for running it. I am not a computer scientist, therefore I am really struggling to find a solution.
Here is the code:
import pandas as pd
import geopandas
from rasterio.windows import Window
import rasterio
import numpy as np
from rasterstats import zonal_stats
from shapely.geometry import box
import os
import ntpath
import time
def path_leaf(path):
head, tail = ntpath.split(path)
return tail or ntpath.basename(head)
def getListOfFiles(dirName):
listOfFile = os.listdir(dirName)
allFiles = list()
for entry in listOfFile:
# Create full path
fullPath = os.path.join(dirName, entry)
# If entry is a directory then get the list of files in this directory
if os.path.isdir(fullPath):
allFiles = allFiles + getListOfFiles(fullPath)
else:
allFiles.append(fullPath)
return allFiles
#The address of the folder containing the ROI's
SHP_address = 'F:\\nota_tecnica_2\\vetores\\amostras_interesse_final\\ROI_wgs84_9_final_dissolvido.shp'
#The address of the satellite images
a = getListOfFiles('C:\\dados_nota_tecnica\\MODIS\\MODIS_quality_mosaic_rec')
#The address of the masks (binary images where 0 is a electable pixel and 1 is a not electable pixel)
b = 'C:\\dados_nota_tecnica\\VIIRS\\final\\'
#The folder where I store my vectors at the end
c = 'F:\\nota_tecnica_2\\vetores\\amostras_controle\\'
#A variable defining which images from the "a" list should be analyzed
_from = 0
_to = 90
#The function that will search for the windows
def janela_movel(MODIS_list,local_viirs,saida):
start = time.time()
df_vazio = pd.DataFrame(
{'mean_b1': [],
'mean_b2': [],
'mean_b4': [],
'euc_dist': [],
'left': [],
'right': [],
'bottom': [],
'top': [],
'id_alvo': []})
for file in (range(_from,_to)): #len(MODIS_list)
MODIS_address = MODIS_list[file]
#Searches for a maks image that matches the name of the satellite image
viirs_address = local_viirs+path_leaf(MODIS_address)
#Open the matched mask image
raster_viirs = rasterio.open(viirs_address)
#Open the raster image
raster_MODIS = rasterio.open(MODIS_address)
#Caculates the average digital value in a ROI for 3 bands of the image (RED, GREEN and NEAR-INFRARED) (actually surface reflectance at NADIR);
media_zonal_b1 = zonal_stats(SHP_address, MODIS_address,
stats="mean",
nodata=0,
all_touched=False,
categorical=False,
geojson_out=True,
band=1)
media_zonal_b2 = zonal_stats(SHP_address, MODIS_address,
stats="mean",
nodata=0,
all_touched=False,
categorical=False,
geojson_out=True,
band=3)
media_zonal_b4 = zonal_stats(SHP_address, MODIS_address,
stats="mean count",
nodata=0,
all_touched=False,
categorical=False,
geojson_out=True,
band=5)
#Now the code will access the ROI. For each one it will extract not only the already computated mean value, but also the coordinates
#of the ROI and its ID (identificator)
for x in range(len(media_zonal_b1)):
mean_band1_alvo = media_zonal_b1[x]['properties']['mean']
mean_band2_alvo = media_zonal_b2[x]['properties']['mean']
mean_band4_alvo = media_zonal_b4[x]['properties']['mean']
id_alvo = x
array_vazio = []
#Here I set the size of the window/kernel
i = 3
j = 3
#Now it will access the satellite image and move this 3x3 window through all pixels
for i_r in range(raster_viirs.height):
for j_r in range(raster_viirs.width):
row_start = i_r
row_stop = i_r + i
col_start = j_r
col_stop = j_r + j
Win = Window.from_slices(slice(row_start, row_stop), slice(col_start, col_stop))
croped = raster_viirs.read(window=Win)
#This is some code to prevent NAN values and not electable pixels
if (-999.0 not in croped) and (1 not in croped) and (np.isnan(croped).any() != True):
bounds = raster_viirs.window_bounds(Win)
croped2 = raster_MODIS.read(window=Win) #aplicando a janela extraída ao dado e reflectancia
if ((np.isnan(croped2).any()) != True) and (croped2.size != 0):
mean_band1 = np.mean(croped2[0])
mean_band2 = np.mean(croped2[2])
mean_band4 = np.mean(croped2[4])
if (mean_band1_alvo or mean_band2_alvo or mean_band4_alvo) is None:
mean_band1_alvo = -999
mean_band2_alvo = -999
mean_band4_alvo = -999
dist = -999
#Calculates the euclidian distance between the bands of the pixels within the window and the ROI
else:
dist = (((mean_band1 - mean_band1_alvo) ** 2) + ((mean_band2 - mean_band2_alvo) ** 2) + (
(mean_band4 - mean_band4_alvo) ** 2)) ** 0.5
# Creates a dataframe with all the electable kernels
array_dados = [dist,mean_band1,mean_band2,mean_band4,bounds[0],bounds[1],bounds[2],bounds[3]]
#aggreagte the kernels in a single array
array_vazio = array_vazio+array_dados
#Transforms in a 2d array and order it to find the most alike kernel (smallest euclidian distance)
array_dados_2d = np.reshape(array_vazio, (-1,8))
array_dados_2d = array_dados_2d[np.argsort(array_dados_2d[:, 0])]
#Number of windows per ROI
numero_de_amostras = 1
array_dados_filtered = array_dados_2d[0:numero_de_amostras, ]
#Accumulates the windows of all the ROI in a single vector (.shp file)
df_dados = pd.DataFrame(
{'euc_dist': array_dados_filtered[:, 0],
'mean_b1': array_dados_filtered[:, 1],
'mean_b2': array_dados_filtered[:, 2],
'mean_b4': array_dados_filtered[:, 3],
'left': array_dados_filtered[:, 4],
'bottom': array_dados_filtered[:, 5],
'right': array_dados_filtered[:, 6],
'top': array_dados_filtered[:, 7],
'id_alvo': id_alvo})
df_vazio = df_vazio.append(df_dados, ignore_index = True)
#Some geocoding for the output vector file.
bbox = df_vazio.apply(lambda row: box(row.left, row.bottom, row.right, row.top), axis=1)
geo_dataframe = geopandas.GeoDataFrame(df_vazio, geometry=bbox)
geo_dataframe.crs = {'init': 'epsg:4326'}
local = saida
geo_dataframe.to_file(driver = 'ESRI Shapefile', filename= (local+path_leaf(MODIS_address)+'.shp'))
df_vazio = pd.DataFrame(
{'mean_b1': [],
'mean_b2': [],
'mean_b4': [],
'euc_dist': [],
'left': [],
'right': [],
'bottom': [],
'top': [],
'id_alvo': []})
end = time.time()
print(end - start)
#finally appling the function
janela_movel(a,b,c)```
The main problem is probably the very high amount of call to raster_MODIS.read which probably perform a slow I/O operation. Moreover, each cell is actually read 9 times (in average) due to the window size.
To solve that, you can read the whole raster into memory in a 2D numpy array and work directly on it. If the raster is too big, it can be split in line bands or even 2D tiles (although this is a bit complex).
Then, if this is not enough, you can then put the numpy computational part (the 2 nested inner loops) in a function and speed it up with numba #njit (note that it will be better/faster if array_vazio is a 2D numpy array allocated directly with the good size, or even with an overestimated size, as numba does not like Python lists).
Finally, if this is still not enough, you can try to use the multiprocessing module to compute each raster in parallel (in a separate process and merge process data at the end).
The resulting code should be several order of magnitude faster.