def decompress(self): """Decompress the stimulus. This will decompress the stimulus. The surface will now be read from memory again. Depending on the size of the stimulus, this method may take some time to compute! Returns ------- time : int the time it took to execute this method """ start = Clock._cpu_time() if self.is_compressed: self._surface = pygame.image.load( self._compression_filename).convert_alpha() self._is_compressed = False if self._logging: expyriment._active_exp._event_file_log( "Stimulus,decompressed,{0}".format(self.id), 2) return int((Clock._cpu_time() - start) * 1000)
def flip(self, booleans): """Flip the stimulus. This is a surface operation. After this, a surface will be present! Parameters ---------- booleans : (bool, bool) booleans to flip or not Returns ------- time : int the time it took to execute this method Notes ----- Depending on the size of the stimulus, this method may take some time to compute! """ start = Clock._cpu_time() if not self._set_surface(self._get_surface()): raise RuntimeError(Visual._compression_exception_message.format( "flip()")) self.unload(keep_surface=True) self._set_surface(pygame.transform.flip(self._get_surface(), booleans[0], booleans[1])) if self._logging: expyriment._active_exp._event_file_log( "Stimulus,flipped,{0}, booleans={1}".format(self.id, booleans), 2) return int((Clock._cpu_time() - start) * 1000)
def move(self, offset): """Moves the stimulus in 2D space. When using OpenGL, this can take longer then 1ms! Parameters ---------- offset : list, optional translation along x and y axis Returns ------- time : int the time it took to execute this method """ start = Clock._cpu_time() moved = False x = offset[0] y = offset[1] if x > 0 or x < 0: self._position[0] = self._position[0] + x moved = True if y > 0 or y < 0: self._position[1] = self._position[1] + y moved = True if moved and self._ogl_screen is not None: self._ogl_screen.refresh_position() return int((Clock._cpu_time() - start) * 1000)
def compress(self): """"Compress the stimulus. This will create a temporary file on the disk where the surface of the stimululs is written to. The surface will now be read from the disk to free memory. Compressed stimuli cannot do surface operations! Preloading comressed stimuli is possible and highly recommended. Depending on the size of the stimulus, this method may take some time to compute! Returns ------- time : int the time it took to execute this method """ start = Clock._cpu_time() if self.is_compressed is False: if self._compression_filename is None: fid, self._compression_filename = tempfile.mkstemp( dir=defaults.tempdir, suffix=".tga") os.close(fid) pygame.image.save(self._get_surface(), self._compression_filename) self._is_compressed = True self._surface = None if self._logging: expyriment._active_exp._event_file_log( "Stimulus,compressed,{0}".format(self.id), 2) return int((Clock._cpu_time() - start) * 1000)
def clear_surface(self): """Clear the stimulus surface. Surfaces are automatically created after any surface operation (presenting, plotting, rotating, scaling, flipping etc.) and preloading. If the stimulus was preloaded, this method unloads the stimulus. This method is functionally equivalent with unload(keep_surface=False). Returns ------- time : int the time it took to execute this method Notes ----- Depending on the size of the stimulus, this method may take some time to compute! """ start = Clock._cpu_time() if self.is_preloaded: self.unload(keep_surface=False) self._is_compressed = False self._set_surface(None) if self._logging: expyriment._active_exp._event_file_log( "Stimulus,surface cleared,{0}".format(self.id), 2) return int((Clock._cpu_time() - start) * 1000)
def send(self, code=None, duration=None): """Send a marker. This sends a marker via the specified interface. If a duration is given, a 0 will be sent automatically after each code. Note for EEG/MEG systems: If the system is receiving the markers on a parallel port, the duration between sending a code an the subsequent 0 should be at least 1000/samplerate! Parameters ---------- code : int, optional a specific code durartion : int, optional duration (in ms) for sending a 0 after a code """ if not code: code = self.default_code if not duration: duration = self.default_duration self._interface.send(code) if duration: start = Clock._cpu_time() while (Clock._cpu_time() - start) * 1000 < duration: pass self._interface.send(0) if self._logging: expyriment._active_exp._event_file_log( "MarkerOutput,sent,{0}".format(code))
def rotate(self, degree): """Rotate the stimulus. This is a surface operation. After this, a surface will be present! Rotating goes along with a quality loss. Thus, rotating an already rotated stimulus is not a good idea. Parameters ---------- degree : int degree to rotate counterclockwise Returns ------- time : int the time it took to execute this method Notes ----- Depending on the size of the stimulus, this method may take some time to compute! """ start = Clock._cpu_time() if not self._set_surface(self._get_surface()): raise RuntimeError(Visual._compression_exception_message.format( "rotate()")) self.unload(keep_surface=True) self._set_surface(pygame.transform.rotate(self._get_surface(), degree)) if self._logging: expyriment._active_exp._event_file_log( "Stimulus,rotated,{0}, degree={1}".format(self.id, degree)) return int((Clock._cpu_time() - start) * 1000)
def wait(self, codes=None, duration=None, no_clear_buffer=False, bitwise_comparison=False, check_for_control_keys=True): """Wait for responses defined as codes. Notes ----- If bitwise_comparision = True, the function performs a bitwise comparison (logical and) between codes and received input and waits until a certain bit pattern is set. This will also by default check for control keys (quit and pause). Thus, keyboard events will be cleared from the cue and cannot be received by a Keyboard().check() anymore! Parameters ---------- codes : int or list, optional bit pattern to wait for if codes is not set (None) the function returns for any event that differs from the baseline duration : int, optional maximal time to wait in ms no_clear_buffer : bool, optional do not clear the buffer (default = False) bitwise_comparison : bool, optional make a bitwise comparison (default = False) check_for_control_keys : bool, optional checks if control key has been pressed (default=True) Returns ------- key : int key code (or None) that quitted waiting rt : int reaction time """ start = Clock._cpu_time() rt = None if not no_clear_buffer: self.clear() while True: expyriment._active_exp._execute_wait_callback() if duration is not None: if int((Clock._cpu_time() - start) * 1000) > duration: return None, None found = self.check(codes, bitwise_comparison) if found is not None: rt = int((Clock._cpu_time() - start) * 1000) break if check_for_control_keys: if Keyboard.process_control_keys(): break if self._logging: expyriment._active_exp._event_file_log( "{0},received,{1},wait".format( self.__class__.__name__, found)) return found, rt
def blur(self, level): """Blur the stimulus. This blurs the stimulus, by scaling it down and up by the factor of 'level'. Parameters ---------- level : int level of bluring Returns ------- time : int the time it took to execute this method Notes ----- Depending on the size of the stimulus, this method may take some time to compute! """ start = Clock._cpu_time() self.scale((1.0 / level, 1.0 / level)) self.scale((level, level)) if self._logging: expyriment._active_exp._event_file_log( "Stimulus,blured,{0}, level={1}".format(self.id, level), 2) return int((Clock._cpu_time() - start) * 1000)
def save(self): """Save file to disk.""" start = Clock._cpu_time() if self._buffer != []: with open(self._fullpath, 'a') as f: f.write("".join(self._buffer)) self._buffer = [] return int((Clock._cpu_time() - start) * 1000)
def save(self): """Save the new data to data-file. Returns ------- time : int the time it took to execute this method """ start = Clock._cpu_time() if len(self._subject_info) > 0 or len(self._experiment_info) > 0 \ or self._variable_names_changed: # Re-write header and varnames tmpfile_name = "{0}/{1}".format(self.directory, uuid.uuid4()) os.rename(self._fullpath, tmpfile_name) fl = open(self._fullpath, 'w+') tmpfl = open(tmpfile_name, 'r') section = None while True: line = tmpfl.readline() if not line: break if line.startswith(self.comment_char + "e"): section = "e" elif line.startswith(self.comment_char + "s"): section = "s" else: if section == "e": # Previous line was last #e if len(self._experiment_info) > 0: fl.write("".join(self._experiment_info)) self._experiment_info = [] section = None elif section == "s": # Previous line was last #s if len(self._subject_info) > 0: fl.write("".join(self._subject_info)) self._subject_info = [] section = None # Re-write variable names after #s-section fl.write(self.variable_names + defaults.outputfile_eol) self._variable_names_changed = False line = '' # Skip old varnames fl.write(line) tmpfl.close() fl.close() os.remove(tmpfile_name) self._subject_info = [] self._experiment_info = [] if self._buffer != []: OutputFile.save(self) if self._logging: expyriment._active_exp._event_file_log("Data,saved") return int((Clock._cpu_time() - start) * 1000)
def wait(self, events, duration=None): """Wait for (a) certain event(s). Events to wait for are in the form of a list with 4 elements and do not include a timestamp: [status, data1, data2, data3] Parameters ---------- events : int or list event(s) to wait for duration : int, optional maximal time to wait in ms Returns ------- evt : int found event rt : int reaction timein ms """ start = Clock._cpu_time() rt = None _event = None self.clear() if type(events) is list and \ len(events) == 4 and \ type(events[0]) is int and \ type(events[1]) is int and \ type(events[2]) is int and \ type(events[3]) is int: events = [events] done = False while not done: expyriment._active_exp._execute_wait_callback() event = self.read(1) if event is not None and event[0][0] in events: rt = int((Clock._cpu_time() - start) * 1000) _event = event[0][0] done = True break if Keyboard.process_control_keys(): done = True break if duration: if int((Clock._cpu_time() - start) * 1000) >= duration: done = True break time.sleep(0.0005) if self._logging: expyriment._active_exp._event_file_log( "MIDI In ({0}),received,{1},wait".format(self.id, _event), 2) return _event, rt
def preload(self, inhibit_ogl_compress=False): """Preload the stimulus to memory. This will prepare the stimulus for a fast presentation. In OpenGL mode this method creates an OpenGL texture based on the surface of the stimulus. When OpenGL is switched off, this method will create a surface if it doesn't exists yet. If stimuli are not preloaded manually, this will happen automatically during presentation. However, stimulus presentation will take some time then! Always preload your stimuli when a timing acurate presentation is needed! Parameters ---------- inhibit_ogl_compress : bool, optional inhibits OpenGL stimuli to be automatically compressed (default=False) Returns ------- time : int the time it took to execute this method Notes ----- Depending on the size of the stimulus, this method may take some time to compute! """ start = Clock._cpu_time() if not expyriment._active_exp.is_initialized: message = "Can't preload stimulus. Expyriment needs to be \ initilized before preloading a stimulus." raise RuntimeError(message) self._was_compressed_before_preload = self.is_compressed if not self.is_preloaded: if expyriment._active_exp.screen.open_gl: self._ogl_screen = _LaminaPanelSurface( self._get_surface(), position=self.position) if not inhibit_ogl_compress: self.compress() else: self.decompress() self._set_surface(self._get_surface()) self._is_preloaded = True if self._logging: expyriment._active_exp._event_file_log( "Stimulus,preloaded,{0}".format(self.id), 2) return int((Clock._cpu_time() - start) * 1000)
def present(self, clear=True, update=True): """Present the stimulus on the screen. This clears and updates the screen automatically. When not preloaded, depending on the size of the stimulus, this method can take some time to compute! Parameters ---------- clear : bool, optional if True the screen will be cleared automatically (default = True) update : bool, optional if False the screen will be not be updated automatically (default = True) Returns ------- time : int the time it took to execute this method """ if not expyriment._active_exp.is_initialized or\ expyriment._active_exp.screen is None: raise RuntimeError("Cannot not find a screen!") start = Clock._cpu_time() preloading_required = not(self.is_preloaded) if clear: expyriment._active_exp.screen.clear() if preloading_required: # Check if stimulus has surface keep_surface = self.has_surface self.preload(inhibit_ogl_compress=True) if expyriment._active_exp.screen.open_gl: self._ogl_screen.display() else: screen = expyriment._active_exp.screen.surface rect = pygame.Rect((0, 0), self.surface_size) screen_size = screen.get_size() rect.center = [self.position[0] + screen_size[0] / 2, - self.position[1] + screen_size[1] / 2] screen.blit(self._get_surface(), rect) if self._logging: expyriment._active_exp._event_file_log("Stimulus,presented,{0}"\ .format(self.id), 1) if update: expyriment._active_exp.screen.update() if preloading_required: self.unload(keep_surface=keep_surface) return int((Clock._cpu_time() - start) * 1000)
def scale(self, factors): """Scale the stimulus. This is a surface operation. After this, a surface will be present! Negative scaling values will flip the stimulus. Scaling goes along with a quality loss. Thus, scaling an already scaled stimulus is not a good idea. Parameters ---------- factors : (int, int) or float tuple representing the x and y factors to scale or a single number. In the case of a single number x and y scaling will be the identical (i.e., proportional scaling) Returns ------- time : int the time it took to execute this method Notes ----- Depending on the size of the stimulus, this method may take some time to compute! """ start = Clock._cpu_time() if not self._set_surface(self._get_surface()): raise RuntimeError(Visual._compression_exception_message.format( "scale()")) self.unload(keep_surface=True) flip = [False, False] if type(factors) in [types.IntType, types.FloatType]: factors = [factors, factors] else: factors = list(factors) if factors[0] < 0: flip[0] = True factors[0] = abs(factors[0]) if factors[1] < 0: flip[1] = True factors[1] = abs(factors[1]) self._set_surface(pygame.transform.smoothscale( self._get_surface(), (int(round(self.surface_size[0] * factors[0])), int(round(self.surface_size[1] * factors[1]))))) if True in flip: self.flip(flip) if self._logging: expyriment._active_exp._event_file_log( "Stimulus,sclaed,{0}, factors={1}".format(self.id, factors), 2) return int((Clock._cpu_time() - start) * 1000)
def unload(self, keep_surface=False): """Unload the stimulus from memory. This will unload preloaded stimuli. In OpenGL mode, this method will remove the reference to the OpenGL texture and the surface (when 'keep_surface' is False). When OpenGL is switched off, the reference to the surface will be removed (when 'keep_surface' is False). Parameters ---------- keep_surface : bool, optional keep the surface after unload (default=False) Returns ------- time : int the time it took to execute this method See Also -------- clear_surface. Notes ----- Depending on the size of the stimulus, this method may take some time to compute! """ start = Clock._cpu_time() if expyriment._active_exp.screen.open_gl: self._ogl_screen = None if self.is_preloaded and not self._was_compressed_before_preload \ and keep_surface: self.decompress() else: # Pygame surface if self.is_preloaded and self._was_compressed_before_preload \ and keep_surface: self.compress() if self.is_preloaded and self._logging: expyriment._active_exp._event_file_log("Stimulus,unloaded,{0}"\ .format(self.id), 2) if not keep_surface: self._is_compressed = False self._surface = None if self._logging: expyriment._active_exp._event_file_log("Stimulus,surface cleared,{0}"\ .format(self.id), 2) self._is_preloaded = False return int((Clock._cpu_time() - start) * 1000)
def wait_press(self, buttons=None, duration=None): """Wait for gamepad button press. Returns the found button and the reaction time. Parameters ---------- buttons : int or list, optional specific buttons to wait for duration : int, optional maximal time to wait in ms Returns ------- button : int button _id of the pressed button rt : int reaction time in ms """ start = Clock._cpu_time() rt = None _button = None self.clear() if buttons is None: buttons = range(self.get_numbuttons()) if type(buttons) is not list: buttons = [buttons] done = False while not done: expyriment._active_exp._execute_wait_callback() for button in buttons: if self.get_button(button): _button = button rt = int((Clock._cpu_time() - start) * 1000) done = True break if _button is not None or Keyboard.process_control_keys(): done = True break if duration: if int((Clock._cpu_time() - start) * 1000) >= duration: done = True break time.sleep(0.0005) if self._logging: expyriment._active_exp._event_file_log("Gamepad,received,{0},wait_press".format(_button)) return _button, rt
def scramble(self, grain_size): """Scramble the stimulus. Attention: If the surface size is not a multiple of the grain size, you may loose some pixels on the edge. Parameters ---------- grain_size : int or (int, int) size of a grain (use tuple of integers for different width & height) Returns ------- time : int the time it took to execute this method Notes ----- Depending on the size of the stimulus, this method may take some time to compute! """ start = Clock._cpu_time() if type(grain_size) is int: grain_size = [grain_size, grain_size] # Make Rect list if not self._set_surface(self._get_surface()): raise RuntimeError(Visual._compression_exception_message.format( "scramble()")) s = self.surface_size source = [] for r in range(s[1] / int(grain_size[1])): for c in range(s[0] / int(grain_size[0])): xy = (c * int(grain_size[0]), r * int(grain_size[1])) source.append(pygame.Rect(xy, grain_size)) # Make copy and shuffle dest = copy.deepcopy(source) random.shuffle(dest) # Create a new surface tmp_surface = pygame.surface.Surface( s, pygame.SRCALPHA).convert_alpha() for n, s in enumerate(source): tmp_surface.blit(self._get_surface(), dest[n], s) self._set_surface(tmp_surface) if self._logging: expyriment._active_exp._event_file_log( "Stimulus,scrambled,{0}, grain_size={1}".format( self.id, grain_size), 2) return int((Clock._cpu_time() - start) * 1000)
def create_mask(self): """Creates a new visual mask. Notes ----- CAUTION: Depending on the size of the stimulus, this method may take some time to execute. Returns ------- time : int the time it took to execute this method in ms """ start = Clock._cpu_time() was_preloaded = self.is_preloaded if was_preloaded: self.unload() s = (self._size[0] + 4 * self.smoothing, self._size[1] + 4 * self.smoothing) #somewhat larger mask im = Image.new("RGB", s) draw = ImageDraw.Draw(im) draw.rectangle([(0, 0), s], outline=self.background_colour, fill=self.background_colour) n_dots_x = int(s[0] / self.dot_size[0]) + 1 n_dots_y = int(s[1] / self.dot_size[1]) + 1 dots = range(n_dots_x * n_dots_y) shuffle(dots) for d in dots[:int(len(dots) * self.dot_percentage / 100)]: y = (d / n_dots_x) * self.dot_size[1] x = (d % n_dots_x) * self.dot_size[0] draw.rectangle([(x, y), (x + self.dot_size[0], y + self.dot_size[1])], outline=self.dot_colour, fill=self.dot_colour) for x in range(self.smoothing): im = im.filter(ImageFilter.BLUR).filter(ImageFilter.SMOOTH_MORE) #crop image and save c = (im.size[0] / 2, im.size[1] / 2) box = (c[0] - self._size[0] / 2, c[1] - self._size[1] / 2, c[0] + self._size[0] / 2, c[1] + self._size[1] / 2) im = im.crop(box) im.save(self._filename, format="png") if was_preloaded: self.preload() return int((Clock._cpu_time() - start) * 1000)
def wait_char(self, char, duration=None, check_for_control_keys=True): """Wait for character(s) (optionally for a certain amount of time). This function will wait for one or more characters and returns the found character as well as the reaction time. (This function clears the event queue!) Parameters ---------- char : int or list a specific character or list of characters to wait for duration : int, optional maximal time to wait in ms check_for_control_keys : bool, optional checks if control key has been pressed (default=True) Returns ------- found : char pressed charater rt : int reaction time in ms """ start = Clock._cpu_time() rt = None found_char = None self.clear() if type(char) is not list: char = [char] pygame.event.pump() done = False while not done: expyriment._active_exp._execute_wait_callback() for event in pygame.event.get(): if check_for_control_keys and Keyboard.process_control_keys(event): done = True elif event.type == pygame.KEYDOWN: if event.unicode in char: rt = int((Clock._cpu_time() - start) * 1000) found_char = event.unicode done = True if duration and not done: done = int((Clock._cpu_time() - start) * 1000) >= duration time.sleep(0.0005) if self._logging: expyriment._active_exp._event_file_log( "Keyboard,received,{0},wait_char".format(found_char)) return found_char, rt
def add_noise(self, grain_size, percentage, colour): """Add visual noise on top of the stimulus. This function might take very long for large stimuli. Parameters ---------- grain_size : int size of the grains for the noise percentage : int percentage of covered area colour : (int, int, int) colour (RGB) of the noise Returns ------- time : int the time it took to execute this method Notes ----- Depending on the size of the stimulus, this method may take some time to compute! """ import _rectangle start = Clock._cpu_time() if not self._set_surface(self._get_surface()): raise RuntimeError(Visual._compression_exception_message.format( "add_noise()")) self.unload(keep_surface=True) number_of_pixel_x = int(self.surface_size[0] / grain_size) + 1 number_of_pixel_y = int(self.surface_size[1] / grain_size) + 1 seq = range(number_of_pixel_x * number_of_pixel_y) random.seed() random.shuffle(seq) for idx in seq[:int(len(seq) * (percentage) / 100.0)]: x = (idx % number_of_pixel_x) * grain_size x = int(self.surface_size[0] / 2 - grain_size / 2 - x) y = (idx / number_of_pixel_x) * grain_size y = int(self.surface_size[1] / 2 - grain_size / 2 - y) dot = _rectangle.Rectangle((grain_size, grain_size), (x, y), colour) dot.plot(self) if self._logging: expyriment._active_exp._event_file_log( "Stimulus,noise added,{0}, grain_size={1}, percentage={2}"\ .format(self.id, grain_size, percentage)) return int((Clock._cpu_time() - start) * 1000)
def __init__(self, position, direction, speed, lifetime, extra_age=0, north_up_clockwise=True, is_target=False): """Create a MovingPosition Parameters ---------- position : (int, int) start position direction : int, float (0-360) movement direction in degrees speed : int the moving speed in pixel per second lifetime : int the time the object lives in milliseconds extra_age : int, optional the object can have already an age when creating or resetting (default=0) 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) is_target : bool target position """ self._start_position = list(position) self.lifetime = lifetime self.extra_age = extra_age # add extra age for shorter lifetime self.is_target = is_target self._speed = speed self._north_up_clockwise = (north_up_clockwise,) self._direction = direction self._update_movement_vector() self._clock = Clock()
def _test2(): info = """This will test if stimulus presentation can be synchronized to the refreshrate of the screen. A good result is a fast, constant and smooth flickering without any distortions (e.g. horizontal stripes, tearing). The estimated refreshrate should resemble your actual screen refreshrate (common refreshrates are between 40 and 240 Hz). [Press RETURN to continue]""" text = stimuli.TextScreen("Stimulus presentation test (2)", info) text.present() exp.keyboard.wait([constants.K_RETURN]) black = stimuli.BlankScreen(colour=constants.C_BLACK) black.preload() white = stimuli.BlankScreen(colour=constants.C_WHITE) white.preload() times = [] black.present() for _x in range(100): start = Clock._cpu_time() black.present() times.append(Clock._cpu_time() - start) start = Clock._cpu_time() white.present() times.append(Clock._cpu_time() - start) refresh_rate = 1000 / (statistics.mean(times) * 1000) info = """Your estimated refresh rate is {0} Hz. [Press RETURN to continue] """.format(refresh_rate) text = stimuli.TextScreen("Results", info) text.present() exp.keyboard.wait([constants.K_RETURN]) text = stimuli.TextScreen( "Was the flickering fast, constant and smooth, without any distortions?", "[Press Y or N]") text.present() key, _rt = exp.keyboard.wait([constants.K_y, constants.K_n]) if key == constants.K_y: response2 = "Yes" elif key == constants.K_n: response2 = "No" return refresh_rate, response2
def __init__(self, position, direction, speed, lifetime, extra_age=0, north_up_clockwise=True, is_target=False): """Create a MovingPosition Parameters ---------- position : (int, int) start position direction : int, float (0-360) movement direction in degrees speed : int the moving speed in pixel per second lifetime : int the time the object lives in milliseconds extra_age : int, optional the object can have already an age when creating or resetting (default=0) 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) is_target : bool target position """ self._start_position = list(position) self.lifetime = lifetime self.extra_age = extra_age # add extra age for shorter lifetime self.is_target = is_target self._speed = speed self._north_up_clockwise = north_up_clockwise, self._direction = direction self._update_movement_vector() self._clock = Clock()
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 plot(self, stimulus): """Plot the stimulus on the surface of another stimulus. Use this to plot more than one stimulus and to present them at the same time afterwards by presenting the stimulus on which they were plotted on. Parameters ---------- stimulus : expyriment stimulus stimulus to whose surface should be plotted Returns ------- time : int the time it took to execute this method Notes ----- Depending on the size of the stimulus, this method may take some time to compute! """ start = Clock._cpu_time() if not stimulus._set_surface(stimulus._get_surface()): raise RuntimeError(Visual._compression_exception_message.format( "plot()")) stimulus.unload(keep_surface=True) self._parent = stimulus rect = pygame.Rect((0, 0), self.surface_size) stimulus_surface_size = stimulus.surface_size rect.center = [self.position[0] + stimulus_surface_size[0] / 2, - self.position[1] + stimulus_surface_size[1] / 2] stimulus._get_surface().blit(self._get_surface(), rect) if self._logging: expyriment._active_exp._event_file_log( "Stimulus,plotted,{0},{1}".format(self.id, stimulus.id), 2) return int((Clock._cpu_time() - start) * 1000)
def wait(self, code=None, bitwise_comparison=False): """Wait for a trigger. Returns the code received and the reaction time [code, rt]. If bitwise_comparison = True, the function performs a bitwise comparison (logical and) between code and received input and waits until a certain bit pattern is set. Parameters code -- a specific code to wait for (int) (optional) bitwise_comparison -- make a bitwise comparison (default=False) """ start = Clock._cpu_time() found = None rt = None if code is None: code = self._default_code self.interface.clear() while True: expyriment._active_exp._execute_wait_callback() read = self.interface.poll() if read is not None: if code is None: #return for every event rt = int((Clock._cpu_time() - start) * 1000) found = read break elif compare_codes(read, code, bitwise_comparison): rt = int((Clock._cpu_time() - start) * 1000) found = read break if Keyboard.process_control_keys(): break if self._logging: expyriment._active_exp._event_file_log( "TriggerInput,received,{0},wait".format(found)) return found, rt
def client(server_ip): # t : test connect # q : quit client # space : enter control.set_develop_mode(True) control.defaults.audiosystem_autostart = False exp = control.initialize() udp_connection = UDPConnection() print(udp_connection) if not udp_connection.connect_peer(server_ip): print("error connecting to peer") exit() stimuli.TextScreen( "connected to " + udp_connection.peer_ip, "\nSPACE: send text\nT: trigger test\nQ: quit").present() c = Clock() while True: key = exp.keyboard.check() if key == ord("q"): break elif key == misc.constants.K_SPACE: text = io.TextInput().get() stimuli.BlankScreen().present() print("send: {} {}".format(c.time, text)) udp_connection.send(text) elif key == ord("t"): times = [] for cnt in range(20): stimuli.TextLine("ping test " + str(cnt)).present() c.reset_stopwatch() ok, time = udp_connection.ping(timeout=1) print("answer received in {} ms".format(c.stopwatch_time)) times.append(time) c.wait(100) stimuli.BlankScreen().present() print(times) feedback = udp_connection.poll() if feedback is not None: print("received: {} {}".format(c.time, feedback)) udp_connection.unconnect_peer()
def __init__(self, port, baudrate=None, bytesize=None, parity=None, stopbits=None, timeout=None, xonxoff=None, rtscts=None, dsrdtr=None, input_history=None, os_buffer_size=None, clock=None): """Create a serial port input and output. The port argument will accept the number of the port (e.g. 0 for COM1) as well as a string describing the full port location ("COM1" or "/dev/ttyS0"). Notes ----- An input_history can be used to overcome the size limitation of the receive buffer of the operating system. An input_history consists of a misc.ByteBuffer instance. In order to not miss any input, the serial port has to be updated regularly (i.e. calling read_input() or clear() before the receive buffer will be full). If the receive buffer size is set correctly, a warning will be given, when the input_history was not updated fast enough. Importantly, the fuller the receive buffer is, the longer clearing and polling will take (this can be more than 1 ms!), since all the bytes have to be transfered to the input_history. Parameters ---------- port : int or str port to use baudrate : int, optional bytesize : int, optional parity : str, optional parity:'E'=even, 'O'=odd, 'N'=none stopbits : int, optional timeout : int, optional the timeout for read(): -1=block xonxoff : int, optional rtscts : int, optional dsrdtr : int, optional input_history : bool, optional True if an input_history should be used os_buffer_size : int, optional the size of the receive input_history provided by the operating system in bytes clock : misc.Clock, optional an experimental clock (optional) """ import types if type(serial) is not types.ModuleType: message = """SerialPort can not be initialized. The Python package 'pySerial' is not installed.""" raise ImportError(message) if float(serial.VERSION) < 2.5: raise ImportError( "Expyriment {0} ".format(__version__) + "is not compatible with PySerial {0}.".format(serial.VERSION) + "\nPlease install PySerial 2.5 or higher.") Input.__init__(self) Output.__init__(self) if baudrate is None: baudrate = defaults.serialport_baudrate if bytesize is None: bytesize = defaults.serialport_bytesize if parity is None: parity = defaults.serialport_parity if stopbits is None: stopbits = defaults.serialport_stopbits if timeout is None: timeout = defaults.serialport_timeout if timeout == -1: timeout = None if xonxoff is None: xonxoff = defaults.serialport_xonxoff if rtscts is None: rtscts = defaults.serialport_rtscts if dsrdtr is None: dsrdtr = defaults.serialport_dsrdtr if clock is not None: self._clock = clock else: if expyriment._active_exp.is_initialized: self._clock = expyriment._active_exp.clock else: self._clock = Clock() if input_history is None: input_history = defaults.serialport_input_history if input_history is True: self._input_history = ByteBuffer( name="SerialPortBuffer (Port {0})".format(repr(port)), clock=self._clock) else: self._input_history = False if os_buffer_size is None: os_buffer_size = defaults.serialport_os_buffer_size self._os_buffer_size = os_buffer_size self._serial = serial.Serial(port, baudrate, bytesize, parity, stopbits, timeout, xonxoff, rtscts, dsrdtr) if not self._serial.isOpen(): raise IOError("Could not open serial port") atexit.register(self.close)
# space : enter control.set_develop_mode(True) exp = control.initialize() udp_connection = UDPConnection() print udp_connection if not udp_connection.connect_peer("192.168.1.1"): # 41.89.98.24 print "error connecting to peer" exit() stimuli.TextLine("connected to " + udp_connection.peer_ip).present() c = Clock() # #udp.send("maximum: 72") ##udp.send("measurement: 25") ##udp.send("filename: test") ##udp.send("report: 5") ##print "--> ", c.time, "done" ##udp.send("done") ##feedback = udp.poll() ##while feedback is None: ## feedback = udp.poll() ##print "<-- ", c.time, feedback ## ##print "--> ", c.time, "start" ##udp.send("start") ##feedback = udp.poll()
def wait(self, keys=None, duration=None, wait_for_keyup=False, check_for_control_keys=True): """Wait for keypress(es) (optionally for a certain amount of time). This function will wait for a keypress and returns the found key as well as the reaction time. (This function clears the event queue!) Parameters ---------- keys : int or list, optional a specific key or list of keys to wait for duration : int, optional maximal time to wait in ms wait_for_keyup : bool, optional if True it waits for key-up check_for_control_keys : bool, optional checks if control key has been pressed (default=True) """ if android is not None: android.show_keyboard() start = Clock._cpu_time() rt = None found_key = None self.clear() if keys is None: keys = self.default_keys if keys is not None and type(keys) is not list: keys = [keys] if wait_for_keyup: target_event = pygame.KEYUP else: target_event = pygame.KEYDOWN pygame.event.pump() done = False while not done: expyriment._active_exp._execute_wait_callback() for event in pygame.event.get(): if check_for_control_keys and Keyboard.process_control_keys(event): done = True elif event.type == target_event: if keys is not None: if event.key in keys: rt = int((Clock._cpu_time() - start) * 1000) found_key = event.key done = True else: rt = int((Clock._cpu_time() - start) * 1000) found_key = event.key done = True if duration and not done: done = int((Clock._cpu_time() - start) * 1000) >= duration time.sleep(0.0005) if self._logging: expyriment._active_exp._event_file_log("Keyboard,received,{0},wait"\ .format(found_key)) if android is not None: android.hide_keyboard() return found_key, rt
class MovingPosition(object): def __init__(self, position, direction, speed, lifetime, extra_age=0, north_up_clockwise=True, is_target=False): """Create a MovingPosition Parameters ---------- position : (int, int) start position direction : int, float (0-360) movement direction in degrees speed : int the moving speed in pixel per second lifetime : int the time the object lives in milliseconds extra_age : int, optional the object can have already an age when creating or resetting (default=0) 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) is_target : bool target position """ self._start_position = list(position) self.lifetime = lifetime self.extra_age = extra_age # add extra age for shorter lifetime self.is_target = is_target self._speed = speed self._north_up_clockwise = north_up_clockwise, self._direction = direction self._update_movement_vector() self._clock = Clock() @property def is_dead(self): """Return True is lifetime of the object is over""" return (self.age >= self.lifetime) @property def age(self): """Return the age of a dot""" return self._clock.stopwatch_time + self.extra_age def reset_age(self, randomize_age=False): """Reset the age to zero (born at current time) or if randomize_age=True, age will randomized.""" if randomize_age: self.extra_age = int(random.random() * self.lifetime) self._clock.reset_stopwatch() def is_outside(self, the_range): """Return True the object is outside the range from (0,0)""" pos = self.position return (math.hypot(pos[0], pos[1]) >= the_range) @property def north_up_clockwise(self): """getter for north up and clockwise""" return self._north_up_clockwise @property def position(self): """The current position (depends on time). Note: This property changes continuously over time, since the position is moving. """ return (self._start_position[0] + self._clock.stopwatch_time * self._movement_vector[0], self._start_position[1] + self._clock.stopwatch_time * self._movement_vector[1]) @property def direction(self): """Getter for direction.""" return self._direction @property def speed(self): """Getter for speed.""" return self._speed @direction.setter def direction(self, x): self._direction = x self._update_movement_vector() @speed.setter def speed(self, x): """speed in pix per second""" self._speed = x self._update_movement_vector() def _update_movement_vector(self): if self._north_up_clockwise: direction = 450 - self._direction else: direction = self._direction angle = direction * (math.pi) / 180 speed = self._speed / float(1000) self._movement_vector = (speed * math.cos(angle), speed * math.sin(angle))
for i, w in enumerate(f.split()): if not w in maptext: maptext[w] = stimuli.TextLine(w) maptext[w].preload() events.put((onset + i * WORD_DURATION, 'text', w, maptext[w])) events.put((onset + (i + 1) * WORD_DURATION, 'blank', 'blank', bs)) #% expyriment.control.start() wm.present() kb.wait_char('t') # wait for scanner TTL fs.present() # clear screen, presenting fixation cross a = Clock() while not (events.empty()): onset, stype, id, stim = events.get() print('event {} {} @ {}'.format(stype, id, onset)) if a.time > onset: print('...delayed @ {}'.format(a.time)) # TODO while a.time < (onset - 10): a.wait(10) k = kb.check() if k is not None: print('keypressed: {} @ {}'.format(k, a.time)) exp.data.add([a.time, k]) stim.present()
class MovingPosition(object): def __init__(self, position, direction, speed, lifetime, extra_age=0, north_up_clockwise=True, is_target=False): """Create a MovingPosition Parameters ---------- position : (int, int) start position direction : int, float (0-360) movement direction in degrees speed : int the moving speed in pixel per second lifetime : int the time the object lives in milliseconds extra_age : int, optional the object can have already an age when creating or resetting (default=0) 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) is_target : bool target position """ self._start_position = list(position) self.lifetime = lifetime self.extra_age = extra_age # add extra age for shorter lifetime self.is_target = is_target self._speed = speed self._north_up_clockwise = (north_up_clockwise,) self._direction = direction self._update_movement_vector() self._clock = Clock() @property def is_dead(self): """Return True is lifetime of the object is over""" return self.age >= self.lifetime @property def age(self): """Return the age of a dot""" return self._clock.stopwatch_time + self.extra_age def reset_age(self, randomize_age=False): """Reset the age to zero (born at current time) or if randomize_age=True, age will randomized.""" if randomize_age: self.extra_age = int(random.random() * self.lifetime) self._clock.reset_stopwatch() def is_outside(self, the_range): """Return True the object is outside the range from (0,0)""" pos = self.position return math.hypot(pos[0], pos[1]) >= the_range @property def north_up_clockwise(self): """getter for north up and clockwise""" return self._north_up_clockwise @property def position(self): """The current position (depends on time). Note: This property changes continuously over time, since the position is moving. """ return ( self._start_position[0] + self._clock.stopwatch_time * self._movement_vector[0], self._start_position[1] + self._clock.stopwatch_time * self._movement_vector[1], ) @property def direction(self): """Getter for direction.""" return self._direction @property def speed(self): """Getter for speed.""" return self._speed @direction.setter def direction(self, x): self._direction = x self._update_movement_vector() @speed.setter def speed(self, x): """speed in pix per second""" self._speed = x self._update_movement_vector() def _update_movement_vector(self): if self._north_up_clockwise: direction = 450 - self._direction else: direction = self._direction angle = direction * (math.pi) / 180 speed = self._speed / float(1000) self._movement_vector = (speed * math.cos(angle), speed * math.sin(angle))