예제 #1
0
def linear_intersection(line_1, line_2):
	"""Not sure exactly what this does, Jon wrote it. Used in curve segment generationn.
	Copy/pasted from KLUtilities so I can eventually remove it from there.
	"""
	# first establish if lines are given as absolute lengths or origins and angles
	l1_xy = None
	l2_xy = None
	try:
		if not all(iterable(p) for p in line_1 + line_2):
			# allow for rotation and clockwise arguments to be passed
			l1_xy = (line_1[0], point_pos(line_1[0], 9999999, *line_1[1:]))
			l2_xy = (line_2[0], point_pos(line_2[0], 9999999, *line_2[1:]))
	except AttributeError:
		raise ValueError("Lines must be either 2 x,y pairs or 1 x,y pair and a radial description.")
	d_x = (l1_xy[0][0] - l1_xy[1][0], l2_xy[0][0] - l2_xy[1][0])
	d_y = (l1_xy[0][1] - l1_xy[1][1], l2_xy[0][1] - l2_xy[1][1])

	def determinant(a, b):
		return a[0] * b[1] - a[1] * b[0]

	div = float(determinant(d_x, d_y))

	if not div:
		raise ValueError('Supplied lines do not intersect.')
	d = (determinant(*l1_xy[0:2]), determinant(*l2_xy[0:2]))

	return (determinant(d, d_x) / div, determinant(d, d_y) / div)
예제 #2
0
    def saccade_in_direction(self,
                             doi,
                             event_queue=None,
                             report=EL_TIME_START):
        """Checks whether any saccades have occured in a given direction since the last time the
        eye event queue was fetched.

        Valid directions include 'up', 'down', 'left', and 'right'. In addition, you can specify
        both a horizontal and vertical direction (e.g. ['left', 'up']) to be more specific in your
        direction of interest. For example::
        
             self.el.saccade_in_directon(doi=['up'])
             
        will detect any saccades that end higher on the screen than they start, whereas::
        
            self.el.saccade_in_directon(doi=['up', 'right'])
            
        will only detect saccades that meet that criteria *and* end further to the right than they
        start.

        Args:
            doi (:obj:`List` of str): The names of the direction(s) of interest to watch for
                saccades in. Both a vertical ('up' or 'down') or horizontal ('left' or 'right')
                direction of interest can be specified.
            event_queue (:obj:`List`, optional): A queue of events returned from
                :meth:`get_event_queue` to inspect for saccade end events. If no event queue
                is provided, the eye event queue will be fetched and processed, emptying it in
                the process.
            report (optional): A flag indicating whether to report the start time
                (``EL_TIME_START``) or end time (``EL_TIME_END``) of the saccade.

        Returns:
            The timestamp of the start or end of the event (see the 'report' argument) if a
            saccade in the queue occurred in a direction of interest, otherwise False.

        """
        if not iterable(doi):
            doi = [doi]  # if direction of interest is a string, make it a list

        directions = ['up', 'down', 'left', 'right']
        for direction in doi:
            if direction not in directions:
                valid_dois = pretty_list(directions, brackets='')
                err_str = "'{0}' is not a valid direction (must be one of {1})."
                raise ValueError(err_str.format(direction, valid_dois))

        if not event_queue:
            event_queue = self.get_event_queue([EL_SACCADE_END])
        if not len(event_queue):
            return False

        for e in event_queue:
            if e == None or self.get_event_type(e) != EL_SACCADE_END:
                continue
            timestamp = self.__saccade_in_direction__(doi, e, report)
            if timestamp:
                return timestamp

        return False
예제 #3
0
 def add_variable(self, name, d_type, values=[]):
     ivar = IndependentVariable(name, d_type)
     for v in values:
         if iterable(v):
             ivar.add_value(*v)
         else:
             ivar.add_value(v)
     self.add(ivar)
예제 #4
0
	def drift_correct(self, location=None, target=None, fill_color=None, draw_target=True):
		"""Checks the accuracy of the eye tracker's calibration by presenting a fixation
		stimulus and requiring the participant to press the space bar while looking directly at
		it. If there is a large difference between the gaze location at the time the key was
		pressed and the true location of the fixation, it indicates that there has been drift
		in the calibration.

		In TryLink mode, drift correct targets are still displayed the same as with a hardware
		eye tracker. Simulated drift corrects are performed by clicking the drift correct target
		with the mouse.

		Args:
			location (Tuple(int, int), optional): The (x,y) pixel coordinates where the drift
				correct target should be located. Defaults to the center of the screen.
			target: A :obj:`Drawbject` or other :func:`KLGraphics.blit`-able shape to use as
				the drift correct target. Defaults to a circular :func:`drift_correct_target`.
			fill_color: A :obj:`List` or :obj:`Tuple` containing an RGBA colour to use for the
				background for the drift correct screen. Defaults to the value of
				``P.default_fill_color``.
			draw_target (bool, optional): A flag indicating whether the function should draw
				the drift correct target itself (True), or whether it should leave it to the
				programmer to draw the target before :meth:`drift_correct` is called (False). 
				Defaults to True.

		"""
		show_mouse_cursor()

		target = drift_correct_target() if target is None else target
		draw_target = EL_TRUE if draw_target in [EL_TRUE, True] else EL_FALSE
		location = P.screen_c if location is None else location
		if not iterable(location):
			raise ValueError("'location' must be a pair of (x,y) pixel coordinates.")
		dc_boundary = CircleBoundary('drift_correct', location, P.screen_y // 30)
		
		while True:
			event_queue = pump(True)
			ui_request(queue=event_queue)
			if draw_target == EL_TRUE:
				fill(P.default_fill_color if not fill_color else fill_color)
				blit(target, 5, location)
				flip()
			else:
				SDL_Delay(2) # required for pump() to reliably return mousebuttondown events
			for e in event_queue:
				if e.type == SDL_MOUSEBUTTONDOWN and dc_boundary.within([e.button.x, e.button.y]):
					hide_mouse_cursor()
					if draw_target == EL_TRUE:
						fill(P.default_fill_color if not fill_color else fill_color)
						flip()
					return 0
    def display_refresh(self,
                        boxes=None,
                        fixation=None,
                        cue=None,
                        target=None):
        # In keypress condition, after target presented, check that gaze
        # is still within fixation bounds and print message at end if not
        if P.keypress_response_cond and not self.before_target:

            gaze = self.el.gaze()
            if not self.bi.within_boundary(label='drift_correct', p=gaze):
                # if lsl(self.el.gaze(), P.screen_c) > self.fixation_boundary:
                self.moved_eyes_during_rc = True

        fill()
        if boxes is not None:
            if iterable(boxes):
                box_l = boxes
            if boxes == V_START_AXIS:
                box_l = [self.target_locs[TOP], self.target_locs[BOTTOM]]
            if boxes == H_START_AXIS:
                box_l = [self.target_locs[LEFT], self.target_locs[RIGHT]]

            for l in box_l:
                blit(self.box, 5, l)

        if fixation is not None:
            blit(fixation, 5, P.screen_c)

        if cue:
            blit(self.asterisk, 5, self.target_locs[cue])

        if target:
            if target != "none":  # if not catch trial, show target
                blit(self.circle, 5, self.target_locs[target])
            if self.before_target:
                self.before_target = False

        flip()
예제 #6
0
    def fixated_boundary(self,
                         label,
                         valid_events=EL_FIXATION_START,
                         event_queue=None,
                         report=None,
                         inspect=None):
        """Checks whether a specified boundary has been fixated since the last time the eye
        event queue was fetched.

        By default, this method tests the most useful gaze attribute of each event against the
        given boundary. This means that ``EL_GAZE_START`` is inspected for fixation start events
        and ``EL_GAZE_AVG`` is inspected for fixation update and fixation end events. You can
        manually specify which gaze attribute to use for boundary testing with the 'inspect'
        argument.

        The valid event constants for this function, along with their supported 'inspect' and
        'report' flag values, are listed in the table below:

        +------------------------+-------------------------------------+--------------------+
        | Valid Events           | Inspect Values                      | Report Values      |
        +========================+=====================================+====================+
        | ``EL_FIXATION_START``  | ``EL_GAZE_START``                   | ``EL_TIME_START``  |
        +------------------------+-------------------------------------+--------------------+
        | ``EL_FIXATION_UPDATE`` | ``EL_GAZE_START``, ``EL_GAZE_AVG``, | ``EL_TIME_START``, |
        |                        | ``EL_GAZE_END``                     | ``EL_TIME_END``    |
        +------------------------+-------------------------------------+--------------------+
        | ``EL_FIXATION_END``    | ``EL_GAZE_START``, ``EL_GAZE_AVG``, | ``EL_TIME_START``, |
        |                        | ``EL_GAZE_END``                     | ``EL_TIME_END``    |
        +------------------------+-------------------------------------+--------------------+

        Args:
            label (str): The label of the boundary to check if the event is in, added using
                add_boundary().
            valid_events (:obj:`List`, optional): A list of constants indicating the fixation
                event type(s) to process. Defaults to fixation start events. 
            event_queue (:obj:`List`, optional): A queue of events returned from
                :meth:`get_event_queue` to inspect for the fixation events. If no event queue
                is provided, the eye event queue will be fetched and processed, emptying it in
                the process.
            report (optional): A flag indicating whether to report the start time
                (``EL_TIME_START``) or end time (``EL_TIME_END``) of the fixation.
            inspect (optional): A flag indicating which gaze attribute of the fixation should be
                checked against the boundary: the gaze at the start of the fixation
                (``EL_GAZE_START``), the gaze at the end of the fixation (``EL_GAZE_END``), or
                the fixation's average gaze (``EL_GAZE_AVG``). Defaults to inspecting start gaze
                for fixation start events and average gaze for fixation update/end events.

        Returns:
            The timestamp of the start or end of the event (see the 'report' argument) if a
            valid fixation event in the queue was within the specified boundary, otherwise False.

        """
        if not iterable(valid_events):
            valid_events = [valid_events]

        for e_type in valid_events:
            if e_type not in EL_FIXATION_ALL:
                raise ValueError(
                    "Valid events for fixated_boundary must be fixation events."
                )

        return self.within_boundary(label, valid_events, event_queue, report,
                                    inspect)
예제 #7
0
 def add_values(self, *args):
     for v in args:
         if iterable(v):
             self.add_value(*v)
         else:
             self.add_value(v)
예제 #8
0
def message(text,
            style=None,
            location=None,
            registration=None,
            blit_txt=True,
            flip_screen=False,
            clear_screen=False,
            align="left",
            wrap_width=None):
    r"""Renders a string of text using a given TextStyle, and optionally draws it to the display.

	Args:
		text (str): The string of text to be rendered.
		style (str, optional): The name of the :class:`~klibs.KLTextManager.TextStyle` to be used. 
			If none provided, defaults to the 'default' TextStyle.
		blit_txt (bool, optional): If True, the rendered text is drawn to the display buffer at
			the location and registration specfied using :func:`~klibs.KLGraphics.blit`. 
			Defaults to True.
		registration (int, optional): An integer from 1 to 9 indicating which location on the
			surface will be aligned to the location value (see manual for more info). Only
			required if blit_txt is True.
		location(tuple(int,int), optional): A tuple of x,y pixel coordinates indicating where to
			draw the object to. Only required if blit_txt is True.
		flip_screen (bool, optional): If True, :func:`~klibs.KLGraphics.flip` is called immediately
			after blitting and the text is displayed on the screen. Only has an effect if blit_txt
			is True. Defaults to False.
		clear_screen (bool, optional): If True, the background of the display buffer will be filled
			with the default fill colour before the text is blitted. Only has an effect if blit_txt
			is True. Defaults to False.
		align (str, optional): The justification of the text, must be one of "left", "center", or
			"right". This only has an effect if there are multiple lines (denoted by "\n") in the
			passed string of text. Defaults to "left" if not specified.
		wrap_width (int, optional): The maximum width of the message before text will wrap around
			to the next line (not currently implemented).

	Returns:
		:obj:`~klibs.KLGraphics.KLNumpySurface.NumpySurface`: A NumpySurface object that can be 
			drawn to the screen using :func:`~klibs.KLGraphics.blit`, or None if blit_txt is True.
	
	Raises:
		ValueError: If blit_txt is true and location is not a valid pair of x/y coordinates.

	"""

    #TODO: 	make sure there won't be any catastrophic issues with this first, but rearrange 'align'
    #		and 'width' so they follow 'text' and 'style'. Also, consider whether using different
    #		method entirely for blitting/flipping messages since it kind of makes this a mess.

    #TODO:	consider whether a separate 'textbox' method (with justification/width/formatting)
    #		would be appropriate, or if having it all rolled into message() is best.

    from klibs.KLEnvironment import txtm

    if not style:
        style = txtm.styles['default']
    else:
        try:
            # TODO: Informative error if requested font style doesn't exist
            style = txtm.styles[style]
        except TypeError:
            pass

    message_surface = txtm.render(text, style, align, wrap_width)
    if blit_txt == False:
        return message_surface
    else:
        if location == "center" and registration is None:  # an exception case for perfect centering
            registration = BL_CENTER
        if registration is None:
            registration = BL_TOP_LEFT

        # process location, infer if need be; failure here is considered fatal
        if not location:
            x_offset = (P.screen_x - P.screen_x) // 2 + style.font_size
            y_offset = (P.screen_y - P.screen_y) // 2 + style.font_size
            location = (x_offset, y_offset)
        else:
            try:
                iter(location)
            except AttributeError:
                raise ValueError(
                    "Argument 'location' must be a location constant or iterable x,y coordinate pair"
                )
            if clear_screen:
                fill(clear_screen if iterable(clear_screen) else P.
                     default_fill_color)
            blit(message_surface, registration, location)
            if flip_screen:
                flip()
예제 #9
0
 def add_figure(self, figure_name):
     if iterable(figure_name):
         self.figures.append(list(figure_name))
     else:
         self.figures.append([figure_name, 1])
예제 #10
0
 def __parse_values__(self):
     for v in self.figures:
         if iterable(v):
             self.figures.append(list(v))
         else:
             self.figures.append([v, 1])