Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
    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
Ejemplo n.º 4
0
 def scramble(self, grain_size):
     self.unlock_pixel_array()
     return Canvas.scramble(self, grain_size)
Ejemplo n.º 5
0
 def flip(self, booleans):
     self.unlock_pixel_array()
     return Canvas.flip(self, booleans)
Ejemplo n.º 6
0
 def rotate(self, degree):
     self.unlock_pixel_array()
     return Canvas.rotate(self, degree)
Ejemplo n.º 7
0
 def copy(self):
     self.unlock_pixel_array()
     return Canvas.copy(self)
Ejemplo n.º 8
0
 def plot(self, stimulus):
     self.unlock_pixel_array()
     return Canvas.plot(self, stimulus)
Ejemplo n.º 9
0
 def preload(self, inhibit_ogl_compress=False):
     self.unlock_pixel_array()
     return Canvas.preload(self, inhibit_ogl_compress)
Ejemplo n.º 10
0
 def plot(self, stimulus):
     self.unlock_pixel_array()
     return Canvas.plot(self, stimulus)
Ejemplo n.º 11
0
 def decompress(self):
     self.unlock_pixel_array()
     return Canvas.decompress(self)
Ejemplo n.º 12
0
 def preload(self, inhibit_ogl_compress=False):
     self.unlock_pixel_array()
     return Canvas.preload(self, inhibit_ogl_compress)
    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
Ejemplo n.º 15
0
 def rotate(self, degree, filter=True):  # Exypriment >=.9
     self.unlock_pixel_array()
     return Canvas.rotate(self, degree, filter)
Ejemplo n.º 16
0
 def clear_surface(self):
     self.unlock_pixel_array()
     return Canvas.clear_surface(self)
Ejemplo n.º 17
0
    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)
Ejemplo n.º 18
0
 def copy(self):
     self.unlock_pixel_array()
     return Canvas.copy(self)
Ejemplo n.º 19
0
 def __init__(self, size, position=None, colour=None):
     Canvas.__init__(self, size, position, colour)
     self._px_array = None
Ejemplo n.º 20
0
 def unload(self, keep_surface=False):
     if not keep_surface:
         self.unlock_pixel_array()
     return Canvas.unload(self, keep_surface)
Ejemplo n.º 21
0
 def decompress(self):
     self.unlock_pixel_array()
     return Canvas.decompress(self)
Ejemplo n.º 22
0
 def rotate(self, degree):
     self.unlock_pixel_array()
     return Canvas.rotate(self, degree)
Ejemplo n.º 23
0
 def clear_surface(self):
     self.unlock_pixel_array()
     return Canvas.clear_surface(self)
Ejemplo n.º 24
0
 def scale(self, factors):
     self.unlock_pixel_array()
     return Canvas.scale(self, factors)
Ejemplo n.º 25
0
 def unload(self, keep_surface=False):
     if not keep_surface:
         self.unlock_pixel_array()
     return Canvas.unload(self, keep_surface)
Ejemplo n.º 26
0
 def flip(self, booleans):
     self.unlock_pixel_array()
     return Canvas.flip(self, booleans)
Ejemplo n.º 27
0
 def scale(self, factors):
     self.unlock_pixel_array()
     return Canvas.scale(self, factors)
Ejemplo n.º 28
0
 def blur(self, level):
     self.unlock_pixel_array()
     return Canvas.blur(self, level)
Ejemplo n.º 29
0
 def blur(self, level):
     self.unlock_pixel_array()
     return Canvas.blur(self, level)
Ejemplo n.º 30
0
 def scramble(self, grain_size):
     self.unlock_pixel_array()
     return Canvas.scramble(self, grain_size)
Ejemplo n.º 31
0
 def add_noise(self, grain_size, percentage, colour):
     self.unlock_pixel_array()
     return Canvas.add_noise(self, grain_size, percentage, colour)
Ejemplo n.º 32
0
 def add_noise(self, grain_size, percentage, colour):
     self.unlock_pixel_array()
     return Canvas.add_noise(self, grain_size, percentage, colour)
Ejemplo n.º 33
0
 def __init__(self, size, position=None, colour=None):
     Canvas.__init__(self, size, position, colour)
     self._px_array = None
Ejemplo n.º 34
0
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