Source code for pyllusion.image.image_scramble

import numpy as np
import PIL.Image
import PIL.ImageDraw
import PIL.ImageFilter
import PIL.ImageFont
import PIL.ImageOps


[docs] def image_scramble(image, blocks=None, omit=None): """ Scramble an image. .. note :: We would like to implement diffeomorphic scrambling by translating this matlab code https://github.com/rhodricusack/diffeomorph/blob/master/diffeomorphic.m Consider helping us! See also: https://jov.arvojournals.org/article.aspx?articleid=2193914 Parameters ---------- image : PIL.Image Image to scramble. blocks : tuple Scramble with blocks. Minimum number of blocks in the x and y direction. Might be higher if the image is not divisible by the number of blocks. If ``None``, will scramble the pixels. omit : str If ``"white"`` or ``"black"``, will scramble all pixels but the totally white or black ones. Note that the ``blocks`` parameter will be ignored. Returns ------- Image Scrambled image. Examples ---------- >>> import pyllusion as ill >>> >>> image = ill.Ebbinghaus().to_image() >>> ill.image_scramble(image, blocks=(10, 10)) >>> ill.image_scramble(image, blocks=(100, 100)) >>> ill.image_scramble(image, blocks=None) >>> ill.image_scramble(image, omit="white") >>> ill.image_scramble(image, omit="black") """ # If using a mask, do bespoke operation if omit is not None: return _scramble_with_mask(image, omit=omit) array = np.array(image) if blocks is None: array = _shuffled(_shuffled(array.swapaxes(0, 1)).swapaxes(0, 1)) return PIL.Image.fromarray(array) n_blocks_width = _find_best_divisor(image.width, blocks[0], blocks[0] * 2) n_blocks_height = _find_best_divisor(image.height, blocks[1], blocks[1] * 2) horizontal = np.array_split(array, n_blocks_width) horizontal = _shuffled(horizontal) # Shuffle (in-place) vertical = [_shuffled(np.array_split(block, n_blocks_height, axis=1)) for block in horizontal] array = np.concatenate(np.concatenate(vertical, axis=1), axis=1) return PIL.Image.fromarray(array)
def _scramble_with_mask(image, omit="white"): # https://stackoverflow.com/questions/74541109/ array = np.array(image.convert("RGB")) # Forge to RGB to have only 3 dimensions # Get mask of non-white or non-black pixels if omit == "white": mask = np.array(image.convert("L")) != 255 elif omit == "black": mask = np.array(image.convert("L")) != 0 else: raise ValueError("omit must be either 'white' or 'black'") # Make a single 24-bit number for each pixel, instead of 3x 8-bit numbers u32 = np.dot(array.astype(np.uint32), [1, 256, 65536]) u32[mask] = _shuffled(u32[mask]) # Now split 24-bit entities back into RGB888 r = u32 & 0xFF g = (u32 >> 8) & 0xFF b = (u32 >> 16) & 0xFF array = np.dstack((r, g, b)).astype(np.uint8) return PIL.Image.fromarray(array) # Internals # --------- def _shuffled(x): """Return a shuffled array. Because python does it in-place.""" np.random.shuffle(x) return x def _find_best_divisor(number, low, high, step=1): """Return the closest divisor so that the remainder is 0""" minimal_truncation, best_divisor = min( (number % divisor, divisor) for divisor in range(low, high, step) ) if minimal_truncation != 0: raise ValueError( "No divisor found. Please increase the number of blocks" + f" (best in to input a number that is a divider of {number})." ) return best_divisor