Skip to content

Commit

Permalink
partially updated benarys cross and changed default ppd for domijan20…
Browse files Browse the repository at this point in the history
…15 - producing the correct stims
  • Loading branch information
LynnSchmittwilken committed Sep 29, 2022
1 parent f213016 commit 31ab942
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 38 deletions.
2 changes: 1 addition & 1 deletion stimuli/illusions/__init__.py
@@ -1,4 +1,4 @@
from .benary_cross import benarys_cross
from .benary_cross import *
from .bullseye import bullseye_stimulus
from .checkerboards import *
from .circular import *
Expand Down
134 changes: 126 additions & 8 deletions stimuli/illusions/benary_cross.py
Expand Up @@ -4,9 +4,9 @@
from stimuli.utils import degrees_to_pixels


def benarys_cross(
ppd=10.0,
cross_size=(8.0, 8.0, 8.0, 8.0),
def benarys_cross_generalized(
shape=(21., 21.),
ppd=18.0,
cross_thickness=5.0,
target_size=(2.0, 2.0),
target_type=("r", "r"),
Expand All @@ -22,10 +22,10 @@ def benarys_cross(
Parameters
----------
shape : float or (float, float)
size of the stimulus in degrees of visual angle (height, width)
ppd : int
pixels per degree (visual angle)
cross_size : (float, float, float, float)
size of the cross' arms in degrees visual angle in form (top, bottom, left, right)
cross_thickness : float
width of the cross bars in degrees visual angle
target_size : (float, float)
Expand All @@ -49,6 +49,10 @@ def benarys_cross(
-------
A stimulus dictionary with the stimulus ['img'] and target mask ['mask']
"""
if isinstance(shape, (float, int)):
shape = (shape, shape)
if isinstance(target_size, (float, int)):
target_size = (target_size, target_size)
if any(
len(lst) != len(target_type)
for lst in [target_ori, target_posx, target_posy]
Expand All @@ -58,6 +62,8 @@ def benarys_cross(
" same length."
)

cross_size = np.array((shape[0], shape[0], shape[1], shape[1])) - cross_thickness
cross_size = cross_size / 2.
target_y_px, target_x_px = degrees_to_pixels(target_size, ppd)
tposy = degrees_to_pixels(target_posy, ppd)
tposx = degrees_to_pixels(target_posx, ppd)
Expand All @@ -66,9 +72,9 @@ def benarys_cross(
mask = np.zeros((img.shape[0], img.shape[1]))

if (target_x_px + np.array(tposx)).max() > img.shape[1]:
raise Exception("Leftmost target does not fit in image.")
raise ValueError("Rightmost target does not fit in image.")
if (target_y_px + np.array(tposy)).max() > img.shape[0]:
raise Exception("Lowest target does not fit in image.")
raise ValueError("Lowest target does not fit in image.")

# Add targets:
for i in range(len(target_posx)):
Expand Down Expand Up @@ -113,9 +119,119 @@ def benarys_cross(
tposy[i] : tposy[i] + tpatch.shape[0],
tposx[i] : tposx[i] + tpatch.shape[1],
] = mpatch

# Make sure that stimulus size is as requested
img = img[0:int(shape[0]*ppd), 0:int(shape[1]*ppd)]
mask = mask[0:int(shape[0]*ppd), 0:int(shape[1]*ppd)]
return {"img": img, "mask": mask}


def benarys_cross_rectangles(
shape=(21., 21.),
ppd=18,
cross_thickness=5.0,
target_size=(2.0, 4.0),
vback=1.0,
vcross=0.0,
vtarget=0.5,
):
"""
Benary's Cross stimulus with two rectangular targets with default placement
Parameters
----------
shape : float or (float, float)
size of the stimulus in degrees of visual angle (height, width)
ppd : int
pixels per degree (visual angle)
cross_thickness : float
width of the cross bars in degrees visual angle
target_size : (float, float)
size of all target(s) in degrees visual angle
vback : float
background value
vcross : float
cross value
vtarget : float
target value
Returns
-------
A stimulus dictionary with the stimulus ['img'] and target mask ['mask']
"""

if isinstance(shape, (float, int)):
shape = (shape, shape)
if isinstance(target_size, (float, int)):
target_size = (target_size, target_size)
if target_size[0] > cross_thickness:
raise ValueError("Target size is larger than cross thickness")

# Calculate parameters for classical Benarys cross with two targets
target_type = ("r",) * 2
target_ori = (0., 0.)
target_posx = ((shape[1]-cross_thickness)/2. - target_size[1], shape[1]-target_size[1])
target_posx = np.floor(np.array(target_posx) * ppd) / ppd
target_posy = ((shape[0]-cross_thickness)/2. - target_size[0], (shape[0]-cross_thickness)/2.)
target_posy = np.floor(np.array(target_posy) * ppd) / ppd
stim = benarys_cross_generalized(shape, ppd, cross_thickness, target_size, target_type,
target_ori, target_posx, target_posy, vback, vcross, vtarget)
return stim


def benarys_cross_triangles(
shape=(21., 21.),
ppd=18,
cross_thickness=5.0,
target_size=(2.0, 4.0),
vback=1.0,
vcross=0.0,
vtarget=0.5,
):
"""
Benary's Cross stimulus with two rectangular targets with default placement
Parameters
----------
shape : float or (float, float)
size of the stimulus in degrees of visual angle (height, width)
ppd : int
pixels per degree (visual angle)
cross_thickness : float
width of the cross bars in degrees visual angle
target_size : (float, float)
size of all target(s) in degrees visual angle
vback : float
background value
vcross : float
cross value
vtarget : float
target value
Returns
-------
A stimulus dictionary with the stimulus ['img'] and target mask ['mask']
"""

if isinstance(shape, (float, int)):
shape = (shape, shape)
if isinstance(target_size, (float, int)):
target_size = (target_size, target_size)
if target_size[0] > cross_thickness:
raise ValueError("Target size is larger than cross thickness")

# Calculate parameters for classical Benarys cross with two targets
target_type = ("t",) * 2
target_ori = (0., 0.)
target_posx = ((shape[1]-cross_thickness)/2. - target_size[1], shape[1]-target_size[1])
target_posx = np.floor(np.array(target_posx) * ppd) / ppd
target_posy = ((shape[0]-cross_thickness)/2. - target_size[0], (shape[0]-cross_thickness)/2.)
target_posy = np.floor(np.array(target_posy) * ppd) / ppd
stim = benarys_cross_generalized(shape, ppd, cross_thickness, target_size, target_type,
target_ori, target_posx, target_posy, vback, vcross, vtarget)
return stim


def todorovic_benary(
ppd=10.0,
L_size=(8.0, 8.0, 2.0, 14.0),
Expand Down Expand Up @@ -242,7 +358,9 @@ def todorovic_benary(
from stimuli.utils import plot_stimuli

stims = {
"Benary's cross": benarys_cross(),
"Benary's cross - generalized": benarys_cross_generalized(),
"Benary's cross - rectangles": benarys_cross_rectangles(),
"Benary's cross - triangles": benarys_cross_triangles(),
"Todorovic' version of Benary's cross": todorovic_benary(),
}
ax = plot_stimuli(stims)
Expand Down
4 changes: 2 additions & 2 deletions stimuli/papers/RHS2007.py
Expand Up @@ -766,9 +766,9 @@ def benary_cross(ppd=PPD, pad=True):
vback = 1.0
vtarget = 0.5
padding = (0.0, 0.0, 4.0, 4.0)
stim = illusions.benary_cross.benarys_cross(
stim = illusions.benary_cross.benarys_cross_generalized(
shape=(13, 23),
ppd=PPD,
cross_size=(4.5, 4.5, 9.5, 9.5),
cross_thickness=4.0,
target_type=("t", "t"),
target_ori=(45.0, 0.0),
Expand Down
42 changes: 15 additions & 27 deletions stimuli/papers/domijan2015.py
Expand Up @@ -20,7 +20,7 @@
"white_yazdanbakhsh",
]

PPD = 1 # default: 1
PPD = 10 # default: 10
HEIGHT_DEG = None # default: None
PAD = True

Expand Down Expand Up @@ -54,20 +54,20 @@ def check_requirements(original_size_px, height_px, height_deg, ppd):
raise ValueError('You need to define two out of ppd, height_px and height_deg')

if height_px is not None and ppd is not None:
conversion_fac = height_px / original_size_px / ppd
conversion_fac = height_px / original_size_px * PPD / ppd

if height_deg is not None and ppd is not None:
conversion_fac = height_deg / original_size_px
conversion_fac = height_deg / original_size_px * PPD

if height_px is not None and height_deg is not None and ppd is not None:
ppd_calc = int(np.round(height_px / height_deg))
assert ppd_calc == ppd
conversion_fac = height_px / original_size_px / ppd
conversion_fac = height_px / original_size_px * PPD / ppd

if height_px is not None and height_deg is not None and ppd is None:
ppd = int(np.round(height_px / height_deg))
conversion_fac = height_px / original_size_px / ppd
return height_px, height_deg, ppd, conversion_fac
conversion_fac = height_px / original_size_px * PPD / ppd
return height_px, height_deg, ppd, conversion_fac / PPD


def dungeon(height_px=110, ppd=PPD, height_deg=HEIGHT_DEG):
Expand Down Expand Up @@ -285,15 +285,15 @@ def simultaneous_brightness_contrast(height_px=100, ppd=PPD, height_deg=HEIGHT_D
target_size = np.array((21, 21)) * conversion_fac
target_pos = np.array((39, 39)) * conversion_fac

stim1 = illusions.sbc.simultaneous_contrast_general(
stim1 = illusions.sbc.simultaneous_contrast_generalized(
shape=im_size,
ppd=ppd,
target_size=target_size,
target_pos=target_pos,
vback=1.,
vtarget=0.5,
)
stim2 = illusions.sbc.simultaneous_contrast_general(
stim2 = illusions.sbc.simultaneous_contrast_generalized(
shape=im_size,
ppd=ppd,
target_size=target_size,
Expand Down Expand Up @@ -340,41 +340,35 @@ def white(height_px=80, ppd=PPD, height_deg=HEIGHT_DEG, pad=PAD):

def benary(height_px=100, ppd=PPD, height_deg=HEIGHT_DEG):
height_px, height_deg, ppd, conversion_fac = check_requirements(100, height_px, height_deg, ppd)
cross_size = np.array((30,)*4) * conversion_fac
target_size = np.array((11.1, 11.1)) * conversion_fac # TODO: fix rounding problem for different ppds
target_posx = np.array((19, 70)) * conversion_fac
target_posy = np.array((19, 30)) * conversion_fac
target_size = np.array((11, 11)) * conversion_fac

stim = illusions.benary_cross.benarys_cross(
stim = illusions.benary_cross.benarys_cross_rectangles(
shape=81*conversion_fac,
ppd=ppd,
cross_size=cross_size,
cross_thickness=21*conversion_fac,
target_type=("r", "r"),
target_ori=(0.0, 0.0),
target_size=target_size,
target_posx=target_posx,
target_posy=target_posy,
vback=1.,
vcross=0.,
vtarget=0.5,
)

padding = np.array((9., 10., 9., 10.)) * conversion_fac
padding = np.array((9, 10., 9, 10.)) * conversion_fac
stim["img"] = pad_img(stim["img"], padding, ppd, val=1.)
stim["mask"] = pad_img(stim["mask"], padding, ppd, val=0)
stim["original_range"] = (1, 9)
return stim


def todorovic(height_px=100, ppd=PPD, height_deg=HEIGHT_DEG):
# Note: Compared to original, targets are misplaced by one pixel
height_px, height_deg, ppd, conversion_fac = check_requirements(100, height_px, height_deg, ppd)
shape = np.array((100, 100)) * conversion_fac
target_size = np.array((41, 41)) * conversion_fac
covers_size = np.array((31, 31)) * conversion_fac
covers_offset = np.array((20, 20)) * conversion_fac

stim1 = illusions.todorovic.todorovic_rectangle(
shape=shape-1,
shape=shape,
ppd=ppd,
target_size=target_size,
covers_size=covers_size,
Expand All @@ -384,7 +378,7 @@ def todorovic(height_px=100, ppd=PPD, height_deg=HEIGHT_DEG):
vcovers=1.,
)
stim2 = illusions.todorovic.todorovic_rectangle(
shape=shape-1,
shape=shape,
ppd=ppd,
target_size=target_size,
covers_size=covers_size,
Expand All @@ -394,12 +388,6 @@ def todorovic(height_px=100, ppd=PPD, height_deg=HEIGHT_DEG):
vcovers=0.,
)

# In original stimulus, the targets are not placed perfectly centered
stim1['img'] = pad_img_to_shape(stim1['img'], shape, val=0.)
stim1['mask'] = pad_img_to_shape(stim1['mask'], shape, val=0)
stim2['img'] = pad_img_to_shape(stim2['img'], shape, val=1.)
stim2['mask'] = pad_img_to_shape(stim2['mask'], shape, val=0)

# Increase target index of right stimulus half
mask2 = stim2["mask"] + 1
mask2[mask2 == 1] = 0
Expand Down

0 comments on commit 31ab942

Please sign in to comment.