Visualizing a matrix in pygame to form a grid - python

So I'm new to both pathfinding and and pygame but I'm trying to visualize the matrix into a grid in pygame. However, when I try the boxes get further and further away and it infinitely loops. So my question is how can i Get this to display a 4x4 grind like my matrix, with each square properly spaced? The code was split into 2 files. the main problem is in the second, I put the first here just for context. Also just theory is fine too I don't necessarily need a code solution just wann wrap my head around this.
P.s. To any familiar with pygame is there anyway to get the Grid/grid.node() information from the pathfinder? thatd make this a bit easier i think
from pathfinding.core.grid import Grid
from pathfinding.finder.a_star import AStarFinder
matrix = [
[1,1,0,1],
[1,0,0,1],
[1,1,0,1],
[1,1,1,1]
]
#creates a grid from the matrix
grid = Grid(matrix=matrix)
start = grid.node(0,0)
end = grid.node(3,0)
class setup:
def createFinder(self):
#create a new instance of a finder
self.finder = AStarFinder(diagonal_movement=DiagonalMovement.always)
#The find_path function returns 2 values, the amounrs of times it runs to --
#-- find a path(runs) and the length of the finished path(path)
self.path, self.runs = self.finder.find_path(start, end, grid)
print(self.path)
def showPath(self):
print('operations', self.runs, 'path length:', len(self.path))
print(grid.grid_str(path=self.path, start=start, end=end))
from Pathfinder import *
import pygame
#creating a pygame screen
white = (255, 255, 255)
black = (198,238,78)
class pygameSetup():
def createDisplay(self):
self.gridDisplay = pygame.display.set_mode((800,600))
self.gridDisplay.fill(white)
def createSquare(self,x,y):
pygame.draw.rect(self.gridDisplay, black, [x,y,10,10])
def visualizeGrid(self):
x = 0
y = 0
z = 0
w = 0
v = 0
while w <=3:
pygame.display.update()
for i in matrix[z]:
print("The matrix is ", matrix[z],"and Z is: ", i)
v += 1
x += x + 11
if x >= 700:
x = 0
y += 11
if v == 4:
i += 1
z+=1
if z >= 4:
z = 0
v = 0
self.createSquare(x,y)
w+1
pS = pygameSetup()
pS.createDisplay()
pS.visualizeGrid()

Your visualizeGrid function is a lot more than you actually need. I am not sure why you have all those variables (v,w,x,y,z), you only need an x and a y coordiante. If you only want to display the grid, here is a simple working solution:
import pygame
gridDisplay = pygame.display.set_mode((200, 200))
pygame.display.get_surface().fill((200, 200, 200)) # background
matrix = [[1 ,1 ,0 ,1],
[1 ,0 ,0 ,1],
[1 ,1 ,0 ,1],
[1 ,1 ,1 ,1]]
# we use the sizes to draw as well as to do our "steps" in the loops.
grid_node_width = 10
grid_node_height = 10
def createSquare(x, y, color):
pygame.draw.rect(gridDisplay, color, [x, y, grid_node_width, grid_node_height ])
def visualizeGrid():
y = 0 # we start at the top of the screen
for row in matrix:
x = 0 # for every row we start at the left of the screen again
for item in row:
if item == 0:
createSquare(x, y, (255, 255, 255))
else:
createSquare(x, y, (0, 0, 0))
x += grid_node_width # for ever item/number in that row we move one "step" to the right
y += grid_node_height # for every new row we move one "step" downwards
pygame.display.update()
visualizeGrid() # call the function
while True:
pass # keeps the window open so you can see the result.
Concerning
get the Grid/grid.node() information from the pathfinder
the docs mention that you can get a node from your grid with grid.node(index_x, index_y):
matrix = [
[1, 1, 1],
[1, 0, 1],
[1, 1, 1]
]
grid = Grid(matrix=matrix)
start = grid.node(0, 0) # getting the first node in the 3x3 Matrix
end = grid.node(2, 2) # getting the last node in the 3x3 Matrix

Related

Delaunay triangulation in Python with opencv and igraph, strange behavior

So I have some code in Python (3.9.13) to obtain a Delaunay triangulation of a set of points in real time and analyze the graph properties. First I use OpenCV (opencv-python 4.6.0.66) Subdiv2D method to obtain the triangulation. Then I convert it in a graph I can analyze with igraph (igraph 0.10.3). But I am not sure why, once every few frames the graph produced by igraph is messed up such as shown in this image:
graph messed up (Left is OpenCV and right is igraph):
Else it is working properly.
good graph (Left is OpenCV and right is igraph):
Here is my demo code:
import time
import numpy
import cv2
import igraph as ig
# Draw a point
def draw_point(img, p, color):
cv2.circle(img, (int(p[0]), int(p[1])), 2, color, 0)
# Get a triangulation
def get_delaunay(subdiv):
return subdiv.getTriangleList()
# Draw delaunay triangles
def draw_delaunay(img, subdiv, delaunay_color):
triangleList = subdiv.getTriangleList()
size = img.shape
r = (0, 0, size[1], size[0])
for t in triangleList:
pt1 = (int(t[0]), int(t[1]))
pt2 = (int(t[2]), int(t[3]))
pt3 = (int(t[4]), int(t[5]))
cv2.line(img, pt1, pt2, delaunay_color, 1, cv2.LINE_AA, 0)
cv2.line(img, pt2, pt3, delaunay_color, 1, cv2.LINE_AA, 0)
cv2.line(img, pt3, pt1, delaunay_color, 1, cv2.LINE_AA, 0)
if __name__ == '__main__':
NUM_PART = 500
SIZE = 1000
REPEAT = 10
for iteration in range(REPEAT):
positions = numpy.random.randint(0, SIZE, size=(NUM_PART, 2))
print("There is {p} positions. And {up} unique position".format(p=len(positions), up=len(numpy.unique(positions, axis=1))))
# Create an instance of Subdiv2D
rect = (0, 0, SIZE, SIZE)
subdiv = cv2.Subdiv2D(rect)
timer = time.time()
# Insert points into subdiv
print("There is {} points in subdiv".format(len(positions)))
for p in positions:
p = p.astype("float32")
subdiv.insert(p)
# get triangulation
trilist = get_delaunay(subdiv)
print("Took {}s".format(round(time.time() - timer, 12)))
print("there is {} triangles in trilist".format(len(trilist)))
# create image
opencv_image = numpy.zeros((SIZE, SIZE, 3))
# Draw delaunay triangles
draw_delaunay(opencv_image, subdiv, (255, 255, 255))
# Draw points
for p in positions:
draw_point(opencv_image, p, (0, 0, 255))
timer = time.time()
n_vertices = NUM_PART
# create graph
g = ig.Graph(n=n_vertices, )
g.vs["name"] = range(NUM_PART)
print("graph name vector of length {l}:\n{v}".format(l=len(g.vs["name"]), v=g.vs["name"]))
# Inversion x positions
positionsx = [SIZE - pos for pos in positions[:, 0]]
g.vs["x"] = positions[:, 0]
print("graph x vector of length {l}:\n{v}".format(l=len(g.vs["x"]), v=g.vs["x"]))
g.vs["y"] = positions[:, 1]
print("graph y vector of length {l}:\n{v}".format(l=len(g.vs["y"]), v=g.vs["y"]))
print("Graph took {}s".format(round(time.time() - timer, 12)))
list_vtx = []
for tri in trilist:
vertex1, _ = subdiv.findNearest((tri[0], tri[1]))
vertex2, _ = subdiv.findNearest((tri[2], tri[3]))
vertex3, _ = subdiv.findNearest((tri[4], tri[5]))
list_vtx.extend([vertex3, vertex2, vertex1])
list_cleared = list(dict.fromkeys(list_vtx))
list_cleared.sort()
print("list cleared of length {len}: {lst}".format(len=len(list_cleared), lst=list_cleared))
for tri in trilist:
vertex1, _ = subdiv.findNearest((tri[0], tri[1]))
vertex2, _ = subdiv.findNearest((tri[2], tri[3]))
vertex3, _ = subdiv.findNearest((tri[4], tri[5]))
#print("vertex 1: {v} of position {p}".format(v=vertex1, p=(tri[0], tri[1])))
#print("vertex 2: {v} of position {p}".format(v=vertex2, p=(tri[2], tri[3])))
#print("vertex 3: {v} of position {p}".format(v=vertex3, p=(tri[4], tri[5])))
# -4 because https://stackoverflow.com/a/52377891/18493005
g.add_edges([
(vertex1 - 4, vertex2 - 4),
(vertex2 - 4, vertex3 - 4),
(vertex3 - 4, vertex1 - 4),
])
# simplify graph
g.simplify()
nodes = g.vs.indices
print(nodes)
print(subdiv)
# create image
igraph_image = numpy.zeros((SIZE, SIZE, 3))
for point in g.vs:
draw_point(igraph_image, (point["x"], point["y"]), (0, 0, 255))
for edge in g.es:
# print(edge.tuple)
# print(g.vs["x"][edge.tuple[0]])
cv2.line(igraph_image, (int(g.vs["x"][edge.tuple[0]]), int(g.vs["y"][edge.tuple[0]])),
(int(g.vs["x"][edge.tuple[1]]), int(g.vs["y"][edge.tuple[1]])), (255, 255, 255), 1, cv2.LINE_AA, 0)
numpy_horizontal = numpy.hstack((opencv_image, igraph_image))
# Show results
cv2.imshow('L: opencv || R: igraph', numpy_horizontal)
cv2.waitKey(0)
I try to have a repeatable result of my graph in igraph. But it is only working 80% of the time which is pretty strange behavior. Any idea of what are my mistakes here?
Edit: it seems to be a variation in the length of the list generated by:
trilist = get_delaunay(subdiv)
list_vtx = []
for tri in trilist:
vertex1, _ = subdiv.findNearest((tri[0], tri[1]))
vertex2, _ = subdiv.findNearest((tri[2], tri[3]))
vertex3, _ = subdiv.findNearest((tri[4], tri[5]))
list_vtx.extend([vertex3, vertex2, vertex1])
list_cleared = list(dict.fromkeys(list_vtx))
list_cleared.sort()
but I am not sure why.
Edit2:
After the modification sugested by Markus. I do not get a messed up graph anymore. But now the graph is missing some edges
x_pos = [0] * NUM_PART # create 0-filled array of x-positions
y_pos = [0] * NUM_PART # create 0-filled array of y-positions
edges = [] # create empty array of edges
# for each triangle add vertex positions and edges
for tri in trilist:
vertex1 = subdiv.findNearest((tri[0], tri[1]))[0] - 4
vertex2 = subdiv.findNearest((tri[2], tri[3]))[0] - 4
vertex3 = subdiv.findNearest((tri[4], tri[5]))[0] - 4
x_pos[vertex1] = tri[0]
y_pos[vertex1] = tri[1]
x_pos[vertex2] = tri[2]
y_pos[vertex2] = tri[3]
x_pos[vertex3] = tri[4]
y_pos[vertex3] = tri[5]
edges.append((vertex1, vertex2))
edges.append((vertex2, vertex3))
edges.append((vertex2, vertex3))
# create graph
g = ig.Graph(NUM_PART, edges)
g.vs["name"] = range(NUM_PART)
g.vs["x"] = x_pos
g.vs["y"] = y_pos
g.simplify()
The following image shows an overlay between 3 type of drawing (White=opencv , Red=Markus suggestion, Green + Red = previous method used)
Overlay of Markus solution in case of no mess up
Overlay of Markus solution in case of mess up
So Markus solution indeed remove the mess up, but also some edges, even in the case that was working previously.
So in fact my test code was working as expected. The issue was not from Subdiv2D or igraph but from the generation of my position.
I made a mistake verifying the uniqueness of my position with
len(numpy.unique(positions, axis=1))
but should have been using
len(numpy.unique(positions, axis=0)).
So when I used subdiv.findNearest()[0] or subdiv.locate()[2] I was in fact finding several points at the same position, and only the first index was thrown back by the function and so the graph was being messed up.
In order to generate unique position I uses the following code and the graph messing disappeared:
rng = numpy.random.default_rng()
xx = rng.choice(range(SIZE), NUM_PART, replace=False).astype("float32")
yy = rng.choice(range(SIZE), NUM_PART, replace=False).astype("float32")
pp= numpy.stack((xx,yy), axis=1)
The fact that numpy.random.randint(0, 1000, size=(500, 2)) was providing a similar position every 10 or so frame is pretty strange to me as the probability of getting two identical positions seems intuitively to be lower than 0.1

How to obtain surfaces tag in Gmsh Python api?

i am trying to generate geometries and meshes with the Python api of Gmsh, planning to use it in FEniCS.
I started creating my geometry following the steps reported here: https://jsdokken.com/src/tutorial_gmsh.html
The author first create the volume and then retrieve the surfaces with the command:
surfaces = gmsh.model.occ.getEntities(dim=2)
Finally, he is able to relate the surface to the tag simply by finding the center of mass (com). He uses the command gmsh.model.occ.getCenterOfMass(dim,tag) and compares it with the know com position of his surfaces, like this:
inlet_marker, outlet_marker, wall_marker, obstacle_marker = 1, 3, 5, 7
walls = []
obstacles = []
for surface in surfaces:
com = gmsh.model.occ.getCenterOfMass(surface[0], surface[1])
if np.allclose(com, [0, B/2, H/2]):
gmsh.model.addPhysicalGroup(surface[0], [surface[1]], inlet_marker)
inlet = surface[1]
gmsh.model.setPhysicalName(surface[0], inlet_marker, "Fluid inlet")
elif np.allclose(com, [L, B/2, H/2]):
gmsh.model.addPhysicalGroup(surface[0], [surface[1]], outlet_marker)
gmsh.model.setPhysicalName(surface[0], outlet_marker, "Fluid outlet")
elif np.isclose(com[2], 0) or np.isclose(com[1], B) or np.isclose(com[2], H) or np.isclose(com[1],0):
walls.append(surface[1])
else:
obstacles.append(surface[1])
Now, my problem is that this cannot work if two or more surfaces share the same com, such as two concentric cylinders.
How can i discriminate between them in such situation?
For example in case of an hollow cylinder, i would like to have a tag for each surface in order to apply different boundary conditions in FEniCS.
Thanks in advance!
You can make use of gmsh.model.getAdjacencies(dim,tag) where dim and tag are the dimension and tag of your entity of interest. This functions returns two lists up, down. The first one gives you the tags of all entities adjacent (=neighbouring) to the entity of interest with dimension dim + 1. The second list gives you the tags of all entities adjacent to the entity of interest with dimension dim - 1.
In 3D (i.e. dim = 3) the up list will be empty because there are no 4D structures in gmsh. The down list will contain all surface tags the boundary of the volume is made of.
Below is an example code. Part 1 is straight forward and in Part 2 I added a functions that sorts the surface tags by their x-coordinate.
import gmsh
gmsh.initialize()
## PART 1:
tag_cylinder_1 = gmsh.model.occ.addCylinder(0, 0, 0, 1, 0, 0, 0.1)
tag_cylinder_2 = gmsh.model.occ.addCylinder(0, 0, 0, 1, 0, 0, 0.2)
gmsh.model.occ.synchronize()
up_cyl_1, down_cyl_1 = gmsh.model.getAdjacencies(3,tag_cylinder_1)
up_cyl_2, down_cyl_2 = gmsh.model.getAdjacencies(3,tag_cylinder_2)
com_1 = gmsh.model.occ.getCenterOfMass(2, down_cyl_1[0])
com_2 = gmsh.model.occ.getCenterOfMass(2, down_cyl_1[1])
com_3 = gmsh.model.occ.getCenterOfMass(2, down_cyl_1[2])
## PART 2:
def calcBoxVolume(box):
dx = box[3] - box[0]
dy = box[4] - box[1]
dz = box[5] - box[2]
return dx*dy*dz
def getOrderedTags(cylTag):
up, down = gmsh.model.getAdjacencies(3,cylTag)
surf_COM = []
for surface in down:
com = [surface] + list(gmsh.model.occ.getCenterOfMass(2, surface))
surf_COM.append(com)
orderedSurfaces = sorted(surf_COM,key = lambda x: x[1])
orderedSurfaceTags = [item[0] for item in orderedSurfaces]
return orderedSurfaceTags
def setPhysicalTags(name,cylTag):
orderedSurfaces = getOrderedTags(cylTag)
gmsh.model.addPhysicalGroup(2, [orderedSurfaces[0]],name="inlet_"+name)
gmsh.model.addPhysicalGroup(2, [orderedSurfaces[1]],name="tube_"+name)
gmsh.model.addPhysicalGroup(2, [orderedSurfaces[3]],name="outlet_"+name)
def setPhysicalTagsCylDiff(name,cylTag):
orderedSurfaces = getOrderedTags(cylTag)
tag_A = orderedSurfaces[1]
tag_B = orderedSurfaces[2]
box_tube_A = gmsh.model.getBoundingBox(2,tag_A)
box_tube_B = gmsh.model.getBoundingBox(2,tag_B)
volBoxA = calcBoxVolume(box_tube_A)
volBoxB = calcBoxVolume(box_tube_B)
if volBoxA > volBoxB:
innerTag = tag_B
outerTag = tag_A
else:
innerTag = tag_A
outerTag = tag_B
gmsh.model.addPhysicalGroup(2, [orderedSurfaces[0]],name="inlet_"+name)
gmsh.model.addPhysicalGroup(2, [innerTag],name="tube_inner_"+name)
gmsh.model.addPhysicalGroup(2, [outerTag],name="tube_outer_"+name)
gmsh.model.addPhysicalGroup(2, [orderedSurfaces[3]],name="outlet_"+name)
# setPhysicalTags("Cylinder_1",tag_cylinder_1)
# setPhysicalTags("Cylinder_2",tag_cylinder_2)
outDimTags, outDimTagsMap = gmsh.model.occ.cut([(3,tag_cylinder_2)],[(3,tag_cylinder_1)])
cylDiffTag = outDimTags[0][1]
gmsh.model.occ.synchronize()
setPhysicalTagsCylDiff("CylDiff",cylDiffTag)
gmsh.model.mesh.generate(2)
gmsh.fltk.run()
gmsh.finalize()

Reading .PNGs, how do you identify clusters of color and rewrite the image file so that every cluster has a unique RGB code?

Continued from this question: How could you rewrite a list of lists so that "islands" of values are unique from one another?
Brief: How would you parse an image, for example:
in such a way that you identify the several clusters of distinct pixels and rewrite the file so that each cluster has a unique color, for example:
Here's how I have tried to implement it with assistance from a few sources, including stackoverflow user #Rabinzel: (detailed reasoning below main code block)
from scipy import ndimage
import numpy as np
from PIL import Image
#set the file path to wherever your provinces.png is located
im = Image.open(r"C:\\Users\\scoop\\Desktop\\prov_test.png")
print('-------------------------------------------')
#DEBUGGING: simply prints the format, size, and mode of your file
print(im.format, im.size, im.mode)
#saves the width and depth of the file
im_xsize = im.size[0]
im_ysize = im.size[1]
#DEBUGGING: prints it
print(im_xsize, im_ysize)
#DEBUGGNG: prints data bands, should be R, G, B
print(im.getbands())
#DEBUGGING: prints RGB value of pixel of choice
print(im.getpixel((0,0)))
print('-------------------------------------------')
#creates array for pixel RGBs
rgb_array = [[None] * im_ysize for length in range(0,im_xsize)]
#fills pixel RGB array
for x in range(0,im_xsize):
for y in range(0,im_ysize):
rgb_array[x][y] = im.getpixel((x,y))
#find unique clusters of identical RGB codes
def find_clusters(array):
clustered = np.empty_like(array)
unique_vals = np.unique(array)
cluster_count = 0
for val in unique_vals:
labelling, label_count = ndimage.label(array == val)
for k in range(1, label_count + 1):
clustered[labelling == k] = cluster_count
cluster_count += 1
return clustered, cluster_count
clusters, cluster_count = find_clusters(rgb_array)
print("Found {} clusters:".format(cluster_count))
#print(clusters)
#defining a list of unique colors
province_color_list = [[0] * 3 for length in range(0,cluster_count)]
#DEBUGGING
print('province count...', cluster_count)
#variables
r = 255
g = 0
b = 0
count = 0
#generating colors
for length in range(0,cluster_count):
province_color_list[length][0] = r
province_color_list[length][1] = g
province_color_list[length][2] = b
g += 25
b += 25
count += 1
if count >= 11:
r -= 1
g = 0
b = 0
count = 0
#DEBUGGING
print('# of colors... ', len(province_color_list))
print(province_color_list)
print('-------------------------------------------')
#writing colors to pixels
for x in range(0,im_xsize):
for y in range(0,im_ysize):
#places province color based on which province current pixel is assigned to
im.putpixel((x,y), (province_color_list[0][0], province_color_list[0][1], province_color_list[0][2]))
#im.save(r"C:\\Users\\scoop\\Desktop\\prov_test.png", im.format)
I load the image using PIL:
im = Image.open(r"C:\\Users\\scoop\\Desktop\\prov_test.png")
I create an array to more easily(?) access the image array, which stores each pixel's color as an RGB color code in tuple form. Then this method identifies the relevant pixel clusters.
rgb_array = [[None] * im_ysize for length in range(0,im_xsize)]
#fills pixel RGB array
for x in range(0,im_xsize):
for y in range(0,im_ysize):
rgb_array[x][y] = im.getpixel((x,y))
#find unique clusters of identical RGB codes
def find_clusters(array):
clustered = np.empty_like(array)
unique_vals = np.unique(array)
cluster_count = 0
for val in unique_vals:
labelling, label_count = ndimage.label(array == val)
for k in range(1, label_count + 1):
clustered[labelling == k] = cluster_count
cluster_count += 1
return clustered, cluster_count
clusters, cluster_count = find_clusters(rgb_array)
Then I create a list of unique RGB codes the length of the # of pixel clustes that exist.
province_color_list = [[0] * 3 for length in range(0,cluster_count)]
#DEBUGGING
print('province count...', cluster_count)
#variables
r = 255
g = 0
b = 0
count = 0
#generating colors
for length in range(0,cluster_count):
province_color_list[length][0] = r
province_color_list[length][1] = g
province_color_list[length][2] = b
g += 25
b += 25
count += 1
if count >= 11:
r -= 1
g = 0
b = 0
count = 0
and finally, I rewrite each pixel with the new RGB code associated with the unique cluster from earlier (and save the image).
#writing colors to pixels
for x in range(0,im_xsize):
for y in range(0,im_ysize):
#places province color based on which province current pixel is assigned to
im.putpixel((x,y), (province_color_list[clusters[x][y]][0], province_color_list[clusters[x][y]][1], province_color_list[clusters[x][y]][2]))
#im.save(r"C:\\Users\\scoop\\Desktop\\prov_test.png", im.format)
Unfortunately there's multiple issues with this script and I get the feeling its degenerated into a bit of nonsense. The chief issues seem to be accessing the RGB tuples of the .PNG Image class and changing them to integers to identify them properly as well as differentiating between distinct clusters not just distinct colors. I haven't even been able to get the script to write the image as anything but a flat color so far.
For reference, I hope to be able to scale this up to handle an image like this:
and give each of those little clusters a unique color. Any and all help appreciated.
OK, let's see if that works for you. If I understood it right what you are trying to achieve, here is my (beginner) solution.
Essentially I take the image, in a 3D array, find all unique colors in the picture and replace them with an integer( function: arr_to_int). Then find all the clusters with the function find_clusters. Create a dictionary with new colors with as many colors as number of clusters (so every int of every cluster gets replaced with a color again).
At the end replace all int with colors again and save the picture.
This was the image I used to start with:
and that's the new picture I got as output:
If you change the process of how to apply them clusters the specific colors you want to use, I think I'm pretty close to what you are trying to achieve (hope so :) )
import numpy as np
import cv2
from scipy import ndimage
# array of GBR colors to single int
def arr_to_int(arr, col_mask):
out = np.ndarray(shape=arr.shape[:2], dtype=int)
out[:,:] = -1
for rgb, idx in col_mask.items():
out[(arr==rgb).all(2)] = idx
return out
# find unique clusters of identical RGB codes
def find_clusters(array):
clustered = np.empty_like(array)
unique_vals = np.unique(array)
cluster_count = 0
for val in unique_vals:
labelling, label_count = ndimage.label(array == val)
for k in range(1, label_count + 1):
clustered[labelling == k] = cluster_count
cluster_count += 1
return clustered, cluster_count
# Load image
im = cv2.imread("prov_test.png")
#im = cv2.resize(im, (2, 3)) #resize for debugging
#print('original image: \n', im, '\n')
#find all unique colors in image (cv2 presents in BGR format!!!)
unique_col_BGR = list(set(tuple(v) for m2d in im for v in m2d))
print('unique values: ', unique_col_BGR, '\n')
#create dict with GBR_colors as keys and unique integers as value
mask_GBR_int = {color:idx for idx,color in enumerate(unique_col_BGR)}
print('mask dict: ', mask_GBR_int, '\n')
#change all color values in im to a single int (mask)
im_with_ints = arr_to_int(im, mask_GBR_int)
#print('pic with mask values: \n', im_with_ints, '\n')
# due to replacing array of 3 values to a single int, new array has one dimension less
print('orig pic resized shape', im.shape)
print('Mask int pic shape', im_with_ints.shape, '\n')
clusters, cluster_count = find_clusters(im_with_ints)
print(f'Found {cluster_count} clusters', '\n')
#print(clusters)
#create dict with length equal to number of clusters and choose color of list_of_colors (random from the internet)
list_of_colors = [[192,192,192],[128,128,128],[128,0,0],[128,128,0],[0,128,0],[128,0,128],[0,128,128],[0,0,128],[255,0,0],[0,255,0],[0,0,255],[255,255,0],[0,255,255],[255,0,255]]
new_color_dict = {idx:val for idx,val in enumerate(list_of_colors[:cluster_count])}
print('new_color_dict: ', new_color_dict,'\n')
#change arr with int to colors again
res = np.array([*new_color_dict.values()])[clusters]
#print('image array with new colors: \n', res)
cv2.imwrite("prov_test_output.png", res)
Output:
unique values: [(0, 255, 0), (255, 0, 0), (0, 0, 255), (0, 255, 255)]
mask dict: {(0, 255, 0): 0, (255, 0, 0): 1, (0, 0, 255): 2, (0, 255, 255): 3}
orig pic resized shape (100, 100, 3)
Mask int pic shape (100, 100)
Found 9 clusters
new_color_dict: {0: [192, 192, 192], 1: [128, 128, 128], 2: [128, 0, 0], 3: [128, 128, 0], 4: [0, 128, 0], 5: [128, 0, 128], 6: [0, 128, 128], 7: [0, 0, 128], 8: [255, 0, 0]}

How could I add a safe_zone around my obstacles or around my robot? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 3 years ago.
Improve this question
I'm working on this A_star algorithm in Python and when the algorithm avoids the obstacles, I need to create a safe_zone around the obstacles or the robot itself, until when the robot passes near to the obstacle, there will be no chance to hit the obstacle. So, how could I add this safe_zone? Any assistance, please?
My code is shown below:
from __future__ import print_function
import random
grid = [[0, 1, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0],#0 are free path whereas 1's are obstacles
[0, 1, 0, 0, 0, 0],
[0, 1, 0, 0, 1, 0],
[0, 0, 0, 0, 1, 0]]
init = [0, 0]
goal = [len(grid)-1, len(grid[0])-1] #all coordinates are given in format [y,x]
cost = 1
#the cost map which pushes the path closer to the goal
heuristic = [[0 for row in range(len(grid[0]))] for col in range(len(grid))]
for i in range(len(grid)):
for j in range(len(grid[0])):
heuristic[i][j] = abs(i - goal[0]) + abs(j - goal[1])
#the actions we can take
delta = [[-1, 0 ], # go up
[ 0, -1], # go left
[ 1, 0 ], # go down
[ 0, 1 ]] # go right
#function to search the path
def search(grid,init,goal,cost,heuristic):
closed = [[0 for col in range(len(grid[0]))] for row in range(len(grid))]# the referrence grid
closed[init[0]][init[1]] = 1
action = [[0 for col in range(len(grid[0]))] for row in range(len(grid))]#the action grid
x = init[0]
y = init[1]
g = 0
f = g + heuristic[init[0]][init[0]]
cell = [[f, g, x, y]]
found = False # flag that is set when search is complete
resign = False # flag set if we can't find expand
while not found and not resign:
if len(cell) == 0:
resign = True
return "FAIL"
else:
cell.sort()#to choose the least costliest action so as to move closer to the goal
cell.reverse()
next = cell.pop()
x = next[2]
y = next[3]
g = next[1]
f = next[0]
if x == goal[0] and y == goal[1]:
found = True
else:
for i in range(len(delta)):#to try out different valid actions
x2 = x + delta[i][0]
y2 = y + delta[i][1]
if x2 >= 0 and x2 < len(grid) and y2 >=0 and y2 < len(grid[0]):
if closed[x2][y2] == 0 and grid[x2][y2] == 0:
g2 = g + cost
f2 = g2 + heuristic[x2][y2]
cell.append([f2, g2, x2, y2])
closed[x2][y2] = 1
action[x2][y2] = i
invpath = []
x = goal[0]
y = goal[1]
invpath.append([x, y])#we get the reverse path from here
while x != init[0] or y != init[1]:
x2 = x - delta[action[x][y]][0]
y2 = y - delta[action[x][y]][1]
x = x2
y = y2
invpath.append([x, y])
path = []
for i in range(len(invpath)):
path.append(invpath[len(invpath) - 1 - i])
print("ACTION MAP")
for i in range(len(action)):
print(action[i])
return path
a = search(grid,init,goal,cost,heuristic)
for i in range(len(a)):
print(a[i])
The typical way to achieve this is to inflate the obstacles before doing the search.
Suppose your robot is circular with a radius of 25 cm. If the robot center is less than 25 cm from the obstacle, the edge of the robot will hit the obstacle, right? Thus you enlarge (inflate) the obstacle by 25 cm (meaning that any point closer than 25 cm from the original obstacle becomes itself an obstacle) so that you can plan the motion of the center of the robot.
If you want an additional safety margin of, for instance, 10 cm (that is, the edge of the robot further than 10 cm from the obstacle), you can inflate the obstacle by 35 cm instead of 25.
For Non-circular Robots, the obstacle should be inflated by at least half of the longest axis to be sure that there is no collision with the obstacles. For example, if Robot's shape is 50x80, the obstacles should be inflated by 80/2 = 40 to ensure that the trajectory is safe.
Also, note that the obstacle inflation method works best for circular/square shaped robots. For Rectangular Robots/Robots which have one longer axis, it can be problematic in the case when the Grid Map around the robot has narrow passages where even though the Robot could realistically pass through it, obstacle inflation can make it infeasible.
This obstacle inflation can be done programmatically using morphology operations on the map considered as an image. See for instance dilation in scikit-image.morphology.

Making a collage in PIL

I. Am. Stuck.
I have been working on this for over a week now, and I cannot seem to get my code to run correctly. I am fairly new to PIL and Python as a whole. I am trying to make a 2x3 collage of some pictures. I have my code listed below. I am trying to get my photos to fit without any access black space in the newly created collage, however when I run my code I can only get 2 pictures to be placed into the collage, instead of the 6 I want. Any suggestions would be helpful.
*CODE EDITED
from PIL import Image
im= Image.open('Tulips.jpg')
out=im.convert("RGB", (
0.412453, 0.357580, 0.180423, 0,
0.212671, 0.715160, 0.072169, 0,
0.019334, 0.119193, 0.950227, 0 ))
out.save("Image2" + ".jpg")
out2=im.convert("RGB", (
0.9756324, 0.154789, 0.180423, 0,
0.212671, 0.715160, 0.254783, 0,
0.123456, 0.119193, 0.950227, 0 ))
out2.save("Image3" + ".jpg")
out3= im.convert("1")
out3.save("Image4"+".jpg")
out4=im.convert("RGB", (
0.986542, 0.154789, 0.756231, 0,
0.212671, 0.715160, 0.254783, 0,
0.123456, 0.119193, 0.112348, 0 ))
out4.save("Image5" + ".jpg")
out5=Image.blend(im, out4, 0.5)
out5.save("Image6" + ".jpg")
listofimages=['Tulips.jpg', 'Image2.jpg', 'Image3.jpg', 'Image4.jpg', 'Image5.jpg', 'Image6.jpg']
def create_collage(width, height, listofimages):
Picturewidth=width//3
Pictureheight=height//2
size=Picturewidth, Pictureheight
new_im=Image.new('RGB', (450, 300))
for p in listofimages:
Image.open(p)
for col in range(0,width):
for row in range(0, height):
image=Image.eval(p, lambda x: x+(col+row)/30)
new_im.paste(p, (col,row))
new_im.save("Collage"+".jpg")
create_collage(450,300,listofimages)
Here's some working code.
When you call Image.open(p), that returns an Image object, so you need to store than in a variable: im = Image.open(p).
I'm not sure what image=Image.eval(p, lambda x: x+(col+row)/30) is meant to do so I removed it.
size is the size of the thumbnails, but you're not using that variable. After opening the image, it should be resized to size.
I renamed Picturewidth and Pictureheight to thumbnail_width and thumbnail_height to make it clear what they are and follow Python naming conventions.
I also moved the number of cols and rows to variables so they can be reused without magic numbers.
The first loop opens each image into an im, thumbnails it and puts it in a list of ims.
Before the next loops we initialise i,x, andy` variables to keep track of which image we're looking at, and the x and y coordinates to paste the thumbnails into the larger canvas. They'll be updated in the next loops.
The first loop is for columns (cols), not pixels (width). (Also range(0, thing) does the same as range(thing).)
Similarly the second loop is for rows instead of pixels. Inside this loop we paste the current image at ims[i] into the big new_im at x, y. These are pixel positions, not row/cols positions.
At the end of the inner loop, increment the i counter, and add thumbnail_height to y.
Similarly, at the end of the outer loop, and add thumnnail_width to x and reset y to zero.
You only need to save new_im once, after these loops have finished.
There's no need for concatenating "Image2" + ".jpg" etc., just do "Image2.jpg".
This results in something like this:
This code could be improved. For example, if you don't need them for anything else, there's no need to save the intermediate ImageX.jpg files, and rather than putting those filenames in listofimages, put the images directly there: listofimages = [im, out1, out2, etc...], and then replace for p in listofimages: with for im in listofimages: and remove im = Image.open(p).
You could also calculate some padding for the images so the blackspace is even.
from PIL import Image
im= Image.open('Tulips.jpg')
out=im.convert("RGB", (
0.412453, 0.357580, 0.180423, 0,
0.212671, 0.715160, 0.072169, 0,
0.019334, 0.119193, 0.950227, 0 ))
out.save("Image2.jpg")
out2=im.convert("RGB", (
0.9756324, 0.154789, 0.180423, 0,
0.212671, 0.715160, 0.254783, 0,
0.123456, 0.119193, 0.950227, 0 ))
out2.save("Image3.jpg")
out3= im.convert("1")
out3.save("Image4.jpg")
out4=im.convert("RGB", (
0.986542, 0.154789, 0.756231, 0,
0.212671, 0.715160, 0.254783, 0,
0.123456, 0.119193, 0.112348, 0 ))
out4.save("Image5.jpg")
out5=Image.blend(im, out4, 0.5)
out5.save("Image6.jpg")
listofimages=['Tulips.jpg', 'Image2.jpg', 'Image3.jpg', 'Image4.jpg', 'Image5.jpg', 'Image6.jpg']
def create_collage(width, height, listofimages):
cols = 3
rows = 2
thumbnail_width = width//cols
thumbnail_height = height//rows
size = thumbnail_width, thumbnail_height
new_im = Image.new('RGB', (width, height))
ims = []
for p in listofimages:
im = Image.open(p)
im.thumbnail(size)
ims.append(im)
i = 0
x = 0
y = 0
for col in range(cols):
for row in range(rows):
print(i, x, y)
new_im.paste(ims[i], (x, y))
i += 1
y += thumbnail_height
x += thumbnail_width
y = 0
new_im.save("Collage.jpg")
create_collage(450, 300, listofimages)
I made a solution inspired by #Hugo's answer which only requires the input list of images. The function automatically creates a grid based on the number of images input.
def find_multiples(number : int):
multiples = set()
for i in range(number - 1, 1, -1):
mod = number % i
if mod == 0:
tup = (i, int(number / i))
if tup not in multiples and (tup[1], tup[0]) not in multiples:
multiples.add(tup)
if len(multiples) == 0:
mod == number % 2
div = number // 2
multiples.add((2, div + mod))
return list(multiples)
def get_smallest_multiples(number : int, smallest_first = True) -> Tuple[int, int]:
multiples = find_multiples(number)
smallest_sum = number
index = 0
for i, m in enumerate(multiples):
sum = m[0] + m[1]
if sum < smallest_sum:
smallest_sum = sum
index = i
result = list(multiples[i])
if smallest_first:
result.sort()
return result[0], result[1]
def create_collage(listofimages : List[str], n_cols : int = 0, n_rows: int = 0,
thumbnail_scale : float = 1.0, thumbnail_width : int = 0, thumbnail_height : int = 0):
n_cols = n_cols if n_cols >= 0 else abs(n_cols)
n_rows = n_rows if n_rows >= 0 else abs(n_rows)
if n_cols == 0 and n_rows != 0:
n_cols = len(listofimages) // n_rows
if n_rows == 0 and n_cols != 0:
n_rows = len(listofimages) // n_cols
if n_rows == 0 and n_cols == 0:
n_cols, n_rows = get_smallest_multiples(len(listofimages))
thumbnail_width = 0 if thumbnail_width == 0 or n_cols == 0 else round(thumbnail_width / n_cols)
thumbnail_height = 0 if thumbnail_height == 0 or n_rows == 0 else round(thumbnail_height/n_rows)
all_thumbnails : List[Image.Image] = []
for p in listofimages:
thumbnail = Image.open(p)
if thumbnail_width * thumbnail_scale < thumbnail.width:
thumbnail_width = round(thumbnail.width * thumbnail_scale)
if thumbnail_height * thumbnail_scale < thumbnail.height:
thumbnail_height = round(thumbnail.height * thumbnail_scale)
thumbnail.thumbnail((thumbnail_width, thumbnail_height))
all_thumbnails.append(thumbnail)
new_im = Image.new('RGB', (thumbnail_width * n_cols, thumbnail_height * n_rows), 'white')
i, x, y = 0, 0, 0
for col in range(n_cols):
for row in range(n_rows):
if i > len(all_thumbnails) - 1:
continue
print(i, x, y)
new_im.paste(all_thumbnails[i], (x, y))
i += 1
y += thumbnail_height
x += thumbnail_width
y = 0
extension = os.path.splitext(listofimages[0])[1]
if extension == "":
extension = ".jpg"
destination_file = os.path.join(os.path.dirname(listofimages[0]), f"Collage{extension}")
new_im.save(destination_file)
Example usage:
listofimages=['Tulips.jpg', 'Image2.jpg', 'Image3.jpg', 'Image4.jpg', 'Image5.jpg', 'Image6.jpg']
create_collage(listofimages)
In this case, because the input images are 6, the function returns a 3x2 (3 rows, 2 columns) collage of the images.
To do so, the function finds the two smallest integer multiples of the length of the input list of graphs (e.g. for 12, it returns 3 and 4 rather than 2 and 6) and creates a grid, where the first number is always the smallest of the multiples and it is taken to be the number of columns (i.e. by default the grid gets fewer columns than rows; for 12 images, you get a 4x3 matrix: 4 rows, 3 columns). This it can be customized via the smallest_first argument (only exposed in get_smallest_multiples()).
Optional arguments also allow to force a number of rows/columns.
The final image size is the sum of the sizes of the single images, but an optional thumbnail_scale argument allows to specify a percentage of scaling for all the thumbnails (defaults to 1.0, i.e. 100%, no scaling).
This function works well when the size of the images are all roughly the same. I have not covered more complex scenarios.

Categories

Resources