def clear_surface(self): self.unlock_pixel_array() return Canvas.clear_surface(self)
class RandomDotKinematogram(Stimulus): """Random Dot Kinematogram""" def __init__(self, area_radius, n_dots, target_direction, target_dot_ratio, position=None, dot_speed=None, dot_lifetime=None, dot_radius=None, dot_colour=None, background_colour=None, north_up_clockwise=None): """Create a Random Dot Kinematogram Parameters: ----------- area_radius : int the radius of the stimulus area n_dots : int number of moving dots target_direction : int, float (0-360) movement target direction in degrees target_dot_ratio : float (0-1) ratio of dots that move consistently in the same target direction (the rest of the target moves in a random direction) can be sometimes only approximated! self.target_dot_ratio returns the precise actual target dot ratio position : (int, int), optional position of the stimulus dot_speed : int, optional the moving speed in pixel per second (default=100) dot_lifetime : int, optional the time the object lives in milliseconds (default 400) dot_radius : int, optional radius of the dots (default = 3) dot_colour : (int, int, int), optional colour (RGB) of the dots (default=experiment.foreground_colour) background_colour : (int, int, int), optional colour (RGB) of the background (default=experiment.background_colour) north_up_clockwise : bool, optional if true (default) all directional information refer to an north up and clockwise system otherwise 0 is right, counterclockwise (default=True) Notes: ------ Logging is switch off per default """ if dot_speed is None: dot_speed = defaults.randomdotkinematogram_dot_speed if dot_lifetime is None: dot_lifetime = defaults.randomdotkinematogram_dot_lifetime if dot_radius is None: dot_radius = defaults.randomdotkinematogram_dot_radius if dot_colour is None: dot_colour = defaults.randomdotkinematogram_dot_colour if north_up_clockwise is None: north_up_clockwise = defaults.randomdotkinematogram_north_up_clockwise if position is None: position = defaults.randomdotkinematogram_position if background_colour is None: background_colour = defaults.randomdotkinematogram_background_colour self.area_radius = area_radius self.dot_speed = dot_speed self.dot_lifetime = dot_lifetime self.dot_radius = dot_radius self.dot_colour = dot_colour self.north_up_clockwise = north_up_clockwise self._canvas = Canvas(size=(2 * (self.area_radius + self.dot_radius), 2 * (self.area_radius + self.dot_radius)), position=position, colour=background_colour) self.reset(n_dots=n_dots, target_direction=target_direction, target_dot_ratio=target_dot_ratio) self.set_logging(False) def reset(self, n_dots, target_direction, target_dot_ratio): """Reset and recreate dot pattern Parameters: ----------- n_dots : int number of moving dots target_direction : int, float (0-360) movement target direction in degrees target_dot_ratio : float (0-1) ratio of dots that move consistently in the same target direction (the rest of the target moves in a random direction) can be sometimes only approximated! self.target_dot_ratio returns the precise actual target dot ratio """ self.target_direction = target_direction self.dots = map(lambda x: self._make_random_dot(direction=None), range(n_dots)) self.target_dot_ratio = target_dot_ratio def reset_all_ages(self, randomize_ages=False): """Reset all ages (born at current time) and randomize start age if required""" map(lambda x: x.reset_age(randomize_age=randomize_ages), self.dots) @property def n_dots(self): """Getter for n_dots.""" return len(self.dots) @property def logging(self): """Getter for logging.""" return self._canvas.logging def set_logging(self, onoff): """Set logging of this object on or off Parameters: ----------- onoff : bool set logging on (True) or off (False) """ self._canvas.set_logging(onoff) @property def n_target_dots(self): return sum(map(lambda x: int(x.is_target), self.dots)) @property def target_dot_ratio(self): """Getter for target dot ratio""" return self.n_target_dots / float(len(self.dots)) @target_dot_ratio.setter def target_dot_ratio(self, value): if value < 0: value = 0 if value > 1: value = 1 curr_n_targets = self.n_target_dots goal_n_targets = int(len(self.dots) * value) while goal_n_targets != curr_n_targets: if goal_n_targets > curr_n_targets: # remove non target and add target for d in self.dots: if not d.is_target: self.dots.remove(d) break d = self._make_random_dot(direction=self.target_direction, randomize_age=True) d.is_target = True self.dots.append(d) elif goal_n_targets < curr_n_targets: # remove a target and add non target for d in self.dots: if d.is_target: self.dots.remove(d) break self.dots.append( self._make_random_dot(direction=None, randomize_age=True)) curr_n_targets = self.n_target_dots @property def last_stimulus(self): """Getter for the last plotted stimulus""" return self._canvas def _make_random_dot(self, direction=None, randomize_age=False): """make a random dot""" while (True): pos = (int(self.area_radius - random.random() * 2 * self.area_radius), int(self.area_radius - random.random() * 2 * self.area_radius)) if math.hypot(pos[0], pos[1]) < self.area_radius: break if direction is None: direction = random.random() * 360 rtn = MovingPosition(position=pos, direction=direction, speed=self.dot_speed, lifetime=self.dot_lifetime, north_up_clockwise=self.north_up_clockwise) if randomize_age: rtn.reset_age(randomize_age=True) return rtn def make_frame(self, background_stimulus=None): """Make new frame. The function creates the current random dot kinematogram and returns it as Expyriment stimulus. Parameters: ----------- background_stimulus : Expyriment stimulus, optional optional stimulus to be plotted in the background (default=None) Notes: ------ Just loop this function and plot the returned stimulus. See present_and_wait_keyboard Returns: -------- stimulus : return the current as Expyriment stimulus """ self._canvas.clear_surface() if background_stimulus is not None: background_stimulus.plot(self._canvas) def _process_dot(d): if d.is_dead or d.is_outside(self.area_radius): if d.is_target: d = self._make_random_dot(direction=d.direction) d.is_target = True else: d = self._make_random_dot() Circle(position=d.position, radius=self.dot_radius, colour=self.dot_colour).plot(self._canvas) return d self.dots = map(_process_dot, self.dots) return self._canvas def present_and_wait_keyboard(self, background_stimulus=None, check_keys=None, change_parameter=(None, None), duration=None, button_box=None): """Present the random dot kinematogram and wait for keyboard press. Parameters: ----------- background_stimulus : Expyriment stimulus, optional optional stimulus to be plotted in the background (default=None) check_keys : int or list, optional a specific key or list of keys to check change_parameter : tuple (int, int), optional, default = (None, None) [step size (target dot ratio), step interval (ms)] if both parameter are defined (not None), target dot ratio changes accordingly while presentation duration: int, optional maximum duration to wait for keypress button_box: expyriment io.ButtonBox object, optional if not the keyboard but a button_box should be used (e.g. io.StreamingButtonBox) """ from expyriment import _active_exp RT = Clock() if button_box is None: button_box = _active_exp.keyboard button_box.clear() self.reset_all_ages(randomize_ages=True) last_change_time = RT.stopwatch_time while (True): if None not in change_parameter: if RT.stopwatch_time >= change_parameter[1] + last_change_time: last_change_time = RT.stopwatch_time self.target_dot_ratio = self.target_dot_ratio + \ change_parameter[0] self.make_frame(background_stimulus=background_stimulus).present() if isinstance(button_box, Keyboard): key = button_box.check(keys=check_keys) else: _active_exp.keyboard.process_control_keys() key = button_box.check(codes=check_keys) if key is not None: break if duration is not None and RT.stopwatch_time >= duration: return (None, None) return key, RT.stopwatch_time
class RandomDotKinematogram(Stimulus): """Random Dot Kinematogram""" def __init__( self, area_radius, n_dots, target_direction, target_dot_ratio, position=None, dot_speed=None, dot_lifetime=None, dot_radius=None, dot_colour=None, background_colour=None, north_up_clockwise=None, ): """Create a Random Dot Kinematogram Parameters: ----------- area_radius : int the radius of the stimulus area n_dots : int number of moving dots target_direction : int, float (0-360) movement target direction in degrees target_dot_ratio : float (0-1) ratio of dots that move consistently in the same target direction (the rest of the target moves in a random direction) can be sometimes only approximated! self.target_dot_ratio returns the precise actual target dot ratio position : (int, int), optional position of the stimulus dot_speed : int, optional the moving speed in pixel per second (default=100) dot_lifetime : int, optional the time the object lives in milliseconds (default 400) dot_radius : int, optional radius of the dots (default = 3) dot_colour : (int, int, int), optional colour (RGB) of the dots (default=experiment.foreground_colour) background_colour : (int, int, int), optional colour (RGB) of the background (default=experiment.background_colour) north_up_clockwise : bool, optional if true (default) all directional information refer to an north up and clockwise system otherwise 0 is right, counterclockwise (default=True) Notes: ------ Logging is switch off per default """ if dot_speed is None: dot_speed = defaults.randomdotkinematogram_dot_speed if dot_lifetime is None: dot_lifetime = defaults.randomdotkinematogram_dot_lifetime if dot_radius is None: dot_radius = defaults.randomdotkinematogram_dot_radius if dot_colour is None: dot_colour = defaults.randomdotkinematogram_dot_colour if north_up_clockwise is None: north_up_clockwise = defaults.randomdotkinematogram_north_up_clockwise if position is None: position = defaults.randomdotkinematogram_position if background_colour is None: background_colour = defaults.randomdotkinematogram_background_colour self.area_radius = area_radius self.dot_speed = dot_speed self.dot_lifetime = dot_lifetime self.dot_radius = dot_radius self.dot_colour = dot_colour self.north_up_clockwise = north_up_clockwise self._canvas = Canvas( size=(2 * (self.area_radius + self.dot_radius), 2 * (self.area_radius + self.dot_radius)), position=position, colour=background_colour, ) self.reset(n_dots=n_dots, target_direction=target_direction, target_dot_ratio=target_dot_ratio) self.set_logging(False) def reset(self, n_dots, target_direction, target_dot_ratio): """Reset and recreate dot pattern Parameters: ----------- n_dots : int number of moving dots target_direction : int, float (0-360) movement target direction in degrees target_dot_ratio : float (0-1) ratio of dots that move consistently in the same target direction (the rest of the target moves in a random direction) can be sometimes only approximated! self.target_dot_ratio returns the precise actual target dot ratio """ self.target_direction = target_direction self.dots = map(lambda x: self._make_random_dot(direction=None), range(n_dots)) self.target_dot_ratio = target_dot_ratio def reset_all_ages(self, randomize_ages=False): """Reset all ages (born at current time) and randomize start age if required""" map(lambda x: x.reset_age(randomize_age=randomize_ages), self.dots) @property def n_dots(self): """Getter for n_dots.""" return len(self.dots) @property def logging(self): """Getter for logging.""" return self._canvas.logging def set_logging(self, onoff): """Set logging of this object on or off Parameters: ----------- onoff : bool set logging on (True) or off (False) """ self._canvas.set_logging(onoff) @property def n_target_dots(self): return sum(map(lambda x: int(x.is_target), self.dots)) @property def target_dot_ratio(self): """Getter for target dot ratio""" return self.n_target_dots / float(len(self.dots)) @target_dot_ratio.setter def target_dot_ratio(self, value): if value < 0: value = 0 if value > 1: value = 1 curr_n_targets = self.n_target_dots goal_n_targets = int(len(self.dots) * value) while goal_n_targets != curr_n_targets: if goal_n_targets > curr_n_targets: # remove non target and add target for d in self.dots: if not d.is_target: self.dots.remove(d) break d = self._make_random_dot(direction=self.target_direction, randomize_age=True) d.is_target = True self.dots.append(d) elif goal_n_targets < curr_n_targets: # remove a target and add non target for d in self.dots: if d.is_target: self.dots.remove(d) break self.dots.append(self._make_random_dot(direction=None, randomize_age=True)) curr_n_targets = self.n_target_dots @property def last_stimulus(self): """Getter for the last plotted stimulus""" return self._canvas def _make_random_dot(self, direction=None, randomize_age=False): """make a random dot""" while True: pos = ( int(self.area_radius - random.random() * 2 * self.area_radius), int(self.area_radius - random.random() * 2 * self.area_radius), ) if math.hypot(pos[0], pos[1]) < self.area_radius: break if direction is None: direction = random.random() * 360 rtn = MovingPosition( position=pos, direction=direction, speed=self.dot_speed, lifetime=self.dot_lifetime, north_up_clockwise=self.north_up_clockwise, ) if randomize_age: rtn.reset_age(randomize_age=True) return rtn def make_frame(self, background_stimulus=None): """Make new frame. The function creates the current random dot kinematogram and returns it as Expyriment stimulus. Parameters: ----------- background_stimulus : Expyriment stimulus, optional optional stimulus to be plotted in the background (default=None) Notes: ------ Just loop this function and plot the returned stimulus. See present_and_wait_keyboard Returns: -------- stimulus : return the current as Expyriment stimulus """ self._canvas.clear_surface() if background_stimulus is not None: background_stimulus.plot(self._canvas) def _process_dot(d): if d.is_dead or d.is_outside(self.area_radius): if d.is_target: d = self._make_random_dot(direction=d.direction) d.is_target = True else: d = self._make_random_dot() Circle(position=d.position, radius=self.dot_radius, colour=self.dot_colour).plot(self._canvas) return d self.dots = map(_process_dot, self.dots) return self._canvas def present_and_wait_keyboard( self, background_stimulus=None, check_keys=None, change_parameter=(None, None), duration=None, button_box=None ): """Present the random dot kinematogram and wait for keyboard press. Parameters: ----------- background_stimulus : Expyriment stimulus, optional optional stimulus to be plotted in the background (default=None) check_keys : int or list, optional a specific key or list of keys to check change_parameter : tuple (int, int), optional, default = (None, None) [step size (target dot ratio), step interval (ms)] if both parameter are defined (not None), target dot ratio changes accordingly while presentation duration: int, optional maximum duration to wait for keypress button_box: expyriment io.ButtonBox object, optional if not the keyboard but a button_box should be used (e.g. io.StreamingButtonBox) """ from expyriment import _active_exp RT = Clock() if button_box is None: button_box = _active_exp.keyboard button_box.clear() self.reset_all_ages(randomize_ages=True) last_change_time = RT.stopwatch_time while True: if None not in change_parameter: if RT.stopwatch_time >= change_parameter[1] + last_change_time: last_change_time = RT.stopwatch_time self.target_dot_ratio = self.target_dot_ratio + change_parameter[0] self.make_frame(background_stimulus=background_stimulus).present() if isinstance(button_box, Keyboard): key = button_box.check(keys=check_keys) else: _active_exp.keyboard.process_control_keys() key = button_box.check(codes=check_keys) if key is not None: break if duration is not None and RT.stopwatch_time >= duration: return (None, None) return key, RT.stopwatch_time