Пример #1
0
def _get_feedback_rect_colors(exp_info):

    colors_param = exp_info.config.feedback_btn_colours

    if colors_param is None:
        if exp_info.config.feedback_place == 'button':
            return xpy.misc.constants.C_GREEN, xpy.misc.constants.C_GREEN

        elif exp_info.config.feedback_place == 'middle':
            return xpy.misc.constants.C_GREEN, xpy.misc.constants.C_RED

        else:
            raise ttrk.ValueError(
                'Unsupported config.feedback_place ({:})'.format(
                    exp_info.config.feedback_place))

    if u.is_rgb(colors_param):
        return colors_param, colors_param

    if u.is_collection(colors_param) and u.is_rgb(
            colors_param[0]) and u.is_rgb(colors_param[1]):
        return tuple(colors_param)

    else:
        raise ttrk.ValueError(
            'Invalid config.feedback_btn_colours ({:}) - expecting a color (Red,Green,Blue) or an array.tuple with two colors'
            .format(colors_param))
Пример #2
0
def _get_feedback_rect_sizes(exp_info):

    size_param = exp_info.config.feedback_rect_size

    #-- If size was explicitly provided: use it
    if size_param is not None and not isinstance(size_param, numbers.Number):

        try:
            #-- If "feedback_rect_size" is a pair of numbers - just translate it to pixels
            single_size = common.xy_to_pixels(size_param, exp_info.screen_size,
                                              'config.feedback_rect_size')
            return single_size, single_size

        except ttrk.TypeError:
            pass

        # -- The "feedback_rect_size" argument is NOT a pair of numbers.
        # -- So we expect it to be an array with two sets of coordinates.

        common.validate_config_param_type("feedback_rect_size",
                                          (list, tuple, np.ndarray),
                                          size_param,
                                          type_name="array/list/tuple")
        result = [
            common.xy_to_pixels(s, exp_info.screen_size,
                                'config.feedback_rect_size')
            for s in size_param
        ]
        if len(size_param) != 2 or result[0] is None or result[1] is None:
            raise ttrk.ValueError(
                'Invalid config.feedback_rect_size: expecting either a size or a pair of sizes'
            )

        return result

    #-- feedback_rect_size was not specified explicitly: set it to default values

    if exp_info.config.feedback_place == 'button':

        #-- The feedback area overlaps with the buttons
        return exp_info.response_button_size, exp_info.response_button_size

    elif exp_info.config.feedback_place == 'middle':

        #-- The feedback area is between the buttons

        width = exp_info.screen_size[0] - 2 * exp_info.response_button_size[0]
        if isinstance(size_param, int):
            height = size_param
        elif isinstance(size_param, numbers.Number):
            height = int(np.round(size_param * exp_info.screen_size[1]))
        else:
            height = int(np.round(exp_info.response_button_size[1] / 4))

        return (width, height), (width, height)

    else:
        raise ttrk.ValueError("Unsupported feedback_place({:})".format(
            exp_info.config.feedback_place))
Пример #3
0
    def _validate_property(self, prop_name, n_stim=0):

        value = getattr(self, prop_name)
        if value is None:
            raise ttrk.ValueError('{:}.{:} was not set'.format(_u.get_type_name(self), prop_name))

        is_multiple_values = getattr(self, "_" + prop_name + "_multiple")
        if is_multiple_values and len(value) < n_stim:
            raise ttrk.ValueError('{:}.{:} has {:} values, but there are {:} values to present'.format(
                _u.get_type_name(self), prop_name, len(value), n_stim))
Пример #4
0
    def animated_object(self, obj):
        if "present" not in dir(obj):
            raise trajtracker.ValueError(
                "{:}.animated_object must be an object with a present() method"
                .format(_u.get_type_name(self)))
        if "position" not in dir(obj):
            raise trajtracker.ValueError(
                "{:}.animated_object must be an object with a 'position' property"
                .format(_u.get_type_name(self)))

        self._animated_object = obj
        self._log_property_changed("animated_object")
Пример #5
0
def _get_feedback_stim_positions(exp_info, sizes=None):

    if sizes is None:
        sizes = [s.size for s in exp_info.feedback_stimuli]
    pos_param = exp_info.config.feedback_stim_position

    #-- If position was explicitly provided: use it
    if pos_param is not None:

        common.validate_config_param_type("feedback_stim_position",
                                          (list, tuple, np.ndarray),
                                          pos_param,
                                          type_name="array/list/tuple")
        if u.is_coord(pos_param):
            #-- One position given: use for both feedback areas
            pos_param = tuple(pos_param)
            return pos_param, pos_param

        elif len(pos_param) == 2 and u.is_coord(pos_param[0]) and u.is_coord(
                pos_param[1]):
            return tuple(pos_param)

        else:
            raise ttrk.ValueError(
                "Invalid config.feedback_stim_position: expecting (x,y) or [(x1,y1), (x2,y2)]"
            )

    #-- Position was not explicitly provided: use default position

    scr_width, scr_height = exp_info.screen_size

    if exp_info.config.feedback_place == 'button':

        #-- Position is top screen corners
        x1 = -(scr_width / 2 - sizes[0][0] / 2)
        y1 = scr_height / 2 - sizes[0][1] / 2
        x2 = (scr_width / 2 - sizes[1][0] / 2)
        y2 = scr_height / 2 - sizes[1][1] / 2

        return (x1, y1), (x2, y2)

    elif exp_info.config.feedback_place == 'middle':

        #-- Position is top-middle of screen
        y1 = scr_height / 2 - sizes[0][1] / 2
        y2 = scr_height / 2 - sizes[1][1] / 2
        return (0, y1), (0, y2)

    else:
        raise ttrk.ValueError("Unsupported config.feedback_place ({:})".format(
            config.feedback_place))
Пример #6
0
def coord_to_pixels(coord, col_name, filename, is_x):
    """
    Convert screen coordinates, which were provided as percentage of the screen width/height,
    into pixels.

    :param coord: The coordinate, on a 0-1 scale; or a list of coordinates
    :param col_name: The column name in the CSV file (just for logging)
    :param filename: CSV file name (just for logging)
    :param is_x: (bool) whether it's an x or y coordinate
    :return: coordinate as pixels (int); or a list of coords
    """

    screen_size = ttrk.env.screen_size[0 if is_x else 1]

    coord_list = coord if isinstance(coord, list) else [coord]
    # noinspection PyTypeChecker
    if sum([not (-1 <= c <= 1) for c in coord_list]) > 0:
        # Some coordinates are way off the screen bounds
        raise ttrk.ValueError("Invalid {:} in the CSV file {:} ({:}): when position is specified as %, its values must be between -1 and 1".
                              format(col_name, filename, coord_list))

    if isinstance(coord, list):
        return [int(np.round(c * screen_size)) for c in coord]
    else:
        return int(np.round(coord * screen_size))
Пример #7
0
    def init_output_file(self,
                         filename=None,
                         xy_precision=5,
                         time_precision=3):
        """
        Initialize a new CSV output file for saving the results

        :param filename: Full path
        :param xy_precision: Precision of x,y coordinates (default: 5)
        :param time_precision: Precision of time (default: 3)
        """
        if filename is not None:
            self._filename = filename

        if self._filename is None:
            raise ttrk.ValueError(
                "filename was not provided to {:}.init_output_file()".format(
                    _u.get_type_name(self)))

        self._xy_precision = xy_precision
        self._time_precision = time_precision

        fh = self._open_file(self._filename, 'w')
        fh.write('trial,time,x,y\n')
        fh.close()

        self._out_file_initialized = True

        self._log_write_if(ttrk.log_debug,
                           "Initializing output file %s" % self._filename,
                           True)
Пример #8
0
def create_confidence_slider(exp_info):
    """
    Create a :class:`~trajtracker.stimuli.Slider` for measuring subjective confidence rating
    
    :param exp_info: The experiment-level objects
    :type exp_info: trajtracker.paradigms.num2pos.ExperimentInfo
    """
    config = exp_info.config

    y = config.confidence_slider_y
    validate_config_param_type("confidence_slider_y", numbers.Number, y)
    y = xy_to_pixels(y, exp_info.screen_size[1], 'config.confidence_slider_y')
    if y is None:
        raise ttrk.ValueError('Invalid config.confidence_slider_y ({:}): '.format(config.confidence_slider_y) +
                              'expecting either an integer or ratio of screen height (between -0.5 and 0.5)')

    validate_config_param_type("confidence_rating", bool, config.confidence_rating)
    if not exp_info.config.confidence_rating:
        return

    #-- Create the confidence slider
    slider_bgnd = _create_confidence_slider_background(exp_info)
    gauge = _create_slider_gauge(slider_bgnd.surface_size[0])
    slider = ttrk.stimuli.Slider(slider_bgnd, gauge, orientation=ttrk.stimuli.Orientation.Vertical,
                                 min_value=0, max_value=100, position=(0, y))

    exp_info.stimuli.add(slider.stimulus, "confidence_slider", visible=False)
    exp_info.confidence_slider = slider

    exp_info.exported_trial_result_fields['confidence'] = ""
    exp_info.exported_trial_result_fields['confidence_n_moves'] = 0
Пример #9
0
    def _set_dot_position_for_time(self, time):

        if time < 0 or time > 10000:
            raise ttrk.ValueError(
                '{:}._set_dot_position_for_time(): invalid "time" argument ({:})'
                .format(_u.get_type_name(self), time))

        remaining_time_ratio = 1 - (min(time, self._zoom_duration) /
                                    self._zoom_duration)

        dx = int(np.round(self._box_size[0] / 2 * remaining_time_ratio))
        dy = int(np.round(self._box_size[1] / 2 * remaining_time_ratio))

        self._dots[0].position = self.position[0] - dx, self.position[
            1] - dy  # top-left
        self._dots[1].position = self.position[0] + dx, self.position[
            1] - dy  # top-right
        self._dots[2].position = self.position[0] - dx, self.position[
            1] + dy  # bottom-left
        self._dots[3].position = self.position[0] + dx, self.position[
            1] + dy  # bottom-right

        if self._should_log(ttrk.log_trace):
            self._log_write(
                "Set dots location, remaining time = {:.0f}%".format(
                    remaining_time_ratio * 100), True)
Пример #10
0
def get_feedback_stim_num(exp_info, trial, user_response):
    """
    Return the number of the feedback stimulus to show (0 or 1)  

    :param exp_info:
    :type exp_info: trajtracker.paradigms.dchoice.ExperimentInfo
    :param trial:
    :type trial: trajtracker.paradigms.dchoice.TrialInfo
    :param user_response: The button selected by the user (0=left, 1=right) 
    """

    selectby = exp_info.config.feedback_select_by

    if selectby == 'accuracy':
        correct = trial.expected_response == user_response
        return 1 - correct

    elif selectby == 'response':
        return user_response

    elif selectby == 'expected':
        return trial.expected_response

    else:
        raise ttrk.ValueError(
            "Unsupported config.feedback_select_by ({:})".format(selectby))
Пример #11
0
    def get_color_at(self, x_coord, y_coord, use_mapping=None):
        """
        Return the color at a given coordinate

        :param x_coord:
        :param y_coord:
        :param use_mapping:
        :return: The color in the given place, or None if the coordinate is out of the image range
        """

        _u.validate_func_arg_type(self, "get_color_at", "x_coord", x_coord, int)
        _u.validate_func_arg_type(self, "get_color_at", "y_coord", y_coord, int)
        _u.validate_func_arg_type(self, "get_color_at", "use_mapping", use_mapping, numbers.Number, none_allowed=True)

        if use_mapping is None:
            use_mapping = self._use_mapping

        if self._color_to_code is None and use_mapping:
            raise trajtracker.ValueError("a call to %s.get_color_at(use_mapping=True) is invalid because color_codes were not specified" % self.__class__)

        if x_coord < self._top_left_x or x_coord >= self._top_left_x+self._width or \
               y_coord < self._top_left_y or y_coord >= self._top_left_y + self._height:
            return None

        x_coord -= self._top_left_x
        y_coord -= self._top_left_y
        #y_coord = len(self._image) - 1 - y_coord   # reverse up/down

        v = self._image[-(y_coord+1)][x_coord]

        return self._color_to_code[v] if use_mapping else v
Пример #12
0
def create_fixation_zoom(exp_info):
    """
    Create a :class:`~trajtracker.stimuli.FixationZoom` fixation 

    :param exp_info: The experiment-level objects
    :type exp_info: trajtracker.paradigms.num2pos.ExperimentInfo
    """

    config = exp_info.config

    y, height = exp_info.get_default_target_y()

    if config.fixzoom_start_zoom_event is None:
        start_zoom_event = ttrk.events.TRIAL_STARTED + 0.2
    elif config.fixzoom_start_zoom_event == ttrk.events.TRIAL_STARTED or config.fixzoom_start_zoom_event == ttrk.events.TRIAL_INITIALIZED:
        raise ttrk.ValueError(('config.fixzoom_start_zoom_event was set to an invalid value ({:}): ' +
                              'it can only be set to times AFTER the trial has already started (e.g., ttrk.events.TRIAL_STARTED + 0.1)').
                              format(config.fixzoom_start_zoom_event))
    else:
        start_zoom_event = config.fixzoom_start_zoom_event

    fixation = ttrk.stimuli.FixationZoom(
        position=(config.text_target_x_coord, y),
        box_size=config.fixzoom_box_size,
        dot_radius=config.fixzoom_dot_radius,
        dot_colour=config.fixzoom_dot_colour,
        zoom_duration=config.fixzoom_zoom_duration,
        stay_duration=config.fixzoom_stay_duration,
        show_event=config.fixzoom_show_event,
        start_zoom_event=start_zoom_event)

    exp_info.fixation = fixation
    exp_info.add_event_sensitive_object(exp_info.fixation)
Пример #13
0
    def axis(self, value):
        _u.validate_attr_type(self, "axis", value, ValidationAxis)
        if value == ValidationAxis.xy:
            raise trajtracker.ValueError(
                _u.ErrMsg.attr_invalid_value(self.__class__, "axis", value))

        self._axis = value
        self._log_property_changed("axis")
Пример #14
0
def _get_response_buttons_positions(exp_info, button_size):

    config = exp_info.config

    position = config.resp_btn_positions

    if position is None:
        #-- Set default position: top-left and top-right of screen
        max_x = int(exp_info.screen_size[0] / 2)
        max_y = int(exp_info.screen_size[1] / 2)
        x = max_x - int(button_size[0] / 2)
        y = max_y - int(button_size[1] / 2)

        return (-x, y), (x, y)

    if u.is_coord(position, allow_float=True):
        #-- One pair of coords given: this is for the left side
        pos_left = -position[0], position[1]
        position = pos_left, position

    elif not trajtracker.utils.is_collection(position) or len(position) != 2 or \
            not u.is_coord(position[0], allow_float=True) or not u.is_coord(position[1], allow_float=True):
        raise ttrk.ValueError(
            "config.resp_btn_positions should be either (x,y) coordinates " +
            "or a pair of coordinates [(xleft, yleft), (xright, yright)]. " +
            "The value provided is invalid: {:}".format(position))

    #-- If x/y are between [-1,1], they mean percentage of screen size
    result = []
    screen_width, screen_height = exp_info.screen_size
    for x, y in position:
        if -1 < x < 1:
            x = int(x * screen_width)
        elif not isinstance(x, int):
            raise ttrk.ValueError(
                "Invalid config.resp_btn_positions: a non-integer x was provided ({:})"
                .format(x))
        if -1 < y < 1:
            y = int(y * screen_height)
        elif not isinstance(y, int):
            raise ttrk.ValueError(
                "Invalid config.resp_btn_positions: a non-integer y was provided ({:})"
                .format(y))
        result.append((x, y))

    return result
Пример #15
0
    def trajectory_generator(self, obj):

        if "get_traj_point" not in dir(obj):
            raise trajtracker.ValueError(
                "{:}.trajectory_generator must be an object with a get_traj_point() method"
                .format(_u.get_type_name(self)))

        self._trajectory_generator = obj
Пример #16
0
    def parse(text):
        """
        Parse a string into an event object. "None" is acceptable.
        """
        if not isinstance(text, str):
            raise trajtracker.ValueError("invalid event format ({:}) - expecting a string value".format(text))

        if re.match('^\s*none\s*$', text.lower()):
            return None

        m = re.match('^\s*(\w+)\s*(\+\s*((\d+)|(\d*.\d+)))?\s*$', text)
        if m is None:
            raise trajtracker.ValueError("invalid event format ({:}) - expecting event_id or event_id+offset".format(text))

        event = Event(m.group(1))
        if m.group(2) is not None:
            event += float(m.group(3))

        return event
Пример #17
0
def parse_text_justification(s):
    s = s.lower()

    if s == "left":
        return 0
    elif s == "center":
        return 1
    elif s == "right":
        return 2
    else:
        raise ttrk.ValueError("Invalid text justification ({:})".format(s))
Пример #18
0
def parse_size(s):
    orig_s = s

    if re.match("^\((.*)\)$", s) is not None:
        s = s[1:-1]

    m = re.match("\s*(\d+)\s*:\s*(\d+)\s*", s)
    if m is None:
        raise ttrk.ValueError("Invalid format for size: {:}".format(orig_s))

    return m.group(1), m.group(2)
Пример #19
0
    def __add__(self, rhs):
        """Define a new event, in a time offset relatively to an existing event"""

        _u.validate_func_arg_type(self, "+", "right operand", rhs, numbers.Number)
        if rhs < 0:
            raise trajtracker.ValueError("Invalid offset ({:}) for event {:}. Only events with positive offset are acceptable".format(
                rhs, self._event_id))

        new_event = Event(self._event_id)
        new_event._offset = self._offset + rhs
        return new_event
Пример #20
0
    def get_response_buttons_size(self):

        width, height = validate_config_param_type("resp_btn_size",
                                                   ttrk.TYPE_SIZE,
                                                   self.config.resp_btn_size)

        # -- If width/height are between [-1,1], they mean percentage of screen size
        if -1 < width < 1:
            width = int(width * self.screen_size[0])
        elif not isinstance(width, int):
            raise ttrk.ValueError(
                "Invalid config.resp_btn_size: a non-integer width was provided ({:})"
                .format(width))
        if -1 < height < 1:
            height = int(height * self.screen_size[1])
        elif not isinstance(height, int):
            raise ttrk.ValueError(
                "Invalid config.resp_btn_size: a non-integer height was provided ({:})"
                .format(height))

        return width, height
Пример #21
0
def parse_rgb(s):
    orig_s = s

    if re.match("^\((.*)\)$", s) is not None:
        s = s[1:-1]

    m = re.match('\s*(\d+)\s*:\s*(\d+)\s*:\s*(\d+)\s*', s)

    if m is None:
        raise ttrk.ValueError("Invalid RGB format: {:}".format(orig_s))

    return m.group(1), m.group(2), m.group(3)
Пример #22
0
    def colormap(self, value):

        if value is None:
            #-- No mapping: use default colors
            self._color_to_code = None

        elif isinstance(value, str) and value.lower() == "default":
            #-- Use arbitrary coding
            self._color_to_code = {}
            n = 0
            for color in sorted(list(self._available_colors)):
                self._color_to_code[color] = n
                n += 1

        elif isinstance(value, str) and value.lower() == "rgb":
            # Translate each triplet to an RGB code
            self._color_to_code = { color: color_rgb_to_num(color) for color in self._available_colors }

        elif isinstance(value, dict):
            #-- Use this mapping; but make sure that all colors from the image were defined
            missing_colors = set()
            for color in self._available_colors:
                if color not in value:
                    missing_colors.add(color)
            if len(missing_colors) > 0:
                raise trajtracker.ValueError("Invalid value for {:}.color_codes - some colors are missing: {:}".format(
                    _u.get_type_name(self), missing_colors))

            self._color_to_code = value.copy()

        elif isinstance(value, type(lambda:1)):
            #-- A function that maps each color to a code
            self._color_to_code = { color: value(color) for color in self._available_colors }

        else:
            raise trajtracker.ValueError(
                "{:}.color_codes can only be set to None, 'default', or a dict. Invalid value: {:}".format(
                    _u.get_type_name(self), value))

        self._log_property_changed("colormap", value)
Пример #23
0
    def activate(self, key):
        """
        Set one of the stimuli as the active one.

        :param key: The key of the stimulus, as set in :func:`~trajtracker.stimuli.ChangingStimulus.add_stimulus`
        """
        if key is None or key in self._stimuli:
            if self._should_log(ttrk.log_trace):
                self._log_write("Activate,{:}".format(key), True)
            self._active_key = key
        else:
            raise ttrk.ValueError(
                "{:}.select(key={:}) - this stimulus was not defined".format(
                    _u.get_type_name(self), key))
Пример #24
0
def parse_rgb(value):

    if isinstance(value, tuple):
        return value

    if not isinstance(value, str):
        raise ttrk.TypeError('Invalid RGB "{:}" - expecting a string'.format(value))

    m = re.match('^\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)\s*$', value)

    if m is None:
        raise ttrk.ValueError('Invalid RGB "{:}"'.format(value))

    return int(m.group(1)), int(m.group(2)), int(m.group(3))
Пример #25
0
    def exit_area(self, value):
        if value is None:
            self._exit_area = None
        elif isinstance(value, str):
            self._exit_area = self._create_default_exit_area(value)
            self._log_property_changed("exit_area", value=value)
        elif "overlapping_with_position" in dir(value):
            self._exit_area = value
            self._log_property_changed("exit_area", value="shape")
        else:
            raise ttrk.ValueError("invalid value for %s.exit_area" %
                                  _u.get_type_name(self))

        self._log_property_changed("exit_area")
Пример #26
0
def load_sound(config, filename):
    """
    Load a sound file
    
    :param config: The experiment configuration object 
    :param filename: No path needed. The file is expected to be under config.sounds_dir 
    """
    full_path = config.sounds_dir + "/" + filename
    if not os.path.isfile(full_path):
        raise ttrk.ValueError('Sound file {:} does not exist. Please check the file name (or perhaps you need to change config.sounds_dir)'.
                              format(full_path))
    sound = xpy.stimuli.Audio(full_path)
    sound.preload()
    return sound
Пример #27
0
def _get_response_buttons_colors(exp_info):

    color = exp_info.config.resp_btn_colours

    if u.is_rgb(color):
        #-- One color given
        return color, color

    if not trajtracker.utils.is_collection(color) or len(color) != 2 or \
            not u.is_rgb(color[0]) or not u.is_rgb(color[1]):
        raise ttrk.ValueError(
            "config.resp_btn_colours should be either a color (R,G,B) " +
            "or a pair of colors (left_rgb, right_rgb). " +
            "The value provided is invalid: {:}".format(color))

    return color
Пример #28
0
    def _validate(self):

        self._validate_property("shown_stimuli")

        missing_pics = [
            s_id for s_id in self._shown_stimuli
            if s_id not in self._available_stimuli
        ]
        if len(missing_pics) > 0:
            raise ttrk.ValueError(
                "shown_stimuli includes unknown stimulus IDs: {:}".format(
                    ", ".join(missing_pics)))

        n_stim = len(self._shown_stimuli)
        self._validate_property("position", n_stim)
        self._validate_property("onset_time", n_stim)
        self._validate_property("duration", n_stim)
Пример #29
0
    def movement_started(self, time):
        """
        Called when the finger/mouse starts moving
        """

        self._log_func_enters("finger_started_moving", [time])

        if not isinstance(time, numbers.Number):
            raise trajtracker.ValueError(
                _u.ErrMsg.invalid_method_arg_type(self.__class__, "reset",
                                                  "numeric", "time", time))

        self._time0 = time

        if self._show_guide:
            #-- Guide should appear. Don't present it immediately, because its position is
            #-- not updated yet; just mark that it should be changed to visible
            self._should_set_guide_visible = True
Пример #30
0
    def _create_default_exit_area(self, name):
        name = name.lower()
        if name == "above":
            f, t = -45, 45

        elif name == "right":
            f, t = 45, 135

        elif name == "below":
            f, t = 135, 215

        elif name == "left":
            f, t = 215, -45

        else:
            raise ttrk.ValueError("unsupported exit area '%s'" % name)

        return nvshapes.Sector(self._start_area.position[0],
                               self._start_area.position[1], 10000, f, t)