def level_indicator(value, text, scaling, width=20, text_size=14, text_gap=20, position=(0, 0), thresholds=None, colour=constants.C_EXPYRIMENT_ORANGE): """make an level indicator in for of an Expyriment stimulus text_gap: gap between indicator and text scaling: Scaling object Returns -------- expyriment.Canvas """ value = scaling.trim(value) # indicator height = scaling.pixel_max - scaling.pixel_min indicator = Canvas(size=[width + 2, height + 2], colour=(30, 30, 30)) zero = scaling.data2pixel(0) px_bar_height = scaling.data2pixel(value) - zero bar = Rectangle(size=(width, abs(px_bar_height)), position=(0, zero + int((px_bar_height + 1) / 2)), colour=colour) bar.plot(indicator) # levels & horizontal lines try: px_horizontal_lines = scaling.data2pixel( values=np.array(thresholds.thresholds)) except: px_horizontal_lines = None if px_horizontal_lines is not None: for px in px_horizontal_lines: level = Rectangle(size=(width + 6, 2), position=(0, px), colour=constants.C_WHITE) level.plot(indicator) # text labels txt = TextLine(text=text, text_size=text_size, position=(0, -1 * (int(height / 2.0) + text_gap)), text_colour=constants.C_YELLOW) # make return canvas w = max(txt.surface_size[0], indicator.size[0]) h = height + 2 * (txt.surface_size[1]) + text_gap rtn = Canvas(size=(w, h), colour=(0, 0, 0), position=position) indicator.plot(rtn) txt.plot(rtn) return rtn
def level_indicator(value, text, scaling, width=20, text_size=14, text_gap=20, position=(0,0), thresholds = None, colour=constants.C_EXPYRIMENT_ORANGE): """make an level indicator in for of an Expyriment stimulus text_gap: gap between indicator and text scaling: Scaling object Returns -------- expyriment.Canvas """ value = scaling.trim(value) # indicator height = scaling.pixel_max - scaling.pixel_min indicator = Canvas(size=[width + 2, height + 2], colour=(30, 30, 30)) zero = scaling.data2pixel(0) px_bar_height = scaling.data2pixel(value) - zero bar = Rectangle(size=(width, abs(px_bar_height)), position=(0, zero + int((px_bar_height + 1) / 2)), colour=colour) bar.plot(indicator) # levels & horizontal lines try: px_horizontal_lines = scaling.data2pixel(values=np.array(thresholds.thresholds)) except: px_horizontal_lines = None if px_horizontal_lines is not None: for px in px_horizontal_lines: level = Rectangle(size=(width+6, 2), position=(0, px), colour=constants.C_WHITE) level.plot(indicator) # text labels txt = TextLine(text=text, text_size=text_size, position=(0, -1 * (int(height / 2.0) + text_gap)), text_colour=constants.C_YELLOW) # make return canvas w = max(txt.surface_size[0], indicator.size[0]) h = height + 2 * (txt.surface_size[1]) + text_gap rtn = Canvas(size=(w, h), colour=(0, 0, 0), position=position) indicator.plot(rtn) txt.plot(rtn) return rtn
def __init__(self, dot_array, position=(0, 0), colours = _colour.ImageColours(), antialiasing=True): if not isinstance(colours, _colour.ImageColours): raise ValueError("Colours must be a ImageColours instance") _Canvas.__init__(self, size=(0, 0), position=position) self.dot_array = dot_array self.colours = colours self.antialiasing = antialiasing self._image = None
def scramble(self, grain_size): self.unlock_pixel_array() return Canvas.scramble(self, grain_size)
def flip(self, booleans): self.unlock_pixel_array() return Canvas.flip(self, booleans)
def rotate(self, degree): self.unlock_pixel_array() return Canvas.rotate(self, degree)
def copy(self): self.unlock_pixel_array() return Canvas.copy(self)
def plot(self, stimulus): self.unlock_pixel_array() return Canvas.plot(self, stimulus)
def preload(self, inhibit_ogl_compress=False): self.unlock_pixel_array() return Canvas.preload(self, inhibit_ogl_compress)
def decompress(self): self.unlock_pixel_array() return Canvas.decompress(self)
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)
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
def rotate(self, degree, filter=True): # Exypriment >=.9 self.unlock_pixel_array() return Canvas.rotate(self, degree, filter)
def clear_surface(self): self.unlock_pixel_array() return Canvas.clear_surface(self)
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 __init__(self, size, position=None, colour=None): Canvas.__init__(self, size, position, colour) self._px_array = None
def unload(self, keep_surface=False): if not keep_surface: self.unlock_pixel_array() return Canvas.unload(self, keep_surface)
def scale(self, factors): self.unlock_pixel_array() return Canvas.scale(self, factors)
def blur(self, level): self.unlock_pixel_array() return Canvas.blur(self, level)
def add_noise(self, grain_size, percentage, colour): self.unlock_pixel_array() return Canvas.add_noise(self, grain_size, percentage, colour)
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