Skip to content

Commit

Permalink
Merge branch 'master' into replicating_RHS2007_stimuli
Browse files Browse the repository at this point in the history
  • Loading branch information
matko031 committed Jul 3, 2021
2 parents 989e0c7 + faafff7 commit 4aa69e6
Show file tree
Hide file tree
Showing 22 changed files with 943 additions and 291 deletions.
5 changes: 5 additions & 0 deletions stimuli/Stimulus.py
@@ -0,0 +1,5 @@
class Stimulus():
def __init__(self):
target_mask = None
img = None

3 changes: 2 additions & 1 deletion stimuli/illusions/__init__.py
Expand Up @@ -4,10 +4,11 @@
from .checkerboard_sbc import checkerboard_contrast
from .cube import cube_illusion
from .dungeon import dungeon_illusion
from .grating_induction import grating_induction
from .grating_induction import grating_illusion
from .grating import grating_illusion
from .hermann import hermann_grid
from .rings import ring_pattern
from .sbc import simultaneous_brightness_contrast, simultaneous_contrast
from .todorovic import todorovic_illusion
from .whites import *
from .square_wave import square_wave
57 changes: 37 additions & 20 deletions stimuli/illusions/benary_cross.py
@@ -1,16 +1,17 @@
import numpy as np
from stimuli.utils import degrees_to_pixels, pad_img
from stimuli.Stimulus import Stimulus


def benarys_cross(cross_size=(80,80,80,80), cross_thickness=20, padding=(10,10,10,10), target_size=10, back=1., cross=0., target=.5):
def benarys_cross(ppd=10, cross_size=(8,8,8,8), cross_thickness=5, padding=(1,1,1,1), target_size=2, back=1., cross=0., target=.5):
"""
Benary's Cross Illusion (with square targets)
Parameters
----------
cross_size: size of the cross in px in form (top, bottom, left, right) specifying the length of each of the cross' bars
cross_thickness: width of the cross bars in px
padding: 4-valued tuple specifying padding (top, bottom, left, right) in px
target_size: size of the side of target square in px
cross_size: size of the cross in degrees visual angle in form (top, bottom, left, right) specifying the length of each of the cross' bars
cross_thickness: width of the cross bars in degrees visual angle
padding: 4-valued tuple specifying padding (top, bottom, left, right) in degrees visual angle
target_size: size of the side of target square in degrees visual angle
back: background value
cross: cross value
target: target value
Expand All @@ -20,30 +21,46 @@ def benarys_cross(cross_size=(80,80,80,80), cross_thickness=20, padding=(10,10,1
2D numpy array
"""

padding_top, padding_bottom, padding_left, padding_right = padding
cross_top, cross_bottom, cross_left, cross_right = cross_size
width = cross_left + cross_thickness + cross_right
height = cross_top+cross_thickness+cross_bottom

cross_top_px, cross_bottom_px, cross_left_px, cross_right_px = degrees_to_pixels(cross_size,ppd)
cross_thickness_px = degrees_to_pixels(cross_thickness, ppd)
target_size_px = degrees_to_pixels(target_size, ppd)

width = cross_left_px + cross_thickness_px + cross_right_px
height = cross_top_px + cross_thickness_px + cross_bottom_px

img = np.ones((height, width)) * back
mask = np.ones((height, width)) * False

x_edge_left, x_edge_right = cross_left, -cross_right
y_edge_top, y_edge_bottom = cross_top, -cross_bottom
x_edge_left, x_edge_right = cross_left_px, -cross_right_px
y_edge_top, y_edge_bottom = cross_top_px, -cross_bottom_px
img[:, x_edge_left:x_edge_right] = cross
img[y_edge_top:y_edge_bottom, :] = cross


tpos1y = y_edge_top - target_size
tpos1x = x_edge_left - target_size
tpos1y = y_edge_top - target_size_px
tpos1x = x_edge_left - target_size_px
tpos2y = y_edge_top
tpos2x = -target_size
img[tpos1y:tpos1y + target_size, tpos1x:tpos1x + target_size] = target
img[tpos2y:tpos2y + target_size, tpos2x:] = target
tpos2x = -target_size_px
img[tpos1y:tpos1y + target_size_px, tpos1x:tpos1x + target_size_px] = target
img[tpos2y:tpos2y + target_size_px, tpos2x:] = target

mask[tpos1y:tpos1y + target_size_px, tpos1x:tpos1x + target_size_px] = True
mask[tpos2y:tpos2y + target_size_px, tpos2x:] = True

img = np.pad(img, ((padding_top, padding_bottom),(padding_left, padding_right)), 'constant', constant_values=back)
img = pad_img(img, padding, ppd, back)
mask = pad_img(mask, padding, ppd, 0)

return img
stim = Stimulus()
stim.target_mask = mask
stim.img = img
return stim

def domijan2015():
return benarys_cross(cross_size=(30,30,30,30), cross_thickness=21, padding=(9,10,9,10),target_size=11, back=9., cross=1., target=5.)
return benarys_cross(ppd=10, cross_size=(3,3,3,3), cross_thickness=2.1, padding=(.9,1.0,.9,1.0),target_size=1.1, back=9., cross=1., target=5.)

if __name__ == '__main__':
import matplotlib.pyplot as plt
stim = benarys_cross()
plt.imshow(stim.img, cmap='gray')
plt.show()
30 changes: 21 additions & 9 deletions stimuli/illusions/bullseye.py
@@ -1,8 +1,9 @@
import numpy as np
from stimuli.illusions.rings import ring_pattern
from stimuli import utils
from stimuli.utils import degrees_to_pixels, pad_img
from stimuli.Stimulus import Stimulus

def bullseye_illusion(n_rings=8, ring_width=5, padding=(10,10,10,10), back=0., rings=1., target=.5):
def bullseye_illusion(ppd=10, n_rings=8, ring_width=.5, target_pos_l=0, target_pos_r=0, padding=(1.0,1.0,1.0,1.0), back=0., rings=1., target=.5):
"""
Bullseye Illusion.
Two ring patterns (see ring_pattern func), with target in centre and one ring pattern inverted.
Expand All @@ -20,21 +21,32 @@ def bullseye_illusion(n_rings=8, ring_width=5, padding=(10,10,10,10), back=0., r
-------
2D numpy array
"""
img1 = ring_pattern(n_rings=n_rings, target_pos_l=0, ring_width=ring_width, padding=padding,
stim1 = ring_pattern(n_rings=n_rings, target_pos_l=target_pos_l, ring_width=ring_width, padding=padding,
back=back, rings=rings, target=target, invert_rings=False, double=False)
img2 = ring_pattern(n_rings=n_rings, target_pos_l=0, ring_width=ring_width, padding=padding,
stim2 = ring_pattern(n_rings=n_rings, target_pos_l=target_pos_r, ring_width=ring_width, padding=padding,
back=back, rings=rings, target=target, invert_rings=True, double=False)

return np.hstack([img1, img2])
img = np.hstack((stim1.img, stim2.img))
mask = np.hstack((stim1.target_mask, stim2.target_mask))

stim = Stimulus()
stim.img = img
stim.target_mask = mask

return stim

def domijan2015():
img = bullseye_illusion(n_rings=8, ring_width=5, padding=(9,10,9,10), back=1., rings=9., target=5.)
img = bullseye_illusion(n_rings=8, ring_width=.5, target_pos_l=0, target_pos_r=0, padding=(.9,1.0,.9,1.0), back=1., rings=9., target=5.)
return img

def RHS2007_bullseye_thin():
return bullseye_illusion(n_rings=8, ring_width=5, padding=(600,600,600,600), back=1., rings=9., target=5.)
return bullseye_illusion(n_rings=8, ring_width=1, padding=(100,100,100,100), back=1., rings=9., target=5.)

def RHS2007_bullseye_thick():
return bullseye_illusion(n_rings=8, ring_width=5, padding=(500,500,500,500), back=1., rings=9., target=5.)

return bullseye_illusion(n_rings=8, ring_width=1, padding=(50,50,50,50), back=1., rings=9., target=5.)

if __name__ == '__main__':
import matplotlib.pyplot as plt
stim = bullseye_illusion()
plt.imshow(stim.img, cmap='gray')
plt.show()
40 changes: 32 additions & 8 deletions stimuli/illusions/checkerboard_contrast_contrast.py
@@ -1,7 +1,9 @@
import numpy as np
from stimuli.utils import degrees_to_pixels, pad_img
from stimuli.Stimulus import Stimulus


def checkerboard_contrast_contrast_effect(n_checks=8, check_size=10, target_length=4, padding=(10,10,10,10), check1=0., check2=2.,
def checkerboard_contrast_contrast_effect(ppd=10, n_checks=8, check_size=1.0, target_length=4, padding=(1.0,1.0,1.0,1.0), check1=0., check2=2.,
tau=.5, alpha=.5):
"""
Contrast-contrast effect on checkerboard with square transparency layer.
Expand All @@ -22,30 +24,52 @@ def checkerboard_contrast_contrast_effect(n_checks=8, check_size=10, target_leng
2D numpy array
"""

padding_top, padding_bottom, padding_left, padding_right = padding
padding_tuple = ((padding_top, padding_bottom), (padding_left, padding_right))
check_size_px = degrees_to_pixels(check_size, ppd)

arr1 = np.ndarray((n_checks, n_checks))
for i, j in np.ndindex((n_checks, n_checks)):
arr1[i, j] = check1 if i % 2 == j % 2 else check2

mask_arr1 = np.zeros((n_checks, n_checks))


idx = np.zeros((n_checks, n_checks), dtype=bool)
tpos = (n_checks - target_length) // 2
idx[tpos:tpos + target_length, tpos:tpos + target_length] = True
arr1[idx] = alpha * arr1[idx] + (1 - alpha) * tau
mask_arr1[idx] = True

arr2 = arr1.copy()
arr2[~idx] = tau

img1 = np.repeat(np.repeat(arr1, check_size, axis=0), check_size, axis=1)
img1 = np.pad(img1, padding_tuple, constant_values=tau, mode="constant")
img2 = np.repeat(np.repeat(arr2, check_size, axis=0), check_size, axis=1)
img2 = np.pad(img2, padding_tuple, constant_values=tau, mode="constant")
mask_arr2 = mask_arr1.copy()

img1 = np.repeat(np.repeat(arr1, check_size_px, axis=0), check_size_px, axis=1)
img1 = pad_img(img1, padding, ppd, tau)
img2 = np.repeat(np.repeat(arr2, check_size_px, axis=0), check_size_px, axis=1)
img2 = pad_img(img2, padding, ppd, tau)

return np.hstack([img1, img2])
mask1 = np.repeat(np.repeat(mask_arr1, check_size_px, axis=0), check_size_px, axis=1)
mask1 = pad_img(mask1, padding, ppd, 0)
mask2 = np.repeat(np.repeat(mask_arr2, check_size_px, axis=0), check_size_px, axis=1)
mask2 = pad_img(mask2, padding, ppd, 0)

img = np.hstack([img1, img2])
mask = np.hstack([mask1, mask2])
stim = Stimulus()
stim.img = img
stim.target_mask = mask

return stim



def domijan2015():
return checkerboard_contrast_contrast_effect(n_checks=8, check_size=10, target_length=4, padding=(9,11,9,11), check1=1.,
check2=9., tau=5, alpha= .5)

if __name__ == '__main__':
import matplotlib.pyplot as plt
img, mask = checkerboard_contrast_contrast_effect()
plt.imshow(img, cmap='gray')
plt.show()
42 changes: 33 additions & 9 deletions stimuli/illusions/checkerboard_sbc.py
@@ -1,8 +1,9 @@
import numpy as np
from stimuli import utils
from stimuli.utils import degrees_to_pixels, pad_img
from stimuli.Stimulus import Stimulus

def checkerboard_contrast(n_checks=8, check_size=10, target1_coords=(3, 2), target2_coords=(5, 5), extend_targets=False,
padding=(10,10,10,10), check1=0., check2=1., target=.5):
def checkerboard_contrast(ppd=10, n_checks=8, check_size=1.0, target1_coords=(3, 2), target2_coords=(5, 5), extend_targets=False,
padding=(1.0,1.0,1.0,1.0), check1=0., check2=1., target=.5):
"""
Checkerboard Contrast
Expand All @@ -23,27 +24,50 @@ def checkerboard_contrast(n_checks=8, check_size=10, target1_coords=(3, 2), targ
"""

padding_top, padding_bottom, padding_left, padding_right = padding
check_size_px = degrees_to_pixels(check_size, ppd)

arr = np.ndarray((n_checks, n_checks))
mask = np.zeros((n_checks, n_checks))

for i, j in np.ndindex((n_checks, n_checks)):
arr[i, j] = check1 if i % 2 == j % 2 else check2

arr[target1_coords] = target
arr[target2_coords] = target

mask[target1_coords] = True
mask[target2_coords] = True

if extend_targets:
for idx in [(-1, 0), (0, 1), (1, 0), (0, -1)]:
arr[target1_coords[0] + idx[0], target1_coords[1] + idx[1]] = target
arr[target2_coords[0] + idx[0], target2_coords[1] + idx[1]] = target

img = np.repeat(np.repeat(arr, check_size, axis=0), check_size, axis=1)
img = np.pad(img, ((padding_top, padding_bottom), (padding_left, padding_right)), constant_values=((check1 + check2) / 2), mode="constant")
mask[target1_coords[0] + idx[0], target1_coords[1] + idx[1]] = True
mask[target2_coords[0] + idx[0], target2_coords[1] + idx[1]] = True

img = np.repeat(np.repeat(arr, check_size_px, axis=0), check_size_px, axis=1)
mask = np.repeat(np.repeat(mask, check_size_px, axis=0), check_size_px, axis=1)

return img
img = pad_img(img, padding, ppd, (check1+check2)/2)
mask = pad_img(mask, padding, ppd, 0)

stim = Stimulus()
stim.img = img
stim.target_mask = mask

return stim


def domijan2015():
return checkerboard_contrast(n_checks=8, check_size=10, target1_coords=(3, 2), target2_coords=(5, 5), extend_targets=False, padding=(9,11,9,11), check1=1., check2=9., target=5.)
return checkerboard_contrast(ppd=10, n_checks=8, check_size=1.0, target1_coords=(3, 2), target2_coords=(5, 5), extend_targets=False, padding=(.9,1.1,.9,1.1), check1=1., check2=9., target=5.)

def domijan2015_extended():
return checkerboard_contrast(n_checks=8, check_size=10, target1_coords=(3, 2), target2_coords=(5, 5), extend_targets=True, padding=(9,11,9,11), check1=1., check2=9., target=5.)
return checkerboard_contrast(ppd=10, n_checks=8, check_size=1.0, target1_coords=(3, 2), target2_coords=(5, 5), extend_targets=True, padding=(.9,1.1,.9,1.1), check1=1., check2=9., target=5.)


if __name__ == '__main__':
import matplotlib.pyplot as plt
img, mask = checkerboard_contrast()
plt.imshow(img, cmap='gray')
plt.show()
19 changes: 17 additions & 2 deletions stimuli/illusions/cornsweet.py
@@ -1,7 +1,9 @@
import numpy as np
from stimuli.Stimulus import Stimulus

def cornsweet(size, ppd, contrast, ramp_width=3, exponent=2.75,
def cornsweet(size=(10,10), ppd=10, contrast=0.5, ramp_width=2, exponent=2.75,
mean_lum=.5):
#TODO: the parameters aren't analogous to the other stimuli
"""
Create a matrix containing a rectangular Cornsweet edge stimulus.
The 2D luminance profile of the stimulus is defined as
Expand Down Expand Up @@ -53,4 +55,17 @@ def cornsweet(size, ppd, contrast, ramp_width=3, exponent=2.75,
profile = (1 - dist) ** exponent * mean_lum * contrast / 2
stim[:, :size[1] // 2] += profile[::-1]
stim[:, size[1] // 2:] -= profile
return stim
mask = None

stim = Stimulus()
stim.img = img
stim.target_mask = mask

return stim


if __name__ == '__main__':
import matplotlib.pyplot as plt
img, mask = cornsweet()
plt.imshow(img, cmap='gray')
plt.show()

0 comments on commit 4aa69e6

Please sign in to comment.