Skip to content

Commit

Permalink
added all missing docstrings, re-organized utils, replaced degrees_to…
Browse files Browse the repository at this point in the history
…_pixels by resolution-funcs
  • Loading branch information
LynnSchmittwilken committed Feb 22, 2023
1 parent 16fe40b commit 7e37afd
Show file tree
Hide file tree
Showing 20 changed files with 761 additions and 450 deletions.
30 changes: 27 additions & 3 deletions stimupy/components/__init__.py
@@ -1,7 +1,6 @@
import itertools
import warnings
from copy import deepcopy

import numpy as np

from stimupy.utils import int_factorize, resolution
Expand Down Expand Up @@ -435,6 +434,15 @@ def round_n_phases(n_phases, length, period="either"):


def create_overview():
"""
Create dictionary with examples from all stimulus-components
Returns
-------
stims : dict
dict with all stimuli containing individual stimulus dicts.
"""

p = {
"visual_size": 10,
"ppd": 20,
Expand Down Expand Up @@ -505,10 +513,26 @@ def create_overview():
return stims


def overview(mask=False, save=None):
def overview(mask=False, save=None, extent_key="shape"):
"""
Plot overview with examples from all stimulus-components
Parameters
----------
mask : bool or str, optional
If True, plot mask on top of stimulus image (default: False).
If string is provided, plot this key from stimulus dictionary as mask
save : None or str, optional
If None (default), do not save the plot.
If string is provided, save plot under this name.
extent_key : str, optional
Key to extent which will be used for plotting.
Default is "shape", using the image size in pixels as extent.
"""
from stimupy.utils import plot_stimuli

stims = create_overview()

# Plotting
plot_stimuli(stims, mask=mask, save=save)
plot_stimuli(stims, mask=mask, save=save, extent_key=extent_key)
6 changes: 3 additions & 3 deletions stimupy/components/mondrians.py
@@ -1,7 +1,7 @@
import numpy as np

from stimupy.components.shapes import parallelogram
from stimupy.utils import degrees_to_pixels, resolution
from stimupy.utils import resolution

__all__ = [
"mondrians",
Expand Down Expand Up @@ -88,8 +88,8 @@ def mondrians(
except Exception:
raise ValueError("Mondrian position tuples should be (ypos, xpos)")

ypos, xpos = degrees_to_pixels(mondrian_positions[m], ppd[0])
individual_shapes = degrees_to_pixels(mondrian_sizes[m], ppd[0])
ypos, xpos = resolution.lengths_from_visual_angles_ppd(mondrian_positions[m], ppd[0])
individual_shapes = resolution.lengths_from_visual_angles_ppd(mondrian_sizes[m], ppd[0])

try:
if len(individual_shapes) == 2:
Expand Down
29 changes: 27 additions & 2 deletions stimupy/illusions/__init__.py
Expand Up @@ -21,6 +21,15 @@


def create_overview():
"""
Create dictionary with examples from all stimulus-illusions
Returns
-------
stims : dict
dict with all stimuli containing individual stimulus dicts.
"""

p = {
"visual_size": (10, 10),
"ppd": 20,
Expand Down Expand Up @@ -135,10 +144,26 @@ def create_overview():
return stims


def overview(mask=False, save=None):
def overview(mask=False, save=None, extent_key="shape"):
"""
Plot overview with examples from all stimulus-illusions
Parameters
----------
mask : bool or str, optional
If True, plot mask on top of stimulus image (default: False).
If string is provided, plot this key from stimulus dictionary as mask
save : None or str, optional
If None (default), do not save the plot.
If string is provided, save plot under this name.
extent_key : str, optional
Key to extent which will be used for plotting.
Default is "shape", using the image size in pixels as extent.
"""
from stimupy.utils import plot_stimuli

stims = create_overview()

# Plotting
plot_stimuli(stims, mask=mask, save=save)
plot_stimuli(stims, mask=mask, save=save, extent_key=extent_key)
10 changes: 5 additions & 5 deletions stimupy/illusions/benarys.py
Expand Up @@ -4,7 +4,7 @@
from scipy.ndimage import rotate

from stimupy.components.shapes import cross, triangle
from stimupy.utils import degrees_to_pixels, resolution
from stimupy.utils import resolution

__all__ = [
"cross_generalized",
Expand Down Expand Up @@ -348,7 +348,7 @@ def todorovic_generalized(
raise ValueError("ppd should be equal in x and y direction")

L_size = (visual_size[0] / 2, visual_size[0] / 2, L_width, visual_size[1] - L_width)
top, bottom, left, right = degrees_to_pixels(L_size, np.unique(ppd))
top, bottom, left, right = resolution.lengths_from_visual_angles_ppd(L_size, np.unique(ppd))
width, height = left + right, top + bottom

# Create stimulus without targets
Expand Down Expand Up @@ -632,9 +632,9 @@ def add_targets(
"target_type, target_orientation, target_x and target_y need the same length."
)

theight, twidth = degrees_to_pixels(target_size, ppd)
ty = degrees_to_pixels(target_y, ppd)
tx = degrees_to_pixels(target_x, ppd)
theight, twidth = resolution.lengths_from_visual_angles_ppd(target_size, ppd)
ty = resolution.lengths_from_visual_angles_ppd(target_y, ppd)
tx = resolution.lengths_from_visual_angles_ppd(target_x, ppd)

if (twidth + np.array(tx)).max() > img.shape[1]:
raise ValueError("Rightmost target does not fit in image.")
Expand Down
12 changes: 6 additions & 6 deletions stimupy/illusions/cubes.py
@@ -1,6 +1,6 @@
import numpy as np

from stimupy.utils import degrees_to_pixels, resolution
from stimupy.utils import resolution

__all__ = [
"varying_cells",
Expand Down Expand Up @@ -80,9 +80,9 @@ def varying_cells(
cell_spacing = list(cell_spacing)
cell_spacing[n_cells - 1] = 0

cheights = degrees_to_pixels(cell_heights, ppd)
cwidths = degrees_to_pixels(cell_widths, ppd)
cspaces = degrees_to_pixels(cell_spacing, ppd)
cheights = resolution.lengths_from_visual_angles_ppd(cell_heights, np.unique(ppd))
cwidths = resolution.lengths_from_visual_angles_ppd(cell_widths, np.unique(ppd))
cspaces = resolution.lengths_from_visual_angles_ppd(cell_spacing, np.unique(ppd))
height = sum(cwidths) + sum(cspaces)
width = height

Expand Down Expand Up @@ -212,8 +212,8 @@ def cube(
targets = ()

height, width = shape
cell_space = degrees_to_pixels(cell_spacing, np.unique(ppd)[0])
cell_thick = degrees_to_pixels(cell_thickness, np.unique(ppd)[0])
cell_space = resolution.lengths_from_visual_angles_ppd(cell_spacing, np.unique(ppd))
cell_thick = resolution.lengths_from_visual_angles_ppd(cell_thickness, np.unique(ppd))

# Initiate image
img = np.ones([height, width]) * intensity_background
Expand Down
16 changes: 8 additions & 8 deletions stimupy/illusions/hermanns.py
@@ -1,6 +1,6 @@
import numpy as np

from stimupy.utils import degrees_to_pixels, resolution
from stimupy.utils import resolution

__all__ = [
"grid",
Expand Down Expand Up @@ -53,19 +53,19 @@ def grid(
if len(np.unique(ppd)) > 1:
raise ValueError("ppd should be equal in x and y direction")

element_height, element_width, element_thick = degrees_to_pixels(element_size, np.unique(ppd))
eheight, ewidth, ethick = resolution.lengths_from_visual_angles_ppd(element_size, np.unique(ppd))

if element_height <= element_thick:
if eheight <= ethick:
raise ValueError("Element thickness larger than height")
if element_width <= element_thick:
if ewidth <= ethick:
raise ValueError("Element thickness larger than width")
if element_thick <= 0:
if ethick <= 0:
raise ValueError("Increase element thickness")

img = np.ones(shape) * intensity_background
for i in range(element_thick):
img[i::element_height, :] = intensity_grid
img[:, i::element_width] = intensity_grid
for i in range(ethick):
img[i::eheight, :] = intensity_grid
img[:, i::ewidth] = intensity_grid

stim = {
"img": img,
Expand Down
4 changes: 2 additions & 2 deletions stimupy/illusions/mondrians.py
@@ -1,7 +1,7 @@
import numpy as np

from stimupy.components.mondrians import mondrians
from stimupy.utils import degrees_to_pixels, resolution
from stimupy.utils import resolution

__all__ = [
"corrugated_mondrians",
Expand Down Expand Up @@ -76,7 +76,7 @@ def corrugated_mondrians(
)

height, width = visual_size
mdepths_px = degrees_to_pixels(mondrian_depths, ppd[0])
mdepths_px = resolution.lengths_from_visual_angles_ppd(mondrian_depths, ppd[0])
max_depth = np.abs(np.array(mdepths_px)).max()
sum_depth = np.array(mdepths_px).sum()
red_depth = np.maximum(max_depth, sum_depth)
Expand Down
24 changes: 17 additions & 7 deletions stimupy/illusions/todorovics.py
Expand Up @@ -2,7 +2,7 @@

from stimupy.components.shapes import cross as cross_shape
from stimupy.components.shapes import rectangle as rectangle_shape
from stimupy.utils import degrees_to_pixels, pad_dict_to_shape, resolution, stack_dicts
from stimupy.utils import pad_dict_to_shape, resolution, stack_dicts

__all__ = [
"rectangle_generalized",
Expand Down Expand Up @@ -111,9 +111,14 @@ def rectangle_generalized(
mask = stim["shape_mask"]

# Add covers
cheight, cwidth = degrees_to_pixels(covers_size, ppd)
cx = degrees_to_pixels(covers_x, np.unique(ppd))
cy = degrees_to_pixels(covers_y, np.unique(ppd))
cheight, cwidth = resolution.lengths_from_visual_angles_ppd(covers_size, np.unique(ppd), round=False)
cx = resolution.lengths_from_visual_angles_ppd(covers_x, np.unique(ppd), round=False)
cy = resolution.lengths_from_visual_angles_ppd(covers_y, np.unique(ppd), round=False)

cheight = int(np.round(cheight))
cwidth = int(np.round(cwidth))
cx = np.round(cx).astype(int)
cy = np.round(cy).astype(int)

if np.max(cx) < np.min(cx) + cwidth or np.max(cy) < np.min(cy) + cheight:
raise ValueError("Covers overlap")
Expand Down Expand Up @@ -338,9 +343,14 @@ def cross_generalized(
img = stim["img"]
mask = stim["shape_mask"]

cheight, cwidth = degrees_to_pixels(covers_size, ppd)
cx = degrees_to_pixels(covers_x, ppd)
cy = degrees_to_pixels(covers_y, ppd)
cheight, cwidth = resolution.lengths_from_visual_angles_ppd(covers_size, np.unique(ppd), round=False)
cx = resolution.lengths_from_visual_angles_ppd(covers_x, np.unique(ppd), round=False)
cy = resolution.lengths_from_visual_angles_ppd(covers_y, np.unique(ppd), round=False)

cheight = int(np.round(cheight))
cwidth = int(np.round(cwidth))
cx = np.round(cx).astype(int)
cy = np.round(cy).astype(int)

for i in range(len(covers_x)):
img[cy[i] : cy[i] + cheight, cx[i] : cx[i] + cwidth] = intensity_covers
Expand Down
8 changes: 4 additions & 4 deletions stimupy/illusions/wedding_cakes.py
@@ -1,7 +1,7 @@
import numpy as np
from scipy.signal import fftconvolve

from stimupy.utils import degrees_to_pixels, resolution
from stimupy.utils import resolution

__all__ = [
"wedding_cake",
Expand Down Expand Up @@ -69,7 +69,7 @@ def wedding_cake(
raise ValueError("ppd should be equal in x and y direction")

nY, nX = shape
Ly, Lx, Lw = degrees_to_pixels(L_size, np.unique(ppd))
Ly, Lx, Lw = resolution.lengths_from_visual_angles_ppd(L_size, np.unique(ppd))
Lyh, Lxh = int(Ly / 2) + 1, int(Lx / 2) + 1

# Create L-shaped patch
Expand Down Expand Up @@ -97,7 +97,7 @@ def wedding_cake(

if target_indices1 is not None and target_height is not None:
# Create target patch2
theight = degrees_to_pixels(target_height, np.unique(ppd))
theight = resolution.lengths_from_visual_angles_ppd(target_height, np.unique(ppd))
tpatch1 = np.zeros(L_patch.shape)
tpatch1[int(Ly / 2 - theight / 2) : int(Ly / 2 + theight / 2), Lx - Lw : :] = (
-intensity_grating[1] + intensity_target
Expand All @@ -120,7 +120,7 @@ def wedding_cake(

if target_indices2 is not None and target_height is not None:
# Create target patch2
theight = degrees_to_pixels(target_height, np.unique(ppd))
theight = resolution.lengths_from_visual_angles_ppd(target_height, np.unique(ppd))
tpatch2 = np.zeros(L_patch.shape)
tpatch2[int(Ly / 2 - theight / 2) : int(Ly / 2 + theight / 2), Lx - Lw : :] = (
-intensity_grating[0] + intensity_target
Expand Down
39 changes: 20 additions & 19 deletions stimupy/illusions/whites.py
Expand Up @@ -8,7 +8,7 @@
from stimupy.illusions.angulars import pinwheel as radial
from stimupy.illusions.circulars import rings as circular
from stimupy.illusions.wedding_cakes import wedding_cake
from stimupy.utils import degrees_to_pixels
from stimupy.utils import resolution

__all__ = [
"generalized",
Expand Down Expand Up @@ -467,45 +467,46 @@ def anderson(

img = stim["img"]
mask = stim["target_mask"]
stripe_center_offset_px = degrees_to_pixels(stripe_center_offset, ppd)
stripe_size_px = degrees_to_pixels(stripe_height, ppd)
cycle_width_px = degrees_to_pixels(1.0 / (frequency * 2), ppd) * 2
phase_width_px = cycle_width_px // 2
soffset = resolution.lengths_from_visual_angles_ppd(stripe_center_offset, np.unique(ppd)[0])
sheight = resolution.lengths_from_visual_angles_ppd(stripe_height, np.unique(ppd)[0])
cycle_width = resolution.lengths_from_visual_angles_ppd(1.0 / (frequency * 2), np.unique(ppd)[0]) * 2

phase_width_px = cycle_width // 2
height, width = img.shape
nbars = width // phase_width_px
ttop, tbot = np.array(target_indices_top), np.array(target_indices_bottom)
ttop[ttop < 0] = nbars + ttop[ttop < 0]
tbot[tbot < 0] = nbars + tbot[tbot < 0]

if stripe_size_px / 2.0 > stripe_center_offset_px:
if sheight / 2.0 > soffset:
raise ValueError("Stripes overlap! Increase stripe offset or decrease stripe size.")
if (target_height / 2 - target_center_offset + stripe_height / 2 - stripe_center_offset) > 0:
raise ValueError(
"Stripes overlap with targets! Increase stripe or target offsets or"
"decrease stripe or target size"
)
if stripe_center_offset * ppd % 1 != 0:
offsets_new = stripe_center_offset_px / ppd
offsets_new = soffset / ppd
warnings.warn(
f"Stripe offsets rounded because of ppd; {stripe_center_offset} -> {offsets_new}"
)

# Add stripe at top
ystart = height // 2 - stripe_center_offset_px - stripe_size_px // 2
img[ystart : ystart + stripe_size_px, 0 : phase_width_px * np.min(ttop)] = intensity_stripes[0]
ystart = height // 2 - soffset - sheight // 2
img[ystart : ystart + sheight, 0 : phase_width_px * np.min(ttop)] = intensity_stripes[0]
img[
ystart : ystart + stripe_size_px, phase_width_px * (np.max(ttop) + 1) : :
ystart : ystart + sheight, phase_width_px * (np.max(ttop) + 1) : :
] = intensity_stripes[0]
if (ystart < 0) or (ystart + stripe_size_px > height):
if (ystart < 0) or (ystart + sheight > height):
raise ValueError("Anderson stripes do not fully fit into stimulus")

# Add stripe at bottom
ystart = height // 2 + stripe_center_offset_px - stripe_size_px // 2
img[ystart : ystart + stripe_size_px, 0 : phase_width_px * np.min(tbot)] = intensity_stripes[1]
ystart = height // 2 + soffset - sheight // 2
img[ystart : ystart + sheight, 0 : phase_width_px * np.min(tbot)] = intensity_stripes[1]
img[
ystart : ystart + stripe_size_px, phase_width_px * (np.max(tbot) + 1) : :
ystart : ystart + sheight, phase_width_px * (np.max(tbot) + 1) : :
] = intensity_stripes[1]
if (ystart < 0) or (ystart + stripe_size_px > height):
if (ystart < 0) or (ystart + sheight > height):
raise ValueError("Anderson stripes do not fully fit into stimulus")

stim["img"] = img
Expand Down Expand Up @@ -698,10 +699,10 @@ def yazdanbakhsh(

img = stim["img"]
mask = stim["target_mask"]
gap_size_px = degrees_to_pixels(gap_size, ppd)
target_offset_px = degrees_to_pixels(target_center_offset, ppd)
tsize_px = degrees_to_pixels(target_height, ppd)
cycle_width_px = degrees_to_pixels(1.0 / (frequency * 2), ppd) * 2
gap_size_px = resolution.lengths_from_visual_angles_ppd(gap_size, np.unique(ppd)[0])
target_offset_px = resolution.lengths_from_visual_angles_ppd(target_center_offset, np.unique(ppd)[0])
tsize_px = resolution.lengths_from_visual_angles_ppd(target_height, np.unique(ppd)[0])
cycle_width_px = resolution.lengths_from_visual_angles_ppd(1.0 / (frequency * 2), np.unique(ppd)[0]) * 2
phase_width_px = cycle_width_px // 2
height, width = img.shape
nbars = width // phase_width_px
Expand Down

0 comments on commit 7e37afd

Please sign in to comment.