Home > Mobile >  Permuting Pixels Within a Binary Mask with PIL
Permuting Pixels Within a Binary Mask with PIL

Time:03-08

I'm completely stuck on this problem - I need to randomly permute the pixels within a circle on a 2D image. I need this for an experiment I am designing, where searching for this small scrambled circle will act as a low-level perceptual part of a task. It is important that the pixels come from the image itself rather than being random values to keep the image intensity uniform between the modified and unmodified images.

I don't really know Python well so my code so far is very hacky - I first open the image, and make sure it is RGB. I then make a deep copy (as I'll be modifying the original with the mask, but I still need the actual original pixel values). Then I draw a bright-red circle (255, 0, 0), and define the pix_mask as anywhere where the red is 255 (our stimuli are natural scenes and objects, so there shouldn't be any other places in the image with this). I then create a new mask image using the pix_mask, and multiply the mask by the copied image data to get the data inside the circle. Sounds a mess, but here are the results:

Red dot I use to specify the position of the mask

Original image after multiplication by the binary mask

And here is the code I'm using to do this:

def create_pixel_mask_images(image_fname: str):
    # Open the image which requires a scrambled dot. Add an alpha channel and make a copy of the data 
    image: JpegImage          = Image.open(image_fname)
    image                     = image.convert("RGB")
    image_copy                = image.copy()

    # Create the drawer and draw a filled red circle on the original image (255, 0, 0)
    image_draw                = ImageDraw.Draw(image)
    image_draw.ellipse((150, 150, 250, 250), fill = "red")
    image.save("TEST_DOT.png")

    # Read the data from the copy and the now-modified original image into arrays
    image_arr: NumpyArray      = numpy.array(image)
    image_copy_arr: NumpyArray = numpy.array(image_copy)

    # Create a binary mask by testing for the red circle we drew on the original image (255 red)
    pix_mask: NumpyArray       = image_arr[:, :, 0] == 255

    # Invert the mask (i.e., everything outside of the dot is now white, and the dot black)
    # Write them out to file to error-check
    mask_image: PILImage         = Image.fromarray((pix_mask * 255).astype(numpy.uint8))
    image.convert("RGB")
    mask_image_inverse: PILImage = ImageOps.invert(mask_image)
    image.convert("RGB")
    pix_mask_inverse: NumpyArray = numpy.array(mask_image_inverse)

    # Read the inverted pixel mask as a numpy array. Multiply the unmodified image by both masks
    # in order to get the data both inside and outside of the mask
    inner_data = image_copy_arr * pix_mask[..., None]
    outer_data = image_copy_arr * pix_mask_inverse[..., None]

    # Create new images from the masked data
    inner_image: PILImage = Image.fromarray(inner_data.astype(numpy.uint8))
    outer_image: PILImage = Image.fromarray(outer_data.astype(numpy.uint8))
    outer_image           = ImageOps.invert(outer_image)

    # AS A CHECK multiply the inner- and outer-masked images together. We should get the original image back
    image_data = image.load()

        for y in range(image.size[1]):
            for x in range(image.size[0]):
                if image_data[x, y] == (255, 0, 0):
                    image_data[x, y] = (255, 255, 255)
    recombined_image = ImageChops.multiply(inner_image, outer_image)
    
    inner_image.save('INNER_DATA.png')
    
    mask_image.save('MASK.jpg')
    mask_image_inverse.save('MASK_INVERSE.jpg')
    recombined_image.save('RECOMBINATION_TEST.jpg')

You can see at the bottom I create a recombined_image by multiplying the inner_image, which is the data within the circle mask, and the outer_image, which is the rest of the data outside of the circle mask, together; however, I just end up the same image as the second picture here (just the data inside the circle).

I use the nested for-loop here to set any red pixels in the original image to white as if I left the region black I would lose the data there (checked the docs for ImageChops.multiply() - enter image description here

  • Related