Source code for stimupy.stimuli.dungeons

import numpy as np

from stimupy.utils import pad_to_shape, resolution

__all__ = [
    "dungeon",
]


[docs]def dungeon( visual_size=None, ppd=None, shape=None, cell_size=None, n_cells=None, target_radius=1, intensity_background=0.0, intensity_grid=1.0, intensity_target=0.5, ): """Dungeon stimulus (Bressan, 2001) with diamond target. Parameters ---------- visual_size : Sequence[Number, Number], Number, or None (default) visual size [height, width] of grating, in degrees ppd : Sequence[Number, Number], Number, or None (default) pixels per degree [vertical, horizontal] shape : Sequence[Number, Number], Number, or None (default) shape [height, width] of grating, in pixels cell_size : Sequence[Number, Number], Number, or None (default) size of individual cell (height, width) n_cells : Sequence[Number, Number], Number, or None (default) the number of square cells (not counting background) per dimension target_radius : int the "Manhattan radius" of the diamond target in # cells intensity_background : float intensity value for background intensity_grid : float intensity value for grid cells intensity_target : float intensity value for target Returns ------- dict[str, Any] dict with the stimulus (key: "img"), mask with integer index for each target (key: "target_mask"), and additional keys containing stimulus parameters References ---------- Bressan, P. (2001). Explaining lightness illusions. Perception, 30(9), 1031-1046. https://doi.org/10.1068/p3109 Domijan, D. (2015). A neurocomputational account of the role of contour facilitation in brightness perception. Frontiers in Human Neuroscience, 9, 93. https://doi.org/10.3389/fnhum.2015.00093 """ if isinstance(visual_size, (float, int)) or (visual_size is None): visual_size = (visual_size, visual_size) if isinstance(cell_size, (float, int)) or (cell_size is None): cell_size = (cell_size, cell_size) if isinstance(n_cells, (float, int)) or (n_cells is None): n_cells = (n_cells, n_cells) params = resolve_dungeon_params(None, visual_size, ppd, n_cells, cell_size) n_cells = (int(params["n_cells"][0]), int(params["n_cells"][1])) height, width = params["shape"].height, params["shape"].width cheight = height / (n_cells[0] * 2 - 1) cwidth = width / (n_cells[1] * 2 - 1) # create 2D array of grid arr = np.ones((n_cells[0] * 2 - 1, n_cells[1] * 2 - 1)) * intensity_grid mask_arr = np.zeros((n_cells[0] * 2 - 1, n_cells[1] * 2 - 1)) # compute Manhattan distances from image center (=radius) of each pixel y = np.arange(-n_cells[0] + 1, n_cells[0]) x = np.arange(-n_cells[1] + 1, n_cells[1]) radii = np.abs(x[np.newaxis]) + np.abs(y[:, np.newaxis]) # add targets idx = radii <= (target_radius * 2) arr[idx] = intensity_target mask_arr[idx] = 1 # compute and apply grid mask grid_mask = [ [(False if i % 2 == 0 and j % 2 == 0 else True) for i in range(n_cells[1] * 2 - 1)] for j in range(n_cells[0] * 2 - 1) ] grid_mask = np.array(grid_mask) arr[grid_mask] = intensity_background mask_arr[grid_mask] = 0 img = arr.repeat(cheight, axis=0).repeat(cwidth, axis=1) mask = mask_arr.repeat(cheight, axis=0).repeat(cwidth, axis=1) # Make sure that stimulus size is as requested if (img.shape[0] != height) or (img.shape[1] != width): img = pad_to_shape(img, (height, width), intensity_background) mask = pad_to_shape(mask, (height, width), 0) stim = { "img": img, "target_mask": mask.astype(int), "target_radius": target_radius, "intensity_background": intensity_background, "intensity_grid": intensity_grid, "intensity_target": intensity_target, **params, } return stim
def resolve_dungeon_params( shape=None, visual_size=None, ppd=None, n_cells=None, cell_size=None, ): # Try to resolve resolution try: shape, visual_size, ppd = resolution.resolve(shape=shape, visual_size=visual_size, ppd=ppd) except resolution.TooManyUnknownsError: ppd = resolution.validate_ppd(ppd) shape = resolution.validate_shape(shape) visual_size = resolution.validate_visual_size(visual_size) n_cells1, visual_angle1, cell_size1 = resolve_cells_1d( visual_size[0], n_cells[0] - 0.5, cell_size[0] ) n_cells2, visual_angle2, cell_size2 = resolve_cells_1d( visual_size[1], n_cells[1] - 0.5, cell_size[1] ) # Now resolve resolution shape, visual_size, ppd = resolution.resolve( shape=shape, visual_size=(visual_angle1 / 2, visual_angle2 / 2), ppd=ppd ) return { "visual_size": visual_size, "ppd": ppd, "shape": shape, "n_cells": (n_cells1 + 0.5, n_cells2 + 0.5), "cell_size": (cell_size1 / 2, cell_size2 / 2), } def resolve_cells_1d( visual_angle=None, n_cells=None, cell_size=None, ): # Try to resolve number and size of cells if cell_size is not None: cells_pd = 1 / cell_size / 2 else: cells_pd = None try: n_cells, visual_angle, cells_pd = resolution.resolve_1D( length=n_cells, visual_angle=visual_angle, ppd=cells_pd ) visual_angle = visual_angle * 2 cell_size = 1 / cells_pd except Exception as e: raise Exception("Could not resolve n_cells, cell_size") from e return n_cells, visual_angle, cell_size def overview(**kwargs): """Generate example stimuli from this module Returns ------- stims : dict dict with all stimuli containing individual stimulus dicts. """ default_params = { "visual_size": 10, "ppd": 30, } default_params.update(kwargs) # fmt: off stimuli = { "dungeon": dungeon(**default_params, n_cells=5) } # fmt: on return stimuli if __name__ == "__main__": from stimupy.utils import plot_stimuli stims = overview() plot_stimuli(stims, mask=False, save=None)