Home > database >  How to sublist in a list the same value pixels of an image (Four Pixel Connectivity)
How to sublist in a list the same value pixels of an image (Four Pixel Connectivity)

Time:03-17

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)

CodePudding user response:

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)
  • Related