def test_noise_signal(f_samp): """ Generate a white noise signal and a white noise with a fade-in and fade-out. Check the generated voltage with an oscilloscope. """ try: gvs = GVS(max_voltage=1.0) gvs.connect(physical_channel_name="cDAQ1Mod1/ao0") except: return # white noise make_stim = GenStim(f_samp) make_stim.noise(10.0, 0.3) samples = make_stim.stim # gvs.write_to_channel(samples) # white noise with fade-in/fade-out make_stim.fade(f_samp * 3.0) faded_samples = make_stim.stim print("start galvanic stim") gvs.write_to_channel(faded_samples) print("end galvanic stim") gvs.quit() return samples, faded_samples
def habituation_signal(): """ Generate a habituation signal with a slow ramp """ amp = 2.0 duration = 25.0 f_samp = 1e3 frequency = 1.0 ramp_length = 10.0 buffer_size = int(duration * f_samp) gvs = GVS(max_voltage=amp) timing = {"rate": f_samp, "samps_per_chan": buffer_size} connected = gvs.connect("cDAQ1Mod1/ao0", **timing) # step stimulus make_stim = GenStim(f_samp=f_samp) make_stim.step(duration, amp) make_stim.fade(f_samp * ramp_length) gvs_wave = make_stim.stim # make_stim = GenStim(f_samp=f_samp) # make_stim.sine(duration, amp, frequency) # gvs_wave = make_stim.stim if connected: print("start galvanic stim") gvs.write_to_channel(gvs_wave) print("end galvanic stim") gvs.quit() return gvs_wave
def test_signal(): """ Generate a signal with an alternating step from 0 V to 1 V and to -1 V. Check the generated voltage with an oscilloscope. """ gvs = GVS(max_voltage=3.0) connected = gvs.connect("cDAQ1Mod1/ao0") if connected: samples = np.concatenate((np.zeros(500), np.ones(1000), np.zeros(500))) samples = np.concatenate((samples, -samples, samples, -samples)) gvs.write_to_channel(samples) gvs.quit()
def test_signal(): """ Generate a signal with an alternating step from 0 V to 1 V and to -1 V. Check the generated voltage with an oscilloscope. """ gvs = GVS(max_voltage=3.0) timing = {"rate": 1e3, "samps_per_chan": 8000} physical_channel_name = "cDAQ1Mod1/ao0" connected = gvs.connect(physical_channel_name, **timing) if connected: samples = np.concatenate((np.zeros(500), np.ones(1000), np.zeros(500))) samples = np.concatenate((samples, -samples, samples, -samples)) gvs.write_to_channel(samples) gvs.quit()
def test_dual_channel(): """ Generate a signal with an alternating step from 0 V to 1 V and to -1 V and send it to two channels. Check the generated voltage with an oscilloscope. """ gvs = GVS(max_voltage=3.0) timing = {"rate": 1e3, "samps_per_chan": 8000} physical_channel_name = ["cDAQ1Mod1/ao0", "cDAQ1Mod1/ao1"] connected = gvs.connect(physical_channel_name, **timing) if connected: samples = np.concatenate((np.zeros(500), np.ones(1000), np.zeros(500))) samples1 = np.concatenate((samples, -samples, samples, -samples)) samples2 = np.concatenate((-samples, samples, -samples, samples)) two_chan_samples = np.stack((samples1, samples2), axis=0) gvs.write_to_channel(two_chan_samples) gvs.quit()
def habituation_signal(): """ Generate a habituation signal with a slow ramp """ amp = 1.0 duration = 25.0 f_samp = 1e3 buffer_size = int(duration * f_samp) gvs = GVS(max_voltage=amp) timing = {"rate": f_samp, "samps_per_chan": buffer_size} connected = gvs.connect("cDAQ1Mod1/ao0", **timing) if connected: # white noise with fade-in/fade-out make_stim = genStim(f_samp) make_stim.noise(25.0, amp) make_stim.fade(f_samp * 10.0) faded_samples = make_stim.stim print("start galvanic stim") gvs.write_to_channel(faded_samples) print("end galvanic stim") gvs.quit() return faded_samples
class TestMaxVoltage(unittest.TestCase): def test_upper_lim(self): self.gvs1 = GVS(max_voltage=5.0) self.assertAlmostEqual(self.gvs1.max_voltage, 1.0) self.gvs1.quit() def test_negative_lim(self): self.gvs2 = GVS(max_voltage=-40) self.assertAlmostEqual(self.gvs2.max_voltage, 1.0) self.gvs2.quit() def test_change_upper_lim(self): self.gvs3 = GVS(max_voltage=2.5) self.gvs3.max_voltage = 10 self.assertAlmostEqual(self.gvs3.max_voltage, 1.0) self.gvs3.quit() def test_voltage_below_upper_lim(self): self.gvs4 = GVS(max_voltage=0.5) self.assertAlmostEqual(self.gvs4.max_voltage, 0.5) self.gvs4.quit()
class TestNidaqConnection(unittest.TestCase): def test_connect_single_channel(self): self.gvs1 = GVS() physical_channel_name = "cDAQ1Mod1/ao0" timing = {"rate": 1e3, "samps_per_chan": 8000} connected = self.gvs1.connect(physical_channel_name, **timing) self.assertTrue(connected) self.gvs1.quit() def test_connect_two_channels(self): self.gvs2 = GVS() physical_channel_name = ["cDAQ1Mod1/ao0", "cDAQ1Mod1/ao1"] timing = {"rate": 1e3, "samps_per_chan": 8000} connected = self.gvs2.connect(physical_channel_name, **timing) self.assertTrue(connected) self.gvs2.quit() def test_connect_without_timing_args(self): self.gvs3 = GVS() physical_channel_name = "cDAQ1Mod1/ao0" connected = self.gvs3.connect(physical_channel_name) self.assertTrue(connected) self.gvs3.quit()
class GVSHandler: def __init__(self, param_queue, status_queue, logging_queue, buffer_size): PHYSICAL_CHANNEL_NAME = "cDAQ1Mod1/ao0" SAMPLING_FREQ = 1e3 # I/O queues self.param_queue = param_queue self.status_queue = status_queue self.logging_queue = logging_queue self.stimulus = None # set up logger worker = Worker(logging_queue, formatter, default_logging_level, "GVSHandler") self.logger = worker.logger # second logger to pass to GVS object subworker = Worker(logging_queue, formatter, default_logging_level, "GVS") self.sublogger = subworker.logger # GVS control object self.gvs = GVS(logger=self.sublogger) self.buffer_size = int(buffer_size) timing = {"rate": SAMPLING_FREQ, "samps_per_chan": self.buffer_size} connected = self.gvs.connect(PHYSICAL_CHANNEL_NAME, **timing) if connected: self.logger.info("NIDAQ connection established") self.status_queue.put({"connected": True}) else: self.logger.info("NIDAQ connection failed") self.status_queue.put({"connected": False}) # GVSHandler can't be a subclass of multiprocessing.Process, as the # GVS object contains ctypes pointers and can't be pickled. # GVSHandler's methods can't be accessed from the parent process. # As a workaround, the event loop is started by calling the run method # here at the end of the initialisation. self.run() def run(self): """ Event loop that listens for queue input. Input of type *dict* is used for stimulus creation, input of type *int* is used to trigger onset of GVS stimulation. Input "STOP" to exit the method. This event loop is automatically started after a GVSHandler object is initialised. """ while True: data = self.param_queue.get() if isinstance(data, str) and (data == "STOP"): quit_gvs = self.gvs.quit() if quit_gvs: self.status_queue.put({"quit": True}) else: self.status_queue.put({"quit": False}) break else: if isinstance(data, np.ndarray): self.stimulus = data if self.stimulus is None: self.status_queue.put({"stim_created": False}) else: self.status_queue.put({"stim_created": True}) elif isinstance(data, bool) and (data is True): self._send_stimulus() else: self.logger.error("Incorrect input to GVSHandler parameter" " queue. Input must be a numpy array " "with samples, a boolean, or a " "string STOP to quit.") self.status_queue.put({"stim_created": False}) def _analog_feedback_loop(self, gvs_wave, start_end_blip_voltage=2.5): """ Add a copy of the GVS signal to send to a second channel via the NIDAQ. The copy (but not the GVS signal) has a 2.5 V blip of a single sample at the start and the end, to signal the onset and end of the stimulation. In the signal that is sent to the GVS channel (here: channel A0), the first and last sample are zero. Also, an extra zero sample is added to the end of both signals, to reset the voltage to baseline. :param gvs_wave: GVS signal :param start_end_blip_voltage: voltage to give to first and last as a signal. Voltage should not be present in the rest of the waveform. :return: stacked signal, with second row being the original GVS signal, the first row being the copied signal with first and last sample changed to 2.5 V. """ duplicate_wave = gvs_wave[:] # blip at start and end of copied GVS wave duplicate_wave[0] = start_end_blip_voltage duplicate_wave[-1] = -start_end_blip_voltage # add voltage reset (0 sample) at the end gvs_wave = np.append(gvs_wave, 0) duplicate_wave = np.append(duplicate_wave, 0) stimulus = np.stack((duplicate_wave, gvs_wave), axis=0) return stimulus def _send_stimulus(self): """ Send the stimulus to the GVS channel, check whether all samples were successfully written """ n_samples = None samps_written = 0 # only try to send if there is a stimulus available if self.stimulus is not None: try: # send the GVS onset time to the main process t_start_gvs = time.time() self.status_queue.put({"t_start_gvs": t_start_gvs}) samps_written = self.gvs.write_to_channel( self.stimulus, reset_to_zero_volts=True) t_end_gvs = time.time() if self.stimulus.ndim == 1: n_samples = len(self.stimulus) else: n_samples = np.shape(self.stimulus)[1] # delete stimulus after sending, so that it can only be sent once self.stimulus = None except AttributeError as err: self.logger.error( "Error: tried to send invalid stimulus to NIDAQ." "\nNote that a stimulus instance can only be" " sent once.\nAttributeError: {}".format(err)) self.logger.info("GVS: {} samples written".format(samps_written)) if n_samples == samps_written: self.status_queue.put({"stim_sent": True}) self.status_queue.put({"t_end_gvs": t_end_gvs}) else: self.status_queue.put({"stim_sent": False}) self.status_queue.put({"t_end_gvs": time.time()})
class GVSHandler(): def __init__(self, param_queue, status_queue, logging_queue, buffer_size): PHYSICAL_CHANNEL_NAME = "cDAQ1Mod1/ao0" SAMPLING_FREQ = 1e3 # I/O queues self.param_queue = param_queue self.status_queue = status_queue self.logging_queue = logging_queue # stimulus generation self.makeStim = GenStim(f_samp=SAMPLING_FREQ) self.stimulus = self.makeStim.stim # set up logger worker = Worker(logging_queue, formatter, default_logging_level, "GVSHandler") self.logger = worker.logger # second logger to pass to GVS object subworker = Worker(logging_queue, formatter, default_logging_level, "GVS") self.sublogger = subworker.logger # GVS control object self.gvs = GVS(logger=self.sublogger) self.buffer_size = int(buffer_size) timing = {"rate": SAMPLING_FREQ, "samps_per_chan": self.buffer_size} connected = self.gvs.connect(PHYSICAL_CHANNEL_NAME, **timing) if connected: self.logger.info("NIDAQ connection established") self.status_queue.put({"connected": True}) else: self.logger.info("NIDAQ connection failed") self.status_queue.put({"connected": False}) # GVSHandler can't be a subclass of multiprocessing.Process, as the # GVS object contains ctypes pointers and can't be pickled. # GVSHandler's methods can't be accessed from the parent process. # As a workaround, the event loop is started by calling the run method # here at the end of the initialisation. self.run() def run(self): """ Event loop that listens for queue input. Input of type *dict* is used for stimulus creation, input of type *int* is used to trigger onset of GVS stimulation. Input "STOP" to exit the method. This event loop is automatically started after a GVSHandler object is initialised. """ while True: data = self.param_queue.get() if data == "STOP": quitted = self.gvs.quit() if quitted: self.status_queue.put({"quit": True}) else: self.status_queue.put({"quit": False}) break else: if type(data).__name__ == "dict": self._create_stimulus(params=data) elif (type(data).__name__ == "bool") & (data is True): self._send_stimulus() else: self.logger.error("Incorrect input to GVSHandler parameter" " queue. Input must be a dict with " "parameters specified in GVS.py, a " "boolean, or a string STOP to quit.") self.status_queue.put({"stim_created": False}) def _create_stimulus(self, params=dict): """ Create stimulus array with parameters defined in *params* :param params: (dict) """ if self._check_args(["duration", "amp"], params): options = deepcopy(params) del options["duration"] del options["amp"] if "fade_samples" in options: del options["fade_samples"] self.makeStim.noise(params["duration"], params["amp"], **options) else: self.status_queue.put({"stim_created": False}) return if self._check_args(["fade_samples"], params): self.makeStim.fade(params["fade_samples"]) self.stimulus = self.makeStim.stim self.status_queue.put({"stim_created": True}) def _send_stimulus(self): """ Send the stimulus to the GVS channel, check whether all samples were successfully written """ n_samples = None samps_written = 0 try: samps_written = self.gvs.write_to_channel(self.stimulus, reset_to_zero_volts=True) n_samples = len(self.stimulus) # delete stimulus after sending, so that it can only be sent once self.stimulus = None except AttributeError as err: self.logger.error("Error: tried to send invalid stimulus to NIDAQ." "\nNote that a stimulus instance can only be" " sent once.\nAttributeError: {}".format(err)) self.logger.info("GVS: {} samples written".format(samps_written)) if n_samples == samps_written: self.status_queue.put({"stim_sent": True}) else: self.status_queue.put({"stim_sent": False}) def _check_args(self, keylist, check_dict): return all(key in check_dict for key in keylist)