I'm looking to output a list of lists in python, in which each list contains the pixel coordinates of each shape of a single colour.
I.e. looking at this image: a 128x128 png
Which I have converted into a 2D grid of RGB values.
I am looking to create a list of lists that each contain the pixel coordinates of the purple "shapes" (squares, rectangles, etc). The criteria for a "shape" is simply at least two same colour pixels adjacent to each other (only up, down, left, right).
In my attempt:
# returns a list of instances of a tile which are all adjacent to each other
# i.e. finds a window's individual tiles and returns them as a list
def coagulateTiles(self, tileType, startX, startY, existingComponents = [], foundTiles = None):
# if the pixel is not the right tile OR if it has already been part of another search
if not self.getTileType(startX, startY) == tileType or not (startX, startY) in foundTiles:
return existingComponents
else:
existingComponents.append((startX, startY))
# a dictionary of the adjacent tiles {"north" = (x, y), etc...}
adjacentCoordDict = self.getAdjacentCoords(startX, startY)
for adjacent in adjacentCoordDict:
if adjacent == tileType:
x, y = adjacentCoordDict[adjacent][0], adjacentCoordDict[adjacent][1]
existingComponents.append(self.coagulateTiles(tileType, x, y, existingComponents))
return existingComponents
def tileSearch(self, tile):
items = []
foundTiles = []
for x in range(self.getSizeX()):
for y in range(self.getSizeY()):
if self.getTileType(x, y) == tile:
if tile in self.getAdjacentTiles(x, y).values():
foundTiles.append((x, y))
listOfTileComponents = self.coagulateTiles(tile, x, y, foundTiles = foundTiles)
items.append(listOfTileComponents)
return items
I end up finding all those pixels, but the outputted list puts all the pixels together into a single list rather than separating the "shapes" into individual sublists.
It returns something like [[(1, 1), (2, 1), (3, 1), (1, 2), (1, 3)]]
rather than [[(1, 1), (2, 1)], [(3, 1), (1, 2), (1, 3)]]
My gut feeling is that of course there is a simple logical error in my attempt, but I can't seem to find where I can specify the sublist creation for it to work.
Any ideas?
Here's the code needed to convert the image above into the grid I'm working with:
import numpy as np
from PIL import Image
# (x=0,y=0) of the grid is in the top left corner
class Grid():
def __init__(self, sizeX, sizeY):
self.sizeX = sizeX
self.sizeY = sizeY
global rgbMap
rgbMap = {
"wall":( 0, 0, 0),
"background": (255,255,255),
"closet": (192,192,224),
"bathroom": (192,255,255), # /washroom
"dining room": (224,255,192), # livingroom/kitchen/dining room
"bedroom": (255,224,128),
"hall": (255,160, 96),
"balcony": (255,224,224),
"opening": (255, 60,128) # door & window
}
global tileMap
tileMap = {v: k for k, v in rgbMap.items()}
# create a 2d numpy array of the given size
self.grid = self.createGrid(sizeX, sizeY)
def createGrid(self, sizeX, sizeY):
return np.empty((sizeX, sizeY), dtype=tuple)
# function to input a colour into the 2d grid
def populate(self, locationX, locationY, rgbTuple):
self.grid[locationX,locationY] = rgbTuple
def get(self):
return self.grid
def getSizeX(self):
return self.sizeX
def getSizeY(self):
return self.sizeY
class MapGenerator:
def __init__(self, finalSize = 128):
# resize the image to be axa size where a = 128
self.workingGridSize = 512
self.finalSize = finalSize
def createFromSaveFile(self):
self.createGrid(fromMemory=True)
print("populating the grid with data")
self.populateGrid(self.finalSize, self.grid, self.floorplan)
self.floorplan.close()
def createGrid(self, fromMemory = False):
if fromMemory == True:
self.floorplan = Image.open('saved.png')
self.grid = Grid(self.finalSize, self.finalSize)
else:
self.floorplan = Image.open('result.png')
self.grid = Grid(self.workingGridSize, self.workingGridSize)
def populateGrid(self, gridsize, givenGrid, image):
for x in range(0, gridsize):
for y in range(0, gridsize):
# print(map.getpixel((x,y)))
givenGrid.populate(x, y, image.getpixel((x,y)))
if __name__ == "__main__":
newMap = MapGenerator()
# newMap.create()
newMap.createFromSaveFile()
openings = newMap.grid.tileSearch("opening")
print(openings)
i've come up with a DFS recursive solution that works:
def tileSearch(self, tile):
"""returns a 2d list containing all the pixels
of each shape of a searched tile type as a sublist
a shape is defined as at least two pixels
of the same colour touching each other with Four-Pixel Connectivity
(up, down, left, right)
Args:
tile (string): the tile to search as a string (see rgbMap dict for tile list)
Returns:
list: a 2D list containing each shape as a sublist of its pixels
"""
searchGrid = copy.deepcopy(self)
resultList = list()
for x in range(searchGrid.getSizeX()):
for y in range(searchGrid.getSizeY()):
if searchGrid.getTileType(x, y) == tile:
adjacentTiles = list(searchGrid.getAdjacentTiles(x, y).values())
if tile in adjacentTiles:
shapeList = list()
searchGrid.coagulateShape(tile, x, y, shapeList)
resultList.append(shapeList)
return resultList
# returns a list of instances of a tile which are all adjacent to each other
# i.e. finds a window's individual tiles and returns them as a list
def coagulateShape(self, tile, x, y, shapeList = []):
""" Performs a recursive Depth First Search
returns a list of coordinates of a tile which are all adjacent to each other
through Four Pixel connectivity (up, down, left, right)
i.e. finds a shape's individual tiles and returns them as a list
Args:
tile (string): the tile to search for (see rgbMap dict for tile list)
x (int): the x coordinate of the starting pixel
y (int): the y coordinate of the starting pixel
shapeList (list): the list that the shape's coordinates will be appended to recursively
"""
# base case
if not self.getTileType(x, y) == tile:
return
# recursive case
self.populate(x, y, (0, 0, 0, 0))
shapeList.append((x, y))
adjacentPixels = list(self.getAdjacentCoords(x, y).values())
for pixel in adjacentPixels:
self.coagulateShape(tile, pixel[0], pixel[1], shapeList)