Source code for stimupy.stimuli.pinwheels

import itertools

import numpy as np

from stimupy.components import combine_masks, draw_regions
from stimupy.components.shapes import circle
from stimupy.components.shapes import ring as ring_shape
from stimupy.stimuli import mask_targets, waves

__all__ = [
    "pinwheel",
]


[docs]def pinwheel( visual_size=None, ppd=None, shape=None, frequency=None, n_segments=None, segment_width=None, rotation=0.0, target_indices=(), target_width=None, target_center=None, intensity_segments=(0.0, 1.0), intensity_background=0.5, intensity_target=0.5, origin="mean", ): """Pinwheel / radial White stimulus Parameters ---------- visual_size : (float, float) The shape of the stimulus in degrees of visual angle. (y,x) ppd : int pixels per degree (visual angle) shape : Sequence[int, int], int, or None (default) shape [height, width] of image, in pixels frequency : Number, or None (default) angular frequency of angular grating, in cycles per angular degree n_segments : int, or None (default) number of segments segment_width : Number, or None (default) angular width of a single segment, in degrees rotation : float, optional rotation (in degrees), counterclockwise, by default 0.0 target_indices : int, or Sequence[int, ...] indices segments where targets will be placed target_width : float, or Sequence[float, ...], optional target width (outer - inner radius) in deg visual angle, by default 1.0 Can specify as many target_widths as target_indices; if fewer widths are passed than indices, cycles through intensities target_center : float, or Sequence[float, ...], optional center (radius) in deg visual angle where each target will be placed within its segment, by default 1.0. Can specify as many centers as target_indices; if fewer centers are passed than indices, cycles through intensities intensity_segments : Sequence[float, ...] intensity value for each segment, by default (1.0, 0.0). Can specify as many intensities as n_segments; If fewer intensities are passed than n_segments, cycles through intensities intensity_background : float (optional) intensity value of background, by default 0.5 intensity_target : float, or Sequence[float, ...], optional intensity value for each target, by default 0.5. Can specify as many intensities as number of target_indices; If fewer intensities are passed than target_indices, cycles through intensities origin : "corner", "mean" or "center" if "corner": set origin to upper left corner if "mean": set origin to hypothetical image center (default) if "center": set origin to real center (closest existing value to mean) 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 ---------- Robinson, A. E., Hammon, P. S., & de Sa, V. R. (2007). Explaining brightness illusions using spatial filtering and local response normalization. Vision Research, 47(12), 1631-1644. https://doi.org/10.1016/j.visres.2007.02.017 """ # Draw angular grating stim = waves.square_angular( visual_size=visual_size, ppd=ppd, shape=shape, frequency=frequency, segment_width=segment_width, n_segments=n_segments, period="ignore", rotation=rotation, origin=origin, intensity_segments=intensity_segments, ) # Mask to circular aperture radius = min(stim["visual_size"]) / 2 stim["circle_mask"] = circle( visual_size=visual_size, ppd=ppd, shape=shape, radius=radius, origin=origin, )["circle_mask"] stim["segment_mask"] = np.where(stim["circle_mask"], stim["segment_mask"], 0) stim["img"] = np.where(stim["circle_mask"], stim["img"], intensity_background) stim["intensity_background"] = intensity_background # Target segment mask if isinstance(target_indices, (int, float)): target_indices = (target_indices,) target_segment_mask = mask_targets( element_mask=stim["grating_mask"], target_indices=target_indices ) stim["target_indices"] = target_indices # Mask ring regions if target_center is None: target_center = radius / 2 if isinstance(target_center, (int, float)): target_center = (target_center,) stim["target_center"] = target_center target_center = tuple(itertools.islice(itertools.cycle(target_center), len(target_indices))) if target_width is None: raise ValueError("pinwheel() missing argument 'target_width' which is not 'None'") if isinstance(target_width, (int, float)): target_width = (target_width,) stim["target_width"] = target_width target_width = tuple(itertools.islice(itertools.cycle(target_width), len(target_indices))) target_ring_masks = [] for target_idx, (center, width) in enumerate(zip(target_center, target_width)): # Draw ring inner_radius = center - (width / 2) outer_radius = center + (width / 2) if inner_radius < 0 or outer_radius > np.min(visual_size) / 2: raise ValueError("target does not fully fit into pinwheel") ring = ring_shape( radii=[inner_radius, outer_radius], intensity_ring=target_idx, visual_size=stim["visual_size"], ppd=stim["ppd"], shape=stim["shape"], ) target_ring_masks.append(ring["ring_mask"]) # Combine segment & ring masks target_masks = [] for target_idx, ring_mask in enumerate(target_ring_masks): # Find where ring intesects with target segment target_mask = (target_segment_mask == target_idx + 1) & ring_mask target_masks.append(target_mask) # Combine target masks if len(target_masks) > 0: target_mask = combine_masks(*target_masks) else: target_mask = np.zeros_like(stim["img"]) stim["target_mask"] = target_mask.astype(int) # Draw target(s) stim["img"] = np.where( target_mask, draw_regions(mask=target_mask, intensities=intensity_target), stim["img"] ) stim["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 = { "visual_size": (10, 10), "ppd": 30, } default_params.update(kwargs) # fmt: off stimuli = { "pinwheel": pinwheel(**default_params, n_segments=10, target_width=2, target_indices=3), } # fmt: on return stimuli if __name__ == "__main__": from stimupy.utils import plot_stimuli stims = overview() plot_stimuli(stims, mask=False, save=None)