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)
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
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)
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()
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)
def add_values(self, *args): for v in args: if iterable(v): self.add_value(*v) else: self.add_value(v)
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()
def add_figure(self, figure_name): if iterable(figure_name): self.figures.append(list(figure_name)) else: self.figures.append([figure_name, 1])
def __parse_values__(self): for v in self.figures: if iterable(v): self.figures.append(list(v)) else: self.figures.append([v, 1])