def _read_bool(self, channel, invert=False, event=None, **kwargs): """ Read a boolean value from a channel or group of channels Parameters ---------- channel: string a channel or group of channels that will all be read at the same time invert: bool whether or not to invert the read value event: dict a dictionary of event information to emit after a True reading Returns ------- The value read from the hardware """ if channel not in self.tasks: raise NIDAQmxError("Channel(s) %s not yet configured" % str(channel)) task = self.tasks[channel] task.start() # while task.get_samples_per_channel_acquired() == 0: # pass value, bits_per_sample = task.read(1) value = value[0, 0] task.stop() if invert: value = 1 - value value = bool(value == 1) if value: events.write(event) return value
def _queue_wav(self, wav_file, start=False, event=None, **kwargs): """ Queue the wav file for playback Parameters ---------- wav_file: string Path to the wave file to load start: bool Whether or not to immediately start playback event: dict a dictionary of event information to emit just before playback """ if self.wf is not None: self._stop_wav() events.write(event) logger.debug("Queueing wavfile %s" % wav_file) self._wav_data = self._load_wav(wav_file) if self._analog_event_handler is not None: # Get the string of (scaled) bits from the event handler bit_string = self._analog_event_handler.to_bit_sequence(event) # multi-channel outputs need to be of shape nsamples x nchannels if len(self._wav_data.shape) == 1: values = self._wav_data.reshape((-1, 1)) else: values = self._wav_data # Add a channel of all zeros self._wav_data = np.hstack([values, np.zeros((values.shape[0], 1))]) # Place the bit string at the start self._wav_data[:len(bit_string), -1] = bit_string self._get_stream(start=start, **kwargs)
def _read_analog(self, channel, nsamples, event=None, **kwargs): """ Read from a channel or group of channels for the specified number of samples. Parameters ---------- channel: string a channel or group of channels that will be read at the same time nsamples: int the number of samples to read event: dict a dictionary of event information to emit after reading Returns ------- a numpy array of the data that was read """ if channel not in self.tasks: raise NIDAQmxError("Channel(s) %s not yet configured" % str(channel)) task = self.tasks[channel] task.configure_timing_sample_clock(source=self.clock_channel, rate=self.samplerate, sample_mode="finite", samples_per_channel=nsamples) values = task.read(nsamples) events.write(event) return values
def _write_analog(self, channel, values, is_blocking=False, event=None, **kwargs): """ Write a numpy array of float64 values to the buffer on a channel or group of channels Parameters ---------- channel: string a channel or group of channels that will all be written to at the same time values: numpy array of float64 values values to write to the hardware. Should be of dimension nchannels x nsamples. is_blocking: bool whether or not to block execution until all samples are written to the hardware event: dict a dictionary of event information to emit just before writing Returns ------- True """ if channel not in self.tasks: raise NIDAQmxError("Channel(s) %s not yet configured" % str(channel)) task = self.tasks[channel] task.stop() task.configure_timing_sample_clock(source=self.clock_channel, rate=self.samplerate, sample_mode="finite", samples_per_channel=values.shape[0]) if self._analog_event_handler is not None: # Get the string of (scaled) bits from the event handler bit_string = self._analog_event_handler.to_bit_sequence(event) # multi-channel outputs need to be of shape nsamples x nchannels if len(values.shape) == 1: values = values.reshape((-1, 1)) # Add a channel of all zeros values = np.hstack([values, np.zeros((values.shape[0], 1))]) # Place the bit string at the start values[:len(bit_string), -1] = bit_string # Write the values to the nidaq buffer # I think we might want to set layout='group_by_scan_number' in .write() task.write(values, auto_start=False) events.write(event) task.start() if is_blocking: task.wait_until_done() task.stop() return True
def _stop_wav(self, event=None, **kwargs): try: logger.debug("Attempting to close pyaudio stream") events.write(event) self.stream.close() logger.debug("Stream closed") except AttributeError: self.stream = None try: self.wf.close() except AttributeError: self.wf = None
def _read_bool(self, channel, invert=False, event=None, **kwargs): """ Read a value from the specified channel Parameters ---------- channel: int the channel from which to read invert: bool whether or not to invert the read value Returns ------- bool: the value read from the hardware Raises ------ ArduinoException Reading from the device failed. """ if channel not in self._state: raise InterfaceError("Channel %d is not configured on device %s" % (channel, self.device_name)) if self.device.inWaiting() > 0: # There is currently data in the input buffer self.device.flushInput() self.device.write(self._make_arg(channel, 0)) # Also need to make sure self.device.read() returns something that ord can work with. Possibly except TypeError while True: try: v = ord(self.device.read()) break # serial.SerialException("Testing") except serial.SerialException: # This is to make it robust in case it accidentally disconnects or you try to access the arduino in # multiple ways pass except TypeError: ArduinoException("Could not read from arduino device") logger.debug("Read value of %d from channel %d on %s" % (v, channel, self)) if v in [0, 1]: if invert: v = 1 - v value = v == 1 if value: events.write(event) return value else: logger.error("Device %s returned unexpected value of %d on reading channel %d" % (self, v, channel))
def _play_wav(self, is_blocking=False, event=None, **kwargs): """ Play the data that is currently in the buffer Parameters ---------- is_blocking: bool Whether or not to play the sound in blocking mode event: dict a dictionary of event information to emit just before playback """ logger.debug("Playing wavfile") events.write(event) self.stream.start() if is_blocking: self.wait_until_done()
def _write_bool(self, channel, value, event=None, **kwargs): '''Write a value to the specified channel :param channel: the channel to write to :param value: the value to write :return: value written if succeeded ''' if channel not in self._state: raise InterfaceError("Channel %d is not configured on device %s" % (channel, self)) logger.debug("Writing %s to device %s, channel %d" % (value, self, channel)) events.write(event) if value: s = self.device.write(self._make_arg(channel, 1)) else: s = self.device.write(self._make_arg(channel, 2)) if s: return value else: raise InterfaceError('Could not write to serial device %s, channel %d' % (self.device, channel))
def _stop_wav(self, event=None, **kwargs): """ Stop the current playback and clear the buffer Parameters ---------- event: dict a dictionary of event information to emit just before stopping """ try: logger.debug("Attempting to close stream") events.write(event) self.stream.stop() logger.debug("Stream closed") except AttributeError: self.stream = None try: self.wf.close() except AttributeError: self.wf = None self._wav_data = None
def _write_bool(self, channel, value, event=None, **kwargs): """ Write a boolean value to a channel or group of channels Parameters ---------- channel: string a channel or group of channels that will all be written to at the same time value: bool or boolean array value to write to the hardware event: dict a dictionary of event information to emit just before writing Returns ------- True """ if channel not in self.tasks: raise NIDAQmxError("Channel(s) %s not yet configured" % str(channel)) task = self.tasks[channel] events.write(event) task.write(value, auto_start=True) return True
def _queue_wav(self, wav_file, start=False, event=None, **kwargs): """ Queue the wav file for playback Parameters ---------- wav_file: string Path to the wave file to load start: bool Whether or not to immediately start playback event: dict a dictionary of event information to emit just before playback """ if self.wf is not None: self._stop_wav() events.write(event) logger.debug("Queueing wavfile %s" % wav_file) self._wav_data = self._load_wav(wav_file) if self._analog_event_handler is not None: # Get the string of (scaled) bits from the event handler bit_string = self._analog_event_handler.to_bit_sequence(event) # multi-channel outputs need to be of shape nsamples x nchannels if len(self._wav_data.shape) == 1: values = self._wav_data.reshape((-1, 1)) else: values = self._wav_data # Add a channel of all zeros self._wav_data = np.hstack( [values, np.zeros((values.shape[0], 1))]) # Place the bit string at the start self._wav_data[:len(bit_string), -1] = bit_string self._get_stream(start=start, **kwargs)
def _poll(self, channel=None, invert=False, last_value=False, suppress_longpress=False, timeout=None, wait=None, event=None, *args, **kwargs): """ Runs a loop, querying for the boolean input to return True. Parameters ---------- channel: default channel argument to pass to _read_bool() invert: bool whether or not to invert the read value last_value: bool if the last read value was True. Necessary to suppress longpresses suppress_longpress: bool if True, attempts to suppress returning immediately if the button is still being pressed since the last call. If last_value is True, then it waits until the interface reads a single False value before allowing it to return. timeout: float the time, in seconds, until polling times out. Defaults to no timeout. wait: float the time, in seconds, to wait between subsequent reads (default no wait). Returns ------- timestamp of True read or None if timed out """ logger.debug("Begin polling from device %s" % self.device_name) if timeout is not None: start = time.time() if channel not in self.tasks: raise NIDAQmxError("Channel(s) %s not yet configured" % str(channel)) task = self.tasks[channel] task.start() while True: # Read the value - cannot use _read_bool because it must start and stop the task each time. value, bits_per_sample = task.read(1) value = value[0, 0] if invert: value = 1 - value value = bool(value == 1) if value: events.write(event) if not isinstance(value, bool): task.stop() raise ValueError("Polling for bool returned something that was not a bool") if value is True: if (last_value is False) or (suppress_longpress is False): logger.debug("Input detected. Returning") task.stop() return datetime.datetime.now() else: last_value = False if timeout is not None: if time.time() - start >= timeout: logger.debug("Polling timed out. Returning") task.stop() return None if wait is not None: utils.wait(wait)
def run(self): """ Runs the trial Summary ------- The main structure is as follows: Get stimulus -> Initiate trial -> Play stimulus -> Receive response -> Consequate response -> Finish trial -> Save data. The stimulus, response and consequate stages are broken into pre, main, and post stages. Only use the stages you need in your experiment. """ self.experiment.this_trial = self # Get the stimulus self.stimulus = self.condition.get() # Any pre-trial logging / computations self.experiment.trial_pre() # Emit trial event self.event.update(action="start", metadata=str(self.index)) events.write(self.event) # Record the trial time self.time = dt.datetime.now() # Perform stimulus playback self.experiment.stimulus_pre() self.experiment.stimulus_main() self.experiment.stimulus_post() # Evaluate subject's response self.experiment.response_pre() self.experiment.response_main() self.experiment.response_post() # Consequate the response with a reward, punishment or neither if self.response == self.condition.response: self.correct = True if self.condition.is_rewarded and self.block.reinforcement.consequate( self): self.reward = True self.experiment.reward_pre() self.experiment.reward_main() self.experiment.reward_post() else: self.correct = False if self.condition.is_punished and self.block.reinforcement.consequate( self): self.punish = True self.experiment.punish_pre() self.experiment.punish_main() self.experiment.punish_post() # Emit trial end event self.event.update(action="end", metadata=str(self.index)) events.write(self.event) # Finalize trial self.experiment.trial_post() # Store trial data self.experiment.subject.store_data(self) # Update session schedulers self.experiment.session.update() if self.experiment.check_session_schedule() is False: logger.debug("Session has run long enough. Ending") raise EndSession
def _play_wav(self, event=None, **kwargs): logger.debug("Playing wavfile") events.write(event) self.stream.start_stream()
def run(self): """ Runs the trial Summary ------- The main structure is as follows: Get stimulus -> Initiate trial -> Play stimulus -> Receive response -> Consequate response -> Finish trial -> Save data. The stimulus, response and consequate stages are broken into pre, main, and post stages. Only use the stages you need in your experiment. """ self.experiment.this_trial = self # Get the stimulus self.stimulus = self.condition.get() # Any pre-trial logging / computations self.experiment.trial_pre() # Emit trial event self.event.update(action="start", metadata=str(self.index)) events.write(self.event) # Record the trial time self.time = dt.datetime.now() # Perform stimulus playback self.experiment.stimulus_pre() self.experiment.stimulus_main() self.experiment.stimulus_post() # Evaluate subject's response self.experiment.response_pre() self.experiment.response_main() self.experiment.response_post() # Consequate the response with a reward, punishment or neither if self.response == self.condition.response: self.correct = True if self.condition.is_rewarded and self.block.reinforcement.consequate(self): self.reward = True self.experiment.reward_pre() self.experiment.reward_main() self.experiment.reward_post() else: self.correct = False if self.condition.is_punished and self.block.reinforcement.consequate(self): self.punish = True self.experiment.punish_pre() self.experiment.punish_main() self.experiment.punish_post() # Emit trial end event self.event.update(action="end", metadata=str(self.index)) events.write(self.event) # Finalize trial self.experiment.trial_post() # Store trial data self.experiment.subject.store_data(self) # Update session schedulers self.experiment.session.update() if self.experiment.check_session_schedule() is False: logger.debug("Session has run long enough. Ending") raise EndSession
def _poll(self, channel=None, invert=False, last_value=False, suppress_longpress=False, timeout=None, wait=None, event=None, *args, **kwargs): """ Runs a loop, querying for the boolean input to return True. Parameters ---------- channel: default channel argument to pass to _read_bool() invert: bool whether or not to invert the read value last_value: bool if the last read value was True. Necessary to suppress longpresses suppress_longpress: bool if True, attempts to suppress returning immediately if the button is still being pressed since the last call. If last_value is True, then it waits until the interface reads a single False value before allowing it to return. timeout: float the time, in seconds, until polling times out. Defaults to no timeout. wait: float the time, in seconds, to wait between subsequent reads (default no wait). Returns ------- timestamp of True read or None if timed out """ logger.debug("Begin polling from device %s" % self.device_name) if timeout is not None: start = time.time() if channel not in self.tasks: raise NIDAQmxError("Channel(s) %s not yet configured" % str(channel)) task = self.tasks[channel] task.start() while True: # Read the value - cannot use _read_bool because it must start and stop the task each time. value, bits_per_sample = task.read(1) value = value[0, 0] if invert: value = 1 - value value = bool(value == 1) if value: events.write(event) if not isinstance(value, bool): task.stop() raise ValueError( "Polling for bool returned something that was not a bool") if value is True: if (last_value is False) or (suppress_longpress is False): logger.debug("Input detected. Returning") task.stop() return datetime.datetime.now() else: last_value = False if timeout is not None: if time.time() - start >= timeout: logger.debug("Polling timed out. Returning") task.stop() return None if wait is not None: utils.wait(wait)