Source code for stimupy.stimuli.cubes

import numpy as np

from stimupy.utils import resolution

__all__ = [
    "varying_cells",
    "cube",
]


[docs]def varying_cells( ppd=None, cell_lengths=None, cell_thickness=None, cell_spacing=None, target_indices=(), intensity_background=0.0, intensity_cells=1.0, intensity_target=0.5, ): """ Cube stimulus (Agostini & Galmonte, 2002) with flexible cell lengths. Parameters ---------- ppd : Number or None (default) pixels per degree (visual angle) cell_lengths : Sequence[Number, ...], Number of None (default) lengths of individual cells in degrees cell_thickness : Number or None (default) thickness of each cell in degrees cell_spacing : Number or None (default) spacing between cells in degrees target_indices : Sequence or None target indices; will be used on each side intensity_background : float intensity value for background intensity_cells : 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 ---------- Agostini, T., and Galmonte, A. (2002). Perceptual organization overcomes the effects of local surround in determining simultaneous lightness contrast. Psychol. Sci. 13, 89-93. https://doi.org/10.1111/1467-9280.00417 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 not isinstance(ppd, (float, int)): ppd = np.unique(ppd) if len(ppd) != 1: raise ValueError("ppd has to be the same in x and y direction") else: ppd = ppd[0] if isinstance(cell_lengths, (float, int)): cell_lengths = (cell_lengths,) if target_indices is None: target_indices = () if isinstance(target_indices, (float, int)): target_indices = (target_indices,) n_cells = len(cell_lengths) clengths = resolution.lengths_from_visual_angles_ppd(cell_lengths, ppd) cthick = resolution.lengths_from_visual_angles_ppd(cell_thickness, ppd) cspace = resolution.lengths_from_visual_angles_ppd(cell_spacing, ppd) height = ( np.maximum(clengths[0], cthick) + np.maximum(clengths[-1], cthick) + sum(clengths[1 : n_cells - 1]) + cspace * (n_cells - 1) ) width = height # Initiate image cell_mask = np.zeros([height, width]) target_mask = np.zeros([height, width]) xs = 0 counter = 1 for i in range(n_cells): if i in target_indices: fill_mask = 1 else: fill_mask = 0 # Add cells top cell_mask[0:cthick, xs : xs + clengths[i]] = counter target_mask[0:cthick, xs : xs + clengths[i]] += fill_mask # Add cells bottom cell_mask[height - cthick : :, width - xs - clengths[i] : width - xs] = counter + 1 target_mask[height - cthick : :, width - xs - clengths[i] : width - xs] += fill_mask # Add cells left cell_mask[height - xs - clengths[i] : height - xs, 0:cthick] = counter + 2 target_mask[height - xs - clengths[i] : height - xs, 0:cthick] += fill_mask # Add cells right cell_mask[xs : xs + clengths[i], width - cthick : :] = counter + 3 target_mask[xs : xs + clengths[i], width - cthick : :] += fill_mask if i == 0: xs += np.maximum(clengths[i], cthick) + cspace elif i == n_cells - 2: xs += np.maximum(clengths[-2], cthick) + cspace else: xs += clengths[i] + cspace counter += 4 unique_vals = np.unique(cell_mask) for v in range(len(unique_vals) - 1): cell_mask[cell_mask == unique_vals[v + 1]] = v + 1 img = np.where(cell_mask != 0, intensity_cells, intensity_background) img = np.where(target_mask != 0, intensity_target, img) target_mask = np.where(target_mask >= 1, 1, 0) stim = { "img": img, "cell_mask": cell_mask.astype(int), "target_mask": target_mask.astype(int), "shape": img.shape, "visual_size": np.array(img.shape) / ppd, "ppd": ppd, "target_indices": target_indices, "cell_lengths": cell_lengths, "cell_thickness": cell_thickness, "cell_spacing": cell_spacing, "intensity_background": intensity_background, "intensity_cells": intensity_cells, "intensity_target": intensity_target, } return stim
[docs]def cube( visual_size=None, ppd=None, shape=None, n_cells=None, target_indices=(), cell_thickness=None, cell_spacing=None, intensity_background=0.0, intensity_cells=1.0, intensity_target=0.5, ): """Cube illusion (Agostini & Galmonte, 2002) Parameters ---------- visual_size : Sequence[Number, Number], Number, or None (default) visual size [height, width] of image, 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 image, in pixels n_cells : int the number of square cells (not counting background) per dimension target_indices : Sequence Target indices. Will be used on each side cell_thickness : Number or None (default) thickness of each cell in degrees cell_spacing : Sequence[Number, Number], Number or None (default) spacing between cells in degrees (height, width) intensity_background : float intensity value for background intensity_cells : 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 ---------- Agostini, T., and Galmonte, A. (2002). Perceptual organization overcomes the effects of local surround in determining simultaneous lightness contrast. Psychol. Sci. 13, 89-93. https://doi.org/10.1111/1467-9280.00417 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 """ # Resolve resolution shape, visual_size, ppd = resolution.resolve(shape=shape, visual_size=visual_size, ppd=ppd) if len(np.unique(ppd)) > 1: raise ValueError("ppd should be equal in x- and y-direction") if n_cells is None: raise ValueError("cube() missing argument 'n_cells' which is not 'None'") if cell_thickness is None: raise ValueError("cube() missing argument 'cell_thickness' which is not 'None'") if cell_spacing is None: raise ValueError("cube() missing argument 'cell_spacing' which is not 'None'") if isinstance(cell_spacing, (float, int)): cell_spacing = (cell_spacing, cell_spacing) if isinstance(n_cells, (float, int)): n_cells = (n_cells, n_cells) if target_indices is None: target_indices = () if isinstance(target_indices, (float, int)): target_indices = (target_indices,) height, width = shape 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 cell_mask = np.zeros([height, width]) target_mask = np.zeros([height, width]) # Calculate cell widths and heights cell_height = int((height - cell_space[0] * (n_cells[0] - 1)) / n_cells[0]) cell_width = int((width - cell_space[1] * (n_cells[1] - 1)) / n_cells[1]) if (cell_thick > cell_height) or (cell_thick > cell_width): raise ValueError("cannot fit all cells into image") # Calculate cell placements: xs = np.arange(0, width - 1, cell_width + cell_space[1]) rxs = xs[::-1] ys = np.arange(0, height - 1, cell_height + cell_space[0]) rys = ys[::-1] # Add cells: top and bottom counter = 1 for i in range(n_cells[1]): if i in target_indices: fill_mask = 1 else: fill_mask = 0 cell_mask[0:cell_thick, xs[i] : xs[i] + cell_width] = counter cell_mask[height - cell_thick : :, rxs[i] : rxs[i] + cell_width] = counter + 1 target_mask[0:cell_thick, xs[i] : xs[i] + cell_width] += fill_mask target_mask[height - cell_thick : :, rxs[i] : rxs[i] + cell_width] += fill_mask counter += 2 # Add cells: left and right for i in range(n_cells[0]): if i in target_indices: fill_mask = 1 else: fill_mask = 0 cell_mask[rys[i] : rys[i] + cell_height, 0:cell_thick] = counter cell_mask[ys[i] : ys[i] + cell_height, height - cell_thick : :] = counter + 1 target_mask[rys[i] : rys[i] + cell_height, 0:cell_thick] += fill_mask target_mask[ys[i] : ys[i] + cell_height, height - cell_thick : :] += fill_mask counter += 2 unique_vals = np.unique(cell_mask) for v in range(len(unique_vals) - 1): cell_mask[cell_mask == unique_vals[v + 1]] = v + 1 img = np.where(cell_mask != 0, intensity_cells, intensity_background) img = np.where(target_mask != 0, intensity_target, img) target_mask = np.where(target_mask >= 1, 1, 0) stim = { "img": img, "cell_mask": cell_mask.astype(int), "target_mask": target_mask.astype(int), "shape": shape, "visual_size": visual_size, "ppd": ppd, "target_indices": target_indices, "n_cells": n_cells, "cell_thickness": cell_thickness, "cell_spacing": cell_spacing, "intensity_background": intensity_background, "intensity_cells": intensity_cells, "intensity_target": intensity_target, } return stim
def overview(**kwargs): """Generate example stimuli from this module Returns ------- stims : dict dict with all stimuli containing individual stimulus dicts. """ default_params = { "ppd": 30, "cell_thickness": 1, "cell_spacing": 0.5, } default_params.update(kwargs) # fmt: off stimuli = { "cubes_regular": cube(**default_params, visual_size=10, n_cells=5, target_indices=(1, 2)), "cubes_variable": varying_cells(**default_params, cell_lengths=(2, 4, 2), target_indices=1), } # fmt: on return stimuli if __name__ == "__main__": from stimupy.utils import plot_stimuli stims = overview() plot_stimuli(stims, mask=False, save=None)