# Simplest way to add static random noise to a stimulus (letter)

Hello,
I am planning to run a letter discrimination experiment. In each trial, a white letter is presented on a black background. The letter is degraded by randomly reversing the contrast polarity of some
proportion of the pixels in both the letter and the background. That is, some proportion of the letter pixels are changed to black, while the same proportion of the background pixels are changed to white.
When this proportion equals 50%, the display becomes a homogeneous, random array of black and white pixels, and the letter is no longer visible. I would appreciate some guidance on the easiest way to build these stimuli within psychopy (an example is provided below).
Best,
Mat

see demo " maskReveal.py " in your psychopy install for a starter

One way might be to use a `BufferImageStim` to capture the image of the letter, manipulate the resulting pixels, and then use those to create and draw a `GratingStim`.

Rough demo below - it might get more complicated if `img_size` is not a power of 2.

``````import numpy as np

import psychopy.visual
import psychopy.event

img_size = 128
win_size = img_size * 2
img_frac = img_size / win_size

# probability of a pixel reversing in polarity
p_flip = 0.25
# whether to flip each pixel, as a vector; start as unflipped
pix_mod = np.ones(img_size ** 2)
# number of pixels to flip
n_to_flip = np.round((img_size ** 2) * p_flip).astype(int)
# set the required proportion to flip
pix_mod[:n_to_flip] = -1
# randomise their positions
np.random.shuffle(pix_mod)
# convert into an image
pix_mod = np.reshape(a=pix_mod, newshape=(img_size,) * 2)
pix_mod = pix_mod[..., np.newaxis]

with psychopy.visual.Window(
size=(win_size,) * 2, color=(-1,) * 3, fullscr=False, units="norm",
) as win:

# the desired letter, without noise
text = psychopy.visual.TextStim(
win=win, text="F", height=0.5, antialias=False, units="norm"
)

# region of the window to capture
capture_rect = [-img_frac, img_frac, img_frac, -img_frac]

# get the image of the letter
buff = psychopy.visual.BufferImageStim(
win=win, stim=[text], interpolate=False, rect=capture_rect
)

# convert from [0, 255] to [-1, +1]
buff_img = np.array(buff.image).astype("float") / 255.0 * 2.0 - 1.0

# do the polarity flipping
img = np.flipud(buff_img) * pix_mod

# generate a stimulus to show the noisy image
img_tex = psychopy.visual.GratingStim(
win=win, tex=img, mask=None, size=(img_size,) * 2, units="pix"
)

img_tex.draw()

win.flip()

psychopy.event.waitKeys()
``````

1 Like

@djmannion Great idea, thank you so much! I canâ€™t modify the code to make it fullscreen though. I tried this :

``````import numpy as np
from psychopy import visual, event, core, gui

img_size = 128
#win_size = img_size * 2#256
#img_frac = img_size / win_size

# probability of a pixel reversing in polarity
p_flip = 0.1
# whether to flip each pixel, as a vector; start as unflipped
pix_mod = np.ones(540*960)
# number of pixels to flip
n_to_flip = np.round((540*960) * p_flip).astype(int)
# set the required proportion to flip
pix_mod[:n_to_flip] = -1
# randomise their positions
np.random.shuffle(pix_mod)
# convert into an image
pix_mod = np.reshape(a=pix_mod, newshape=(540,960))#(128,128) array
pix_mod = pix_mod[..., np.newaxis]#(432,768,1) array   (height, width,  number of color channels (grayscale here))

win = visual.Window(monitor='mathieu', color=[0,0,0], colorSpace='rgb255', units='norm', fullscr=True)
expName="lexical decision"
expInfo={'participant':''}

dlg=gui.DlgFromDict(dictionary=expInfo,title=expName, order=['participant'])
if dlg.OK==False: core.quit() #user pressed cancel

text = visual.TextStim(
win=win, text="F", height=0.15, antialias=False, units="norm", fullscr=True)

#text.draw()
#win.flip()
#event.waitKeys()
#core.quit()

#
### region of the window to capture
capture_rect = [-0.5, 0.5, 0.5, -0.5]    #[left, top, right, bottom]
##
### get the image of the letter
buff = visual.BufferImageStim(win=win, stim=[text], interpolate=False, rect=capture_rect)#screen shot of the stimulus in the back buffer (hidden), norm units (mandatory)
#
#
## convert from [0, 255] to [-1, +1]
buff_img = np.array(buff.image).astype("float") / 255.0 * 2.0 - 1.0
#
## do the polarity flipping
img = np.flipud(buff_img) * pix_mod
#
## generate a stimulus to show the noisy image
img_tex = visual.GratingStim(
#
img_tex.draw()
win.flip()
#
#event.waitKeys()

event.waitKeys()
core.quit()
``````

Which does not even return something fullscreen! There might something with the BufferImageStim function that I donâ€™t understand. Any idea?
There is another complication when switching to fullscreen mode. On windows 10 (15â€™ laptop), 1920*1080 resolution, psychopy uses an incorrect resolution (WARNING User requested fullscreen with size [800 600], but screen is actually [1536, 864]). I think Psychopy uses the default 125% scaling from windows. However, adding a gui at the beginning of the code e.g.

``````expName="lexical decision"
expInfo={'participant':''}
dlg=gui.DlgFromDict(dictionary=expInfo,title=expName, order=['participant'])
if dlg.OK==False: core.quit()
``````

solves the issue (Psychopy now identify the 1920*1080 resolution) so I am a little lost here. Maybe @jon has some insight on this? (note: I am using Psychopy v3.2.4)

Hmmm, I donâ€™t have any insight into why it isnâ€™t capturing fullscreen.

The most elegant way of generating that particular kind of noise would be:

• render the stimulus with no noise
• change the blend mode to use multiplication (instead of the current options of adding or averaging)
• render some pixels that are either -1 or +1 at your chosen ratio (where -1 pixels fell they would essentially flip the color)

Actually, another solution might be to use the dedicated Noise stimulus, which I think does allow for more complex combinations of textures. I donâ€™t have an immediate suggestion for how to do it but @schofiaj might be able to help since he wrote that stimulus class and thinks a lot about noise stimuli

The demo assumes a square window size, which wonâ€™t be the case when running fullscreen. You would need to adjust the logic to handle non-square sizes.

Something like the below - though there may be issues with the window resolution scaling you mention (I donâ€™t have any experience with that).

``````import numpy as np

import psychopy.visual
import psychopy.event

# horizontal and vertical sizes of the noise region
# these are relative to the vertical size of the window
noise_frac_xy = np.array([0.5, 0.5])

with psychopy.visual.Window(
color=(-1,) * 3, fullscr=True, units="norm",
) as win:

# size of the window, in pixels
(win_x, win_y) = win.size

# size of the captured region, in pxiels
img_size_xy = np.round(noise_frac_xy * win_y).astype(int)

# captured region as fractions of the window size
img_frac_xy = img_size_xy / win.size
(img_frac_x, img_frac_y) = img_frac_xy

# captured size in (row, column) format
img_size_ij = img_size_xy[::-1]

# probability of a pixel reversing in polarity
p_flip = 0.25
# total number of pixels in the noise region
n_pixels = np.prod(img_size_ij)
# whether to flip each pixel, as a vector; start as unflipped
pix_mod = np.ones(n_pixels)
# number of pixels to flip
n_to_flip = np.round(n_pixels * p_flip).astype(int)
# set the required proportion to flip
pix_mod[:n_to_flip] = -1
# randomise their positions
np.random.shuffle(pix_mod)
# convert into an image
pix_mod = np.reshape(a=pix_mod, newshape=img_size_ij)
pix_mod = pix_mod[..., np.newaxis]

# the desired letter, without noise
text = psychopy.visual.TextStim(
win=win, text="F", height=0.5, antialias=False, units="norm"
)

# region of the window to capture
capture_rect = [
-img_frac_x,  # left
+img_frac_y,  # top
+img_frac_x,  # right
-img_frac_y,  # bottom
]

# get the image of the letter
buff = psychopy.visual.BufferImageStim(
win=win, stim=[text], interpolate=False, rect=capture_rect
)

# convert from [0, 255] to [0, 1], binarise, then convert to [-1, +1]
buff_img = (
np.round(np.array(buff.image).astype("float") / 255.0) * 2.0 - 1.0
)

# do the polarity flipping
img = np.flipud(buff_img) * pix_mod

# generate a stimulus to show the noisy image
img_tex = psychopy.visual.ImageStim(