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:
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()
-