Skip to content

Commit

Permalink
updated benarys cross to allow more targets and also triangular targe…
Browse files Browse the repository at this point in the history
…ts; added benarys to rhs2007
  • Loading branch information
LynnSchmittwilken committed Jun 16, 2022
1 parent 2e9c08d commit 811252a
Show file tree
Hide file tree
Showing 3 changed files with 267 additions and 54 deletions.
208 changes: 170 additions & 38 deletions stimuli/illusions/benary_cross.py
@@ -1,42 +1,56 @@
import numpy as np
from stimuli.utils import degrees_to_pixels, pad_img, plot_stim
from scipy.ndimage import rotate
from stimuli.utils import degrees_to_pixels, plot_stim
from stimuli.components import triangle


def benarys_cross(
ppd=10,
cross_size=(8, 8, 8, 8),
cross_thickness=5,
target_size=2,
back=1.0,
cross=0.0,
target=0.5,
ppd=10.,
cross_size=(8., 8., 8., 8.),
cross_thickness=5.,
target_size=(2., 2.),
target_type=('r', 'r'),
target_ori=(0., 0.),
target_posx=(6., 19.),
target_posy=(6., 8.),
vback=1.,
vcross=0.,
vtarget=0.5,
):
"""
Benary's Cross Illusion (with square targets)
Benary's Cross Illusion
Parameters
----------
ppd : int
pixels per degree (visual angle)
cross_size : (float, float, float, float)
size of the cross in degrees visual angle in form (top, bottom, left, right) specifying the length of each of the cross' bars
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
padding : (float, float, float, float)
4-valued tuple specifying padding (top, bottom, left, right) in degrees visual angle
target_size : float
size of the side of target square in degrees visual angle
back : float
target_size : (float, float)
size of all target(s) in degrees visual angle
target_type : tuple of strings
type of targets to use; option: r (rectangle), t (triangle); as many targets as types
target_ori : tuple of floats
tuple with orientation of targets in deg, as many targets as orientations
target_posx : tuple of floats
tuple with x coordinates of targets in degrees, as many targets as coordinates
target_posy : tuple of floats
tuple with y coordinates of targets in degrees, as many targets as coordinates
vback : float
background value
cross : float
vcross : float
cross value
target : float
vtarget : float
target value
Returns
-------
A stimulus object
A stimulus dictionary with the stimulus ['img'] and target mask ['mask']
"""
if any(len(lst) != len(target_type) for lst in [target_ori, target_posx, target_posy]):
raise Exception("target_type, target_ori, target_posx and target_posy need to be of same length.")

(
cross_top_px,
Expand All @@ -45,38 +59,156 @@ def benarys_cross(
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)

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)
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
if (target_x_px + np.array(tposx)).max() > width:
raise Exception("Leftmost target does not fit in image.")
if (target_y_px + np.array(tposy)).max() > height:
raise Exception("Lowest target does not fit in image.")

img = np.ones((height, width)) * vback
mask = np.zeros((height, width))

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_px
tpos1x = x_edge_left - target_size_px
tpos2y = y_edge_top
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
] = 1
mask[tpos2y : tpos2y + target_size_px, tpos2x:] = 2
img[:, x_edge_left:x_edge_right] = vcross
img[y_edge_top:y_edge_bottom, :] = vcross

# Add targets:
for i in range(len(target_posx)):
if target_type[i] == 'r':
tpatch = np.zeros([target_y_px, target_x_px]) + vtarget

elif target_type[i] == 't':
tpatch = triangle(ppd, (target_size[0], target_size[1]), 0., vtarget)

else:
raise Exception('You can only use r or t as shapes')

# Rotate, resize to original shape and clean
tpatch = rotate(tpatch, angle=target_ori[i])
tpatch[tpatch < vtarget*0.9] = 0.
tpatch[tpatch > vtarget*0.9] = vtarget
tpatch = tpatch[~np.all(tpatch == 0, axis=1)] # Remove all rows with only zeros
tpatch = tpatch[:, ~np.all(tpatch == 0, axis=0)] # Remove all cols with only zeros
mpatch = np.copy(tpatch)
mpatch[mpatch != 0] = i+1

# Only change the target parts if the image:
ipatch = img[tposy[i]:tposy[i]+tpatch.shape[0], tposx[i]:tposx[i]+tpatch.shape[1]]
ipatch[tpatch == vtarget] = 0.
tpatch = tpatch + ipatch

img[tposy[i]:tposy[i]+tpatch.shape[0], tposx[i]:tposx[i]+tpatch.shape[1]] = tpatch
mask[tposy[i]:tposy[i]+tpatch.shape[0], tposx[i]:tposx[i]+tpatch.shape[1]] = mpatch
return {"img": img, "mask": mask}


def todorovic_benary(
ppd=10.,
L_size=(8., 8., 2., 14.),
target_size=(2., 2.),
target_type=('r', 'r'),
target_ori=(0., 0.),
target_posx=(2., 12.),
target_posy=(6., 8.),
vback=1.,
vcross=0.,
vtarget=0.5,
):
"""
Todorovic Benary's Cross Illusion
Parameters
----------
ppd : int
pixels per degree (visual angle)
L_size : (float, float, float, float)
size of the L's arms in degrees visual angle in form (height-top-L, height-bottom-L,
width-finger-Ls, width-basis-Ls)
target_size : (float, float)
size of all target(s) in degrees visual angle
target_type : tuple of strings
type of targets to use; option: r (rectangle), t (triangle); as many targets as types
target_ori : tuple of floats
tuple with orientation of targets in deg, as many targets as orientations
target_posx : tuple of floats
tuple with x coordinates of targets in degrees, as many targets as coordinates
target_posy : tuple of floats
tuple with y coordinates of targets in degrees, as many targets as coordinates
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 any(len(lst) != len(target_type) for lst in [target_ori, target_posx, target_posy]):
raise Exception("target_type, target_ori, target_posx and target_posy need to be of same length.")

(
L_top_px,
L_bottom_px,
L_left_px,
L_right_px,
) = degrees_to_pixels(L_size, ppd)
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)
width = L_left_px + L_right_px
height = L_top_px + L_bottom_px

if (target_x_px + np.array(tposx)).max() > width:
raise Exception("Leftmost target does not fit in image.")
if (target_y_px + np.array(tposy)).max() > height:
raise Exception("Lowest target does not fit in image.")

img = np.ones((height, width)) * vback
mask = np.zeros((height, width))

img[:, 0:L_left_px] = vcross
img[height-L_bottom_px::, 0:width-L_left_px] = vcross

# Add targets:
for i in range(len(target_posx)):
if target_type[i] == 'r':
tpatch = np.zeros([target_y_px, target_x_px]) + vtarget

elif target_type[i] == 't':
tpatch = triangle(ppd, (target_size[0], target_size[1]), 0., vtarget)

else:
raise Exception('You can only use r or t as shapes')

# Rotate, resize to original shape and clean
tpatch = rotate(tpatch, angle=target_ori[i])
tpatch[tpatch < vtarget*0.9] = 0.
tpatch[tpatch > vtarget*0.9] = vtarget
tpatch = tpatch[~np.all(tpatch == 0, axis=1)] # Remove all rows with only zeros
tpatch = tpatch[:, ~np.all(tpatch == 0, axis=0)] # Remove all cols with only zeros
mpatch = np.copy(tpatch)
mpatch[mpatch != 0] = i+1

# Only change the target parts if the image:
ipatch = img[tposy[i]:tposy[i]+tpatch.shape[0], tposx[i]:tposx[i]+tpatch.shape[1]]
ipatch[tpatch == vtarget] = 0.
tpatch = tpatch + ipatch

img[tposy[i]:tposy[i]+tpatch.shape[0], tposx[i]:tposx[i]+tpatch.shape[1]] = tpatch
mask[tposy[i]:tposy[i]+tpatch.shape[0], tposx[i]:tposx[i]+tpatch.shape[1]] = mpatch
return {"img": img, "mask": mask}


if __name__ == "__main__":
import matplotlib.pyplot as plt

stim = benarys_cross()
stim = todorovic_benary()
plot_stim(stim, mask=True)
99 changes: 88 additions & 11 deletions stimuli/papers/RHS2007.py
Expand Up @@ -2,7 +2,7 @@

import numpy as np
import stimuli
from stimuli.utils import degrees_to_pixels, pad_img_to_shape
from stimuli.utils import degrees_to_pixels, pad_img_to_shape, pad_img

__all__ = [
"WE_thick",
Expand Down Expand Up @@ -31,6 +31,7 @@
"benary_cross",
"todorovic_benary1_2",
"todorovic_benary3_4",
"todorovic_benary1_2_3_4",
"bullseye_thin",
"bullseye_thick",
]
Expand Down Expand Up @@ -600,7 +601,6 @@ def checkerboard209(ppd=PPD):
shape = degrees_to_pixels(VISEXTENT, ppd)
stim["img"] = pad_img_to_shape(stim["img"], shape, val=0.5)
stim["mask"] = pad_img_to_shape(stim["mask"], shape, val=0)

return stim


Expand All @@ -609,19 +609,96 @@ def corrugated_mondrian():
raise NotImplementedError


def benary_cross():
# TODO: not available atm
raise NotImplementedError
def benary_cross(ppd=PPD):
vback = 1.
vtarget = 0.5
padding = (0., 0., 4., 4.)
stim = stimuli.illusions.benary_cross.benarys_cross(
ppd=PPD,
cross_size=(4.5, 4.5, 9.5, 9.5),
cross_thickness=4.,
target_type=('t', 't'),
target_ori=(45., 0.),
target_size=(2.5, 2.5),
target_posx=(9.5-np.sqrt(11.25), 13.5),
target_posy=(4.5, 2.),
vback=vback,
vcross=0.,
vtarget=vtarget,
)

shape = degrees_to_pixels(VISEXTENT, ppd)
img = pad_img(stim["img"], padding, ppd, val=vback)
img = pad_img_to_shape(img, shape, vtarget)
mask = pad_img(stim["mask"], padding, ppd, val=0)
mask = pad_img_to_shape(mask, shape, 0)
return {"img": img, "mask": mask}

def todorovic_benary1_2():
# TODO: not available atm
raise NotImplementedError

def todorovic_benary1_2(ppd=PPD):
vback = 1.
vtarget = 0.5
stim = stimuli.illusions.benary_cross.todorovic_benary(
ppd=PPD,
L_size=(6.5, 6.5, 2.5, 28.5),
target_size=(2.5, 2.5),
target_type=('t', 't'),
target_ori=(0., 180.),
target_posx=(2.5, 26.),
target_posy=(4., 6.5),
vback=vback,
vcross=0.,
vtarget=vtarget,
)

def todorovic_benary3_4():
# TODO: not available atm
raise NotImplementedError
shape = degrees_to_pixels(VISEXTENT, ppd)
img = pad_img_to_shape(stim["img"], shape, vtarget)
mask = pad_img_to_shape(stim["mask"], shape, 0)
return {"img": img, "mask": mask}


def todorovic_benary3_4(ppd=PPD):
vback = 1.
vtarget = 0.5
stim = stimuli.illusions.benary_cross.todorovic_benary(
ppd=PPD,
L_size=(6.5, 6.5, 2.5, 28.5),
target_size=(2.5, 2.5),
target_type=('t', 't'),
target_ori=(45., 225.),
target_posx=(9.5, 18.),
target_posy=(6.5, 6.5-np.sqrt(11.25)/2.),
vback=vback,
vcross=0.,
vtarget=vtarget,
)

shape = degrees_to_pixels(VISEXTENT, ppd)
img = pad_img_to_shape(stim["img"], shape, vtarget)
mask = pad_img_to_shape(stim["mask"], shape, 0)
return {"img": img, "mask": mask}


def todorovic_benary1_2_3_4(ppd=PPD):
vback = 1.
vtarget = 0.5
stim = stimuli.illusions.benary_cross.todorovic_benary(
ppd=PPD,
L_size=(6.5, 6.5, 2.5, 28.5),
target_size=(2.5, 2.5),
target_type=('t', 't', 't', 't'),
target_ori=(0., 45., 225., 180.),
target_posx=(2.5, 9.5, 18., 26.),
target_posy=(4., 6.5, 6.5-np.sqrt(11.25)/2., 6.5),
vback=vback,
vcross=0.,
vtarget=vtarget,
)

shape = degrees_to_pixels(VISEXTENT, ppd)
img = pad_img_to_shape(stim["img"], shape, vtarget)
mask = pad_img_to_shape(stim["mask"], shape, 0)
return {"img": img, "mask": mask}


def bullseye_thin(ppd=PPD):
Expand Down

0 comments on commit 811252a

Please sign in to comment.