From e242f1684f6894459807f67e5217a6e77841fc68 Mon Sep 17 00:00:00 2001 From: Florian Rau Date: Tue, 31 Oct 2023 15:32:29 +0000 Subject: [PATCH 1/5] cleanup dead code --- CHANGELOG.md | 1 + scripts/BosaiCallTest.py | 58 -------------- scripts/bonsai_stop.py | 18 ----- scripts/bpod_lights.py | 18 ----- scripts/cleanup.py | 19 ----- scripts/create_custom_project_from_alyx.py | 15 ---- scripts/daq.py | 84 -------------------- scripts/daq_test.py | 91 ---------------------- scripts/plot_certification_bpod_data.py | 24 ------ scripts/post_session_check_sync_pulses.py | 55 ------------- scripts/register_screen_lux.py | 17 ---- scripts/sync_to_alyx.py | 40 ---------- scripts/wheel_positions.py | 78 ------------------- 13 files changed, 1 insertion(+), 517 deletions(-) delete mode 100644 scripts/BosaiCallTest.py delete mode 100644 scripts/bonsai_stop.py delete mode 100644 scripts/bpod_lights.py delete mode 100644 scripts/cleanup.py delete mode 100644 scripts/create_custom_project_from_alyx.py delete mode 100644 scripts/daq.py delete mode 100644 scripts/daq_test.py delete mode 100644 scripts/plot_certification_bpod_data.py delete mode 100644 scripts/post_session_check_sync_pulses.py delete mode 100644 scripts/register_screen_lux.py delete mode 100644 scripts/sync_to_alyx.py delete mode 100644 scripts/wheel_positions.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a2eafcef..cc83aa50d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ Changelog --------- + 8.12.1 ------ * bugfix: remember ability for setting the status LED diff --git a/scripts/BosaiCallTest.py b/scripts/BosaiCallTest.py deleted file mode 100644 index 0e63f5d8c..000000000 --- a/scripts/BosaiCallTest.py +++ /dev/null @@ -1,58 +0,0 @@ -import logging -import os -import subprocess -from pathlib import Path - -log = logging.getLogger("iblrig") - -IBLRIG_FOLDER = r"C:\iblrig" -CWD = os.getcwd() -BONSAI_FOLDER = Path(IBLRIG_FOLDER) / "Bonsai" - -bns = BONSAI_FOLDER / "Bonsai64.exe" -if bns.exists(): - bns = str(bns) -else: - bns = str(BONSAI_FOLDER / "Bonsai.exe") - -certification_folder = Path(IBLRIG_FOLDER) / "visual_stim" / "ephys_certification" -wrkfl = str(certification_folder / "ephys_certification.bonsai") -SESSION_RAW_DATA_FOLDER = certification_folder - -# Flags -noedit = "--no-editor" # implies start and no-debug? -noboot = "--no-boot" -start = "--start" -# Properties -SA0_DueTime = "-p:SpontaneousActivity0.DueTime=00:15:00" -ODS0_Count = "-p:OrientationDirectionSelectivityStim0.Count=20" -RFM_FileName = "-p:ReceptiveFieldMappingStim.FileNameRFMapStim=" + str( - Path(SESSION_RAW_DATA_FOLDER) / "_iblrig_RFMapStim.raw.bin" -) -RFM_MappingTime = "-p:ReceptiveFieldMappingStim.MappingTime=00:10:00" -CRCS_CheckerboardTime = "-p:ContrastReversingCheckerboardStim.CheckerboardTime=00:03:00" -CSTS_StimFileName = "-p:ContrastSelectivityTaskStim.StimFileName=" + str( - certification_folder / "Extensions" / "stims.csv" -) -SA1_DueTime = "-p:SpontaneousActivity1.DueTime=00:15:00" -ODS1_Count = "-p:OrientationDirectionSelectivityStim1.Count=20" - -cmd = [ - bns, - wrkfl, - noboot, - noedit, - SA0_DueTime, - SA1_DueTime, - RFM_FileName, - ODS0_Count, - ODS1_Count, - RFM_MappingTime, - CRCS_CheckerboardTime, - CSTS_StimFileName, -] - -os.chdir(certification_folder) -s = subprocess.run(cmd, stdout=subprocess.PIPE) # locking call -os.chdir(CWD) -log.info("You're done, please remove the mouse.\n" * 42) diff --git a/scripts/bonsai_stop.py b/scripts/bonsai_stop.py deleted file mode 100644 index 536b7bb5b..000000000 --- a/scripts/bonsai_stop.py +++ /dev/null @@ -1,18 +0,0 @@ -# import iblrig.bonsai as bonsai -# TODO: check if this works! -from pythonosc import udp_client -import sys - - -if __name__ == "__main__": - OSC_CLIENT_IP = "127.0.0.1" - OSC_CLIENT_PORT = int(sys.argv[1]) - osc_client = udp_client.SimpleUDPClient(OSC_CLIENT_IP, OSC_CLIENT_PORT) - osc_client.send_message("/x", 1) - - # stim = bonsai.osc_client('stim') - # camera = bonsai.osc_client('camera') - # mic = bonsai.osc_client('mic') - # stim.send_message("/x", 1) - # camera.send_message("/x", 1) - # mic.send_message("/x", 1) diff --git a/scripts/bpod_lights.py b/scripts/bpod_lights.py deleted file mode 100644 index 28cab4dce..000000000 --- a/scripts/bpod_lights.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python -# -*- coding:utf-8 -*- -# @Author: Niccolò Bonacchi -# @Date: Monday, February 18th 2019, 1:46:37 pm -import sys - -import iblrig.params as params -from iblrig.bpod_helper import bpod_lights - -if __name__ == "__main__": - if len(sys.argv) == 2: - comport = params.get_board_comport() - command = sys.argv[1] - else: - comport = sys.argv[1] - command = sys.argv[2] - - bpod_lights(comport, int(command)) diff --git a/scripts/cleanup.py b/scripts/cleanup.py deleted file mode 100644 index 19d0c6d33..000000000 --- a/scripts/cleanup.py +++ /dev/null @@ -1,19 +0,0 @@ -""" -Removes pybpod data files from setup folders. -Data and settings from pybpod data files is in ibl data files. -Assumes it's called as a post command from task folder -""" -from pathlib import Path -import os - - -IBLRIG_FOLDER = Path(__file__).absolute().parent.parent -IBLRIG_PARAMS_FOLDER = IBLRIG_FOLDER.parent / "iblrig_params" - -experiments_folder = IBLRIG_PARAMS_FOLDER / "IBL" / "experiments" - -sess_folders = experiments_folder.rglob("sessions") - -for s in sess_folders: - if "setups" in str(s): - os.system(f"rd /s /q {str(s)}") diff --git a/scripts/create_custom_project_from_alyx.py b/scripts/create_custom_project_from_alyx.py deleted file mode 100644 index f7afcf9f9..000000000 --- a/scripts/create_custom_project_from_alyx.py +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env python -# -*- coding:utf-8 -*- -# @File: scripts/create_custom_project_from_alyx.py -# @Author: Niccolo' Bonacchi (@nbonacchi) -# @Date: Tuesday, August 17th 2021, 5:21:16 pm -import iblrig.pybpod_config as pc -import argparse - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description="Create custom project, users, and subjects from Alyx" - ) - parser.add_argument("project_name", help="Name of existing Alyx project") - args = parser.parse_args() - pc.create_custom_project_from_alyx(args.project_name) diff --git a/scripts/daq.py b/scripts/daq.py deleted file mode 100644 index bfe911fb4..000000000 --- a/scripts/daq.py +++ /dev/null @@ -1,84 +0,0 @@ -import time - -import nidaqmx -import numpy as np -from nidaqmx.constants import AcquisitionType, Edge, TerminalConfiguration, VoltageUnits -from pathlib import Path -import os - - -# %matplotlib auto -# fig = plt.figure() -# ax1 = fig.add_subplot(211) -# ax2 = fig.add_subplot(212) - -fpath = r"C:\Users\User\Desktop\scratch_bonsai-harp\NIdata.bin" -data = np.fromfile(fpath, dtype=np.float64) - -nsamples = 1024 -sample_frequency = 1000 -# freqs = np.linspace(0, int(sample_frequency / 2), int(nsamples / 2 + 1)) - -# task = nidaqmx.Task() -# with nidaqmx.Task() as task: -# task.di_channels.add_di_chan( -# "Dev1/port0/line0", -# ) -# task.timing.cfg_samp_clk_timing( -# sample_frequency, active_edge=Edge.RISING, -# sample_mode=AcquisitionType.CONTINUOUS, samps_per_chan=nsamples -# ) -# # task.start() -# # task.stop() - - -with nidaqmx.Task() as task: - task.ai_channels.add_ai_voltage_chan( - "Dev1/ai0", - terminal_config=TerminalConfiguration.RSE, - min_val=-5.0, - max_val=5.0, - units=VoltageUnits.VOLTS, - ) - task.timing.cfg_samp_clk_timing( - sample_frequency, - active_edge=Edge.RISING, - sample_mode=AcquisitionType.CONTINUOUS, - samps_per_chan=1000, - ) - - fpath = Path(r"C:\iblrig_data\test.npy") - with fpath.open("ab") as f: - # task.start() - # task.stop() - tstart = time.time() - tend = time.time() - while tend - tstart < 4: - t1 = time.time() - data = task.read(number_of_samples_per_channel=4096) - t2 = tend = time.time() - tdata = np.array(data) - np.save(f, tdata) - # tdata -= np.mean(tdata) - # ffto = np.fft.fft(tdata) - # ffto = np.abs(ffto) - # ffto = ffto[0 : (int(nsamples / 2) + 1)] - - # ax1.clear() - # ax2.clear() - # ax1.plot(data) - # ax2.plot(freqs, ffto) - # fig.show(0) - # plt.pause(0.000000001) - # t3 = int(round(time.time() * 1000)) - print(f"t2-t1 = {t2 - t1}s") - - -with fpath.open("rb") as f: - fsz = os.fstat(f.fileno()).st_size - out = np.load(f) - while f.tell() < fsz: - out = np.vstack((out, np.load(f))) - -fpath.unlink() -print(out.shape) diff --git a/scripts/daq_test.py b/scripts/daq_test.py deleted file mode 100644 index 46bba20b2..000000000 --- a/scripts/daq_test.py +++ /dev/null @@ -1,91 +0,0 @@ -import nidaqmx -from nidaqmx.constants import ( - AcquisitionType, - Edge, - TerminalConfiguration, - VoltageUnits, -) # Signal - -import numpy as np - -device = "Dev1" -sampling_freq = 1000 -buffer_size = 1000 -global out -out = np.array([]) - - -def callback(task_handle, every_n_samples_event_type, number_of_samples, callback_data): - global out - print(task_handle) - print(every_n_samples_event_type) - print(number_of_samples) - print(callback_data) - samples = task.read(number_of_samples) - out = np.append(out, samples, axis=None) - print(f"{int(len(out) / number_of_samples)}...", end="", flush=True) - return 0 - - -def digital_callback(task_handle, signal_type, callback_data): - print(task_handle, signal_type, callback_data) - samples = task.read(buffer_size) - out.extend(samples) - print(f"{int(len(out)/buffer_size)}...", end="", flush=True) - return 0 - - -def done_callback(task_handle, status, callback_data): - print("Status", status) - return 0 - - -if __name__ == "__main__": - - # Set pmt gain - # pmt_gain = 0.5 - # with nidaqmx.Task() as task: - # task.ao_channels.add_ao_voltage_chan(f"/{device}/ao1","",0,1.25) - # task.write(pmt_gain) - - # Start recording task - task = nidaqmx.Task(new_task_name="ReaderTask") - - task.ai_channels.add_ai_voltage_chan( - f"/{device}/ai0", - name_to_assign_to_channel="", - terminal_config=TerminalConfiguration.DEFAULT, - min_val=0, - max_val=10, - units=VoltageUnits.VOLTS, - custom_scale_name="", - ) - - task.timing.cfg_samp_clk_timing( - sampling_freq, - source="", - active_edge=Edge.RISING, - sample_mode=AcquisitionType.CONTINUOUS, - samps_per_chan=buffer_size, - ) - task.register_every_n_samples_acquired_into_buffer_event(buffer_size, callback) - task.register_done_event(done_callback) - - task.start() - input("Running task. Press Enter to stop. Seconds elapsed: \n") - task.stop() - - print(len(out)) - print(len(out) % buffer_size) - - # out = {'data': [], 'n': 0} - # task = nidaqmx.Task() - - # task.di_channels.add_di_chan("Dev1/port0/line0") - # task.register_signal_event(Signal.SAMPLE_CLOCK, digital_callback) - # task.timing.cfg_samp_clk_timing( - # sampling_freq, - # source="", - # active_edge=Edge.RISING, - # sample_mode= AcquisitionType.FINITE, - # samps_per_chan=1000) diff --git a/scripts/plot_certification_bpod_data.py b/scripts/plot_certification_bpod_data.py deleted file mode 100644 index 608bff7e7..000000000 --- a/scripts/plot_certification_bpod_data.py +++ /dev/null @@ -1,24 +0,0 @@ -import json - -import matplotlib.pyplot as plt -import numpy as np - -spath = r"C:\iblrig_data\Subjects\_iblrig_calibration\2019-10-09\005" -fpath = spath + r"\raw_behavior_data\_iblrig_taskData.raw.jsonable" - -with open(fpath, "r") as f: - data = json.load(f) - -# data = {'Bpod start timestamp': 0.334712, 'Trial start timestamp': 0.334712, 'Trial end timestamp': 119.658717, 'States timestamps': {'trial_start': [(0, 0.0001)], 'listen': [(0.0001, 111.54180000000001)]}, 'Events timestamps': {'Tup': [0.0001], 'BNC1High': [7.8509, 7.9339, 8.3339, 9.9504, 11.567, 11.9669, 12.0668, 14.1333, 14.1661, 14.2665, 14.6667, 16.2998, 17.8829, 18.2829, 18.3827, 19.4159, 22.4322, 25.448700000000002, 28.465, 31.4812, 34.5144, 37.5306, 40.5305, 42.6129, 42.7135, 43.113600000000005, 44.7132, 46.3299, 46.7299, 46.8296, 46.9132, 48.929, 49.029500000000006, 49.4296, 51.0461, 52.645900000000005, 53.0458, 53.1458, 54.195800000000006, 55.212, 55.245000000000005, 55.3457, 55.745400000000004, 57.362, 58.9619, 59.361900000000006, 59.461800000000004, 60.5118, 63.511500000000005, 66.4945, 69.4941, 72.4939, 72.5432, 72.6606, 73.04390000000001, 74.6769, 76.2602, 76.6769, 76.7765, 78.8264, 78.8592, 78.9598, 79.3598, 80.9925, 82.5926, 82.9929, 83.0926, 84.1256, 87.1421, 90.1419, 93.17500000000001, 96.19120000000001, 99.2077, 102.22370000000001, 105.2406, 107.3229, 107.42360000000001, 107.8236, 109.44000000000001, 111.0398, 111.4398, 111.5224], 'BNC1Low': [7.8695, 8.0699, 8.8698, 11.0196, 11.8359, 12.036200000000001, 12.085700000000001, 14.1521, 14.2025, 14.402600000000001, 15.2192, 17.3522, 18.1519, 18.352, 18.4017, 21.451900000000002, 24.4682, 27.4846, 30.5008, 33.534, 36.533500000000004, 39.549800000000005, 42.5996, 42.6495, 42.8495, 43.649300000000004, 45.7992, 46.5991, 46.7991, 46.848800000000004, 48.9155, 48.965500000000006, 49.165600000000005, 49.9656, 52.1152, 52.915, 53.115100000000005, 53.1646, 55.182300000000005, 55.2312, 55.281600000000005, 55.4816, 56.2815, 58.431400000000004, 59.2312, 59.431200000000004, 59.4808, 62.514100000000006, 65.4972, 68.49680000000001, 71.49640000000001, 72.52980000000001, 72.5798, 72.7797, 73.5963, 75.7461, 76.5459, 76.7461, 76.7955, 78.84530000000001, 78.89580000000001, 79.0958, 79.8956, 82.062, 82.8619, 83.06190000000001, 83.11160000000001, 86.16170000000001, 89.178, 92.1945, 95.2107, 98.227, 101.2433, 104.25970000000001, 107.3096, 107.35940000000001, 107.5596, 108.3592, 110.5091, 111.30900000000001, 111.509, 111.54180000000001]}} # noqa -ev = data["Events timestamps"] -high = ev["BNC1High"] -low = ev["BNC1Low"] -bla = [] -bla.extend(high) -bla.extend(low) -bla = sorted(bla) -plt.plot(bla, np.ones(len(bla)), ".") -plt.plot(high, np.ones(len(high)) + 0.001, "^") -plt.plot(low, np.ones(len(low)) - 0.001, "v") -plt.ylim([0.95, 1.05]) -plt.show() diff --git a/scripts/post_session_check_sync_pulses.py b/scripts/post_session_check_sync_pulses.py deleted file mode 100644 index 8521f2760..000000000 --- a/scripts/post_session_check_sync_pulses.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env python -# @Author: Niccolò Bonacchi -# @Creation_Date: Thursday, February 21st 2019, 7:13:37 pm -# @Editor: Michele Fabbri -# @Edit_Date: 2022-02-01 -import sys -from pathlib import Path - -import iblrig.raw_data_loaders as raw -import matplotlib.pyplot as plt -import numpy as np -from iblrig.misc import get_port_events - -if __name__ == "__main__": - if len(sys.argv) == 1: - print("I need a file name...") - session_data_file = Path(sys.argv[1]) - if not session_data_file.exists(): - raise FileNotFoundError(f"{session_data_file}") - if session_data_file.name.endswith(".jsonable"): - data = raw.load_data(session_data_file.parent.parent) - else: - try: - data = raw.load_data(session_data_file) - except Exception: - print("Not a file or a valid session folder") - unsynced_trial_count = 0 - frame2ttl = [] - sound = [] - camera = [] - trial_end = [] - for trial_data in data: - tevents = trial_data["behavior_data"]["Events timestamps"] - ev_bnc1 = get_port_events(tevents, name="BNC1") - ev_bnc2 = get_port_events(tevents, name="BNC2") - ev_port1 = get_port_events(tevents, name="Port1") - if not ev_bnc1 or not ev_bnc2 or not ev_port1: - unsynced_trial_count += 1 - frame2ttl.extend(ev_bnc1) - sound.extend(ev_bnc2) - camera.extend(ev_port1) - trial_end.append(trial_data["behavior_data"]["Trial end timestamp"]) - print(f"Found {unsynced_trial_count} trials with bad sync data") - - f = plt.figure() # figsize=(19.2, 10.8), dpi=100) - ax = plt.subplot2grid((1, 1), (0, 0), rowspan=1, colspan=1) - - ax.plot(camera, np.ones(len(camera)) * 1, "|") - ax.plot(sound, np.ones(len(sound)) * 2, "|") - ax.plot(frame2ttl, np.ones(len(frame2ttl)) * 3, "|") - [ax.axvline(t, alpha=0.5) for t in trial_end] - ax.set_ylim([0, 4]) - ax.set_yticks(range(4)) - ax.set_yticklabels(["", "camera", "sound", "frame2ttl"]) - plt.show() diff --git a/scripts/register_screen_lux.py b/scripts/register_screen_lux.py deleted file mode 100644 index 5b107a9ea..000000000 --- a/scripts/register_screen_lux.py +++ /dev/null @@ -1,17 +0,0 @@ -import iblrig.params as params -import datetime - - -pars = params.load_params_file() -print(f"\nPrevious value on [{pars['SCREEN_LUX_DATE']}] was [{pars['SCREEN_LUX_VALUE']}]") -value = input("\nPlease input the value of the luxometer (lux): ") -pars["SCREEN_LUX_VALUE"] = float(value) -pars["SCREEN_LUX_DATE"] = str(datetime.datetime.now().date()) -print(" Updating local params file...") -lpars = params.update_params_file(pars) - -print( - "\nLux measurement updated on", - f"[{lpars['SCREEN_LUX_DATE']}] with value [{lpars['SCREEN_LUX_VALUE']}]", - "\n", -) diff --git a/scripts/sync_to_alyx.py b/scripts/sync_to_alyx.py deleted file mode 100644 index 0db7c1665..000000000 --- a/scripts/sync_to_alyx.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python -# @File: scripts/sync_to_alyx.py -# @Author: Niccolo' Bonacchi (@nbonacchi) -# @Date: Thursday, May 26th 2022, 1:29:50 pm -import argparse - -import iblrig.pybpod_config as pbc -from iblrig.ibllib_calls import call_one_get_project_data, get_all_subjects_from_project - - -def main(project: str, lab: str) -> None: - """Substitutes the deprecated sync to alyx module of pybpod - - Args: - project (str): The project name in pybpod to receive users and subjects - lab (str): The labname of the subjects - - Especially for platform projects subjects are numerous, restricting by those in current lab - Avoids downloading too much useless subject references and speeds up the query - """ - # The project in pybpod where to add the subjects - project_name = pbc.local2alyx_names[project] - # Get the subjects you want to add from Alyx and save them to disk - call_one_get_project_data(project_name, lab=lab) - # Load all the subjects queried from Alyx - subjects = get_all_subjects_from_project(project_name) - # Filter for alive subjects - alive_subjects = [x for x in subjects if x.get("alive")] - # Create and entry in the pybpod GUI for each subject - for asub in alive_subjects: - pbc._create_and_patch_subject(project_name, asub) - # Create pybpod users from Alyx users - pbc.create_local_users_from_alyx_project(project_name) - - -if __name__ == "__main__": - ap = argparse.ArgumentParser() - ap.add_argument("-p", "--project", required=True, help="The project to add subjects") - ap.add_argument("-l", "--lab", help="The lab to query subjects", default="mainenlab") - main(**vars(ap.parse_args())) diff --git a/scripts/wheel_positions.py b/scripts/wheel_positions.py deleted file mode 100644 index 5b1589419..000000000 --- a/scripts/wheel_positions.py +++ /dev/null @@ -1,78 +0,0 @@ -""" -RE spits out ticks [-512, 512] which is a full turn of the wheel -I linearly rescale it to [-180, 180] # wheel degrees -The screen "wants" a positionX from [-1, 1] where 0 is the center of the screen -I I then divide all positions as they are coming by a GainFactor -GainFactor = 1 / (mm_per_deg * UserDefinedGain) -UserDefinedGain = 4.0 -mm_per_deg = (2 * Pi * WheelRadius) / 360 -where WheelRadius = 31mm - -Now that I have the transformation done as if the stimulus would start from the center I need to -offset it by the InitPosition of the stimulus (either -35 or 35) -Then for "safety" I pass an unwrapping function for the cases when the stimulus ight go over the -edge of the screen -I do this in the same go -((InitPosition + out_value) + 180) % 360 - 180 and that is what is sent to the screen... - -((-35 + (1 / (1 / ((math.pi * 2 * 31) / 360) * 4))) + 180) % 360 -180 -""" -import math -import matplotlib.pyplot as plt - - -WHEEL_RADIUS = 31 -USER_DEFINED_GAIN = 4.0 -MM_PER_DEG = (2 * math.pi * WHEEL_RADIUS) / 360 -GAIN_FACTOR = 1 / (MM_PER_DEG * USER_DEFINED_GAIN) - - -def get_scale_shift(from_min, from_max, to_min, to_max): - scale = (to_max - to_min) / (from_max - from_min) - shift = -from_min * scale + to_min - return scale, shift - - -def rescale(input, from_min, from_max, to_min, to_max): - scale, shift = get_scale_shift(from_min, from_max, to_min, to_max) - try: - iter(input) - except TypeError: - input = [input] - - for i in input: - yield i * scale + shift - - -RE_TICKS = range(-512, 512) -RE_TICK_DEG_VALUE = list(rescale(RE_TICKS, -512, 512, -180, 180)) - - -def pos_on_screen(pos, init_pos): - try: - iter(pos) - except TypeError: - pos = [pos] - - for p in pos: - yield (((p / GAIN_FACTOR) + init_pos) + 180) % 360 - 180 - - -relative_wheel_degrees = range(-20, 21) # RE_TICK_DEG_VALUE -absolute_screen_deg_form_left_stim = list(pos_on_screen(relative_wheel_degrees, -35)) -absolute_screen_deg_form_right_stim = list(pos_on_screen(relative_wheel_degrees, 35)) - -ax = plt.subplot(111) -ax.plot( - relative_wheel_degrees, absolute_screen_deg_form_left_stim, c="b", ls="--", marker=".", -) -ax.plot(relative_wheel_degrees, absolute_screen_deg_form_right_stim[::-1], "g.--") -ax.axhline() -ax.axhline(-35) -ax.axhline(35) -ax.axhline(70, c="gray") -ax.axhline(-70, c="gray") -ax.set_xlabel(f"Wheel degrees - Gain = {USER_DEFINED_GAIN}") -ax.set_ylabel(f"Screen degrees - Gain = {USER_DEFINED_GAIN}") -# ax.clear() -plt.show() From 846f2b3b88e591e300bc97574d4464df66f3377d Mon Sep 17 00:00:00 2001 From: Florian Rau Date: Tue, 31 Oct 2023 16:21:33 +0000 Subject: [PATCH 2/5] remove dead code ... --- iblrig/base_tasks.py | 5 +- iblrig/graphic.py | 179 ------------------------------------ iblrig/session_creator.py | 143 +--------------------------- iblrig/spacer.py | 117 ----------------------- iblrig/test/test_spacers.py | 34 ------- 5 files changed, 7 insertions(+), 471 deletions(-) delete mode 100644 iblrig/spacer.py delete mode 100644 iblrig/test/test_spacers.py diff --git a/iblrig/base_tasks.py b/iblrig/base_tasks.py index c4384b271..6fb1f1794 100644 --- a/iblrig/base_tasks.py +++ b/iblrig/base_tasks.py @@ -31,11 +31,11 @@ import iblrig import iblrig.path_helper +from iblutil.spacer import Spacer from iblutil.util import Bunch, setup_logger from iblrig.hardware import Bpod, MyRotaryEncoder, sound_device_factory, SOFTCODE import iblrig.frame2TTL as frame2TTL import iblrig.sound as sound -import iblrig.spacer import iblrig.alyx import iblrig.graphic as graph import ibllib.io.session_params as ses_params @@ -699,8 +699,7 @@ def softcode_handler(code): def send_spacers(self): self.logger.info("Starting task by sending a spacer signal on BNC1") sma = StateMachine(self.bpod) - spacer = iblrig.spacer.Spacer() - spacer.add_spacer_states(sma, next_state="exit") + Spacer().add_spacer_states(sma, next_state="exit") self.bpod.send_state_machine(sma) self.bpod.run_state_machine(sma) # Locks until state machine 'exit' is reached return self.bpod.session.current_trial.export() diff --git a/iblrig/graphic.py b/iblrig/graphic.py index 8e45290e9..279475556 100644 --- a/iblrig/graphic.py +++ b/iblrig/graphic.py @@ -7,17 +7,9 @@ Popup and string input prompts """ import tkinter as tk -from tkinter import messagebox from tkinter import simpledialog -def popup(title, msg): - root = tk.Tk() - root.withdraw() - messagebox.showinfo(title, msg) - root.quit() - - def numinput(title, prompt, default=None, minval=None, maxval=None, nullable=False, askint=False): root = tk.Tk() root.withdraw() @@ -36,174 +28,3 @@ def numinput(title, prompt, default=None, minval=None, maxval=None, nullable=Fal askint=askint, ) return ans - - -def strinput(title, prompt, default="COM", nullable=False): - """ - Example: - >>> strinput("RIG CONFIG", "Insert RE com port:", default="COM") - """ - import tkinter as tk - from tkinter import simpledialog - - root = tk.Tk() - root.withdraw() - ans = simpledialog.askstring(title, prompt, initialvalue=default) - if (ans is None or ans == "" or ans == default) and not nullable: - return strinput(title, prompt, default=default, nullable=nullable) - else: - return ans - - -def login( - title="Enter Credentials", default_username=None, default_passwd=None, add_fields=None, -): - """ - Dialog box prompting for username and password. - - :param title: Window title - :param default_username: default field for username - :param default_passwd: default field for password - :param add_fields: list of new fields to be added to the dialog - :return: - """ - - class Toto: - def __init__( - self, root, default_username=None, default_passwd=None, title=None, add_fields=None, - ): - self.add_fields = add_fields or [] - self.var1 = tk.StringVar() - self.root = root - # self.root.geometry('300x160') - self.root.title(title) - # frame for window margin - self.parent = tk.Frame(self.root, padx=10, pady=10) - self.parent.pack(fill=tk.BOTH, expand=True) - # entrys with not shown text - self.add_entries = [] - for fname in self.add_fields: - self.add_entries.extend([self.make_entry(self.parent, fname + ":", 42, show="")]) - - self.user = self.make_entry( - self.parent, "User name:", 42, show="", default=default_username - ) - self.password = self.make_entry( - self.parent, "Password:", 42, show="*", default=default_passwd - ) - # button to attempt to login - self.button = tk.Button( - self.parent, borderwidth=4, text="Login", width=42, pady=8, command=self.get_value, - ) - self.button.pack(side=tk.BOTTOM) - self.user.focus_set() - self.USR = None - self.MDP = None - self.root.bind("", self.push_enter) - # do not reproduce vim behaviour - self.root.protocol("WM_DELETE_WINDOW", self.cancel_login) - - def make_entry(self, _, caption, width=None, default="", **options): - tk.Label(self.parent, text=caption).pack(side=tk.TOP) - entry = tk.Entry(self.parent, **options) - if width: - entry.config(width=width) - entry.pack(side=tk.TOP, padx=10, fill=tk.BOTH) - if default: - entry.insert(0, default) - return entry - - def push_enter(self, _): - self.get_value() - - def get_value(self): - self.USR = self.user.get() - self.MDP = self.password.get() - self.ADD = [] - for entry in self.add_entries: - self.ADD.extend([entry.get()]) - self.root.destroy() - self.root.quit() - - def cancel_login(self): - self.USR = None - self.MDP = None - self.ADD = [] - for entry in self.add_entries: - self.ADD.extend([None]) - self.root.destroy() - self.root.quit() - - root = tk.Tk() - toto = Toto( - root, - title=title, - default_passwd=default_passwd, - default_username=default_username, - add_fields=add_fields, - ) - root.mainloop() - return [toto.USR] + [toto.MDP] + toto.ADD - - -def multi_input(title="Enter Credentials", add_fields=None, defaults=None): - class Toto: - def __init__(self, root, title=None, add_fields=None, defaults=None): - self.fields_to_add = add_fields or [] - if defaults is None or len(defaults) != len(add_fields): - self.defaults = [None for x in self.fields_to_add] - else: - self.defaults = defaults - self.var1 = tk.StringVar() - self.root = root - # self.root.geometry('300x160') - self.root.title(title) - # frame for window margin - self.parent = tk.Frame(self.root, padx=10, pady=10) - self.parent.pack(fill=tk.BOTH, expand=True) - # entrys with not shown text - self.add_entries = [] - for fname, fdef in zip(self.fields_to_add, self.defaults): - self.add_entries.extend( - [self.make_entry(self.parent, fname + ":", 42, show="", default=fdef)] - ) - # button to attempt to login - self.button = tk.Button( - self.parent, borderwidth=4, text="Submit", width=42, pady=8, command=self.get_value, - ) - self.button.pack(side=tk.BOTTOM) - self.root.bind("", self.push_enter) - # do not reproduce vim behaviour - self.root.protocol("WM_DELETE_WINDOW", self.cancel_login) - - def make_entry(self, _, caption, width=None, default="", **options): - tk.Label(self.parent, text=caption).pack(side=tk.TOP) - entry = tk.Entry(self.parent, **options) - if width: - entry.config(width=width) - entry.pack(side=tk.TOP, padx=10, fill=tk.BOTH) - if default: - entry.insert(0, default) - return entry - - def push_enter(self, _): - self.get_value() - - def get_value(self): - self.ADD = [] - for entry in self.add_entries: - self.ADD.extend([entry.get()]) - self.root.destroy() - self.root.quit() - - def cancel_login(self): - self.ADD = [] - for entry in self.add_entries: - self.ADD.extend([None]) - self.root.destroy() - self.root.quit() - - root = tk.Tk() - toto = Toto(root, title=title, add_fields=add_fields, defaults=defaults) - root.mainloop() - return toto.ADD diff --git a/iblrig/session_creator.py b/iblrig/session_creator.py index b102758e5..62cb65dc4 100644 --- a/iblrig/session_creator.py +++ b/iblrig/session_creator.py @@ -2,17 +2,13 @@ Creates sessions, pre-generates stim and ephys sessions """ import math -from pathlib import Path -import matplotlib.pyplot as plt import numpy as np import iblrig.misc as misc -from iblrig import path_helper -from iblrig.misc import smooth_rolling_window as smooth -def draw_position(position_set, stim_probability_left): +def draw_position(position_set, stim_probability_left) -> int: return int(np.random.choice(position_set, p=[stim_probability_left, 1 - stim_probability_left])) @@ -68,89 +64,6 @@ def make_ephysCW_pcqs(pc): return pcqs -def pre_generate_ephysCW_session_files( - nsessions, path="./tasks/_iblrig_tasks_ephysChoiceWorld/sessions" -): - iblrig_path = path_helper.get_iblrig_path() - path = iblrig_path / Path(path) - path.mkdir(parents=True, exist_ok=True) - for i in range(nsessions): - pc, len_block = make_ephysCW_pc() - pcqs = make_ephysCW_pcqs(pc) - np.save(path / f"session_{i}_ephys_pcqs.npy", pcqs) - np.save(path / f"session_{i}_ephys_len_blocks.npy", len_block) - - -def plot_pcqs(session_num, folder="./tasks/_iblrig_tasks_ephysChoiceWorld/sessions"): - iblrig_path = path_helper.get_iblrig_path() - folder_path = Path(folder) - folder = str(iblrig_path / folder_path) - num = session_num - pcqs = np.load(folder + f"/pcqs_session_{num}.npy") - len_block = np.load(folder + f"/pcqs_session_{num}_len_blocks.npy") - - with plt.xkcd(scale=1, length=100, randomness=2): - f = plt.figure(figsize=(16, 12), dpi=80) - f.suptitle(f"Session number: {num}") - ax_position = plt.subplot2grid([2, 2], [0, 0], rowspan=1, colspan=1, fig=f) - ax_contrast = plt.subplot2grid( - [2, 2], [0, 1], rowspan=1, colspan=1, fig=f, sharex=ax_position - ) - ax_qperiod = plt.subplot2grid( - [2, 2], [1, 0], rowspan=1, colspan=1, fig=f, sharex=ax_position - ) - ax_sphase = plt.subplot2grid( - [2, 2], [1, 1], rowspan=1, colspan=1, fig=f, sharex=ax_position - ) - - ax_position.plot(pcqs[:, 0], ".", label="Position", color="b") - ax_position.plot(smooth(pcqs[:, 0], window_len=20, window="blackman"), alpha=0.5, color="k") - - ax_contrast.plot(pcqs[:, 1] * 100, ".", label="Contrasts") - - ax_qperiod.plot(pcqs[:, 2], ".", label="Quiescent period") - - ax_sphase.plot(pcqs[:, 3], ".", label="Stimulus phase") - - [ - ax.set_ylabel(l) - for ax, l in zip( - f.axes, - ["Position (º)", "Contrasts (%)", "Quiescent period (s)", "Stimulus phase (rad)"], - ) - ] - [ax.axvline(x, alpha=0.5) for x in np.cumsum(len_block) for ax in f.axes] - f.show() - return pcqs, len_block - - -# CERTIFICATION -def make_stims_for_certification_pcs(seed_num=None, save=False): - if seed_num is not None: - np.random.seed(seed_num) - iblrig_path = path_helper.get_iblrig_path() - # Generate the position and contrast for the replayed stims - contrasts = [1.0, 0.5, 0.25, 0.125, 0.0625] - - positions = [-35, 35] - pc_repeats = 20 - - pos = sorted(positions * len(contrasts) * pc_repeats) - cont = contrasts * pc_repeats * len(positions) - - sphase = [np.random.uniform(0, 2 * math.pi) for x in cont] - gabors = np.array([[int(p), c, s] for p, c, s in zip(pos, cont, sphase)]) - - np.random.shuffle(gabors) - # Make into strings for saving - if save: - fpath = iblrig_path / "visual_stim" / "ephys_certification" - fpath = fpath / "Extensions" / "certification_stims.csv" - np.savetxt(fpath, gabors, delimiter=" ", fmt=["%d", "%f", "%f"]) - - return gabors - - # PASSIVE CHOICE WORLD def make_stims_for_passiveCW_pcs(seed_num=None): # XXX if seed_num is not None: @@ -219,10 +132,10 @@ def make_passiveCW_session_delays_ids(seed_num=None): # XXX # get the delays between the stims (add the first delay) sess_delays_out = np.insert(np.diff(sess_delays_cumsum), 0, sess_delays_cumsum[0]) tot_dur = ( - np.sum( - np.sum(g_len) + np.sum(n_len) + np.sum(t_len) + np.sum(v_len) + np.sum(sess_delays_out) - ) - / 60 + np.sum( + np.sum(g_len) + np.sum(n_len) + np.sum(t_len) + np.sum(v_len) + np.sum(sess_delays_out) + ) + / 60 ) # print(f'Stim IDs: {sess_labels_out}') @@ -230,49 +143,3 @@ def make_passiveCW_session_delays_ids(seed_num=None): # XXX print(f"Total duration of stims: {tot_dur} m") return sess_delays_out, sess_labels_out - - -def pre_generate_passiveCW_session_files( - nsessions, path="./tasks/_iblrig_tasks_ephysChoiceWorld/sessions" -): - # Standalone passive in - # /home/nico/Projects/IBL/int-brain-lab/personal_project_protocols/carandiniharris_midbrain_ibl/tasks/_chmid_tasks_passive/sessions - path = Path(path) - path.mkdir(parents=True, exist_ok=True) - for i in range(nsessions): - (delays, ids,) = make_passiveCW_session_delays_ids() - pcs = make_stims_for_passiveCW_pcs() - np.save(path / f"session_{i}_passive_stimIDs.npy", ids) - np.save(path / f"session_{i}_passive_stimDelays.npy", delays) - np.save(path / f"session_{i}_passive_pcs.npy", pcs) - else: - (delays, ids,) = make_passiveCW_session_delays_ids() - pcs = make_stims_for_passiveCW_pcs() - np.save(path / "session_mock_passive_stimIDs.npy", ids) - np.save(path / "session_mock_passive_stimDelays.npy", delays) - np.save(path / "session_mock_passive_pcs.npy", pcs) - - -def pre_generate_stim_phase(nsessions, path="./tasks/_iblrig_tasks_ephysChoiceWorld/sessions"): - path = Path(path) - path.mkdir(parents=True, exist_ok=True) - for i in range(nsessions): - length = len(np.load(path.joinpath(f"session_{i}_ephys_pcqs.npy"))) - sphase = np.array([np.random.uniform(0, 2 * math.pi) for x in range(length)]) - np.save(path / f"session_{i}_stim_phase.npy", sphase) - else: - length = len(np.load(path.joinpath("session_mock_ephys_pcqs.npy"))) - sphase = np.array([np.random.uniform(0, 2 * math.pi) for x in range(length)]) - np.save(path / "session_mock_stim_phase.npy", sphase) - - -# Variables thatchange every trial: -# contrast, position, phase, quiescence_duration, probability_left, -# reverse_contingecy, laser_on, block_id, trial_id - - -def create_session_contrasts(seed=None): - if seed is not None: - np.random.seed(seed) - - return np.random.uniform(0.1, 0.9, 180) diff --git a/iblrig/spacer.py b/iblrig/spacer.py deleted file mode 100644 index e39283c42..000000000 --- a/iblrig/spacer.py +++ /dev/null @@ -1,117 +0,0 @@ -""" -The purpose of this module is provide tools to generate and identify spacers. - -Spacers are sequences of up and down pulses with a specific, identifiable pattern. -They are generated with a chirp coding to reduce cross-correlaation sidelobes. -They are used to mark the beginning of a behaviour sequence within a session. - -Usage: - spacer = Spacer() - spacer.add_spacer_states(sma, t, next_state='first_state') - for i in range(ntrials): - sma.add_state( - state_name="first_state", - state_timer=tup, - state_change_conditions={"Tup": f"spacer_low_{i:02d}"}, - output_actions=[("BNC1", 255)], # To FPGA - ) -""" - -import numpy as np - - -class Spacer(object): - """ - Computes spacer up times using a chirp up and down pattern - Returns a list of times for the spacer states - Each time corresponds to an up time of the BNC1 signal - - dt_start: first spacer up time - dt_end: last spacer up time - n_pulses: number of spacer up times, one-sided (i.e. 8 means 16 - 1 spacers times) - tup: duration of the spacer up time - """ - def __init__(self, dt_start=.02, dt_end=.4, n_pulses=8, tup=.05): - self.dt_start = dt_start - self.dt_end = dt_end - self.n_pulses = n_pulses - self.tup = tup - assert np.all(np.diff(self.times) > self.tup), 'Spacers are overlapping' - - def __repr__(self): - return f"Spacer(dt_start={self.dt_start}, dt_end={self.dt_end}, n_pulses={self.n_pulses}, tup={self.tup})" - - @property - def times(self, latency=0): - """ - Computes spacer up times using a chirp up and down pattern - :return: numpy arrays of times - """ - # upsweep - t = np.linspace(self.dt_start, self.dt_end, self.n_pulses) + self.tup - # downsweep - t = np.r_[t, np.flipud(t[1:])] - t = np.cumsum(t) - return t - - def generate_template(self, fs=1000): - """ - Generates a spacer voltage template to cross-correlate with a voltage trace - from a DAQ to detect a voltage trace - :return: - """ - t = self.times - ns = int((t[-1] + self.tup * 10) * fs) - sig = np.zeros(ns, ) - sig[(t * fs).astype(np.int32)] = 1 - sig[((t + self.tup) * fs).astype(np.int32)] = -1 - sig = np.cumsum(sig) - return sig - - def add_spacer_states(self, sma=None, next_state="exit"): - """ - Add spacer states to a state machine - :param sma: pybpodapi.state_machine.StateMachine object - :param next_state: name of the state following the spacer states - :return: - """ - assert next_state is not None - t = self.times - dt = np.diff(t, append=t[-1] + self.tup * 2) - for i, time in enumerate(t): - if sma is None: - print(i, time, dt[i]) - continue - next_loop = f"spacer_high_{i + 1:02d}" if i < len(t) - 1 else next_state - sma.add_state( - state_name=f"spacer_high_{i:02d}", - state_timer=self.tup, - state_change_conditions={"Tup": f"spacer_low_{i:02d}"}, - output_actions=[("BNC1", 255)], # To FPGA - ) - sma.add_state( - state_name=f"spacer_low_{i:02d}", - state_timer=dt[i] - self.tup, - state_change_conditions={"Tup": next_loop}, - output_actions=[], - ) - - def find_spacers(self, signal, threshold=0.9, fs=1000): - """ - Find spacers in a voltage time serie. Assumes that the signal is a digital signal between 0 and 1 - :param signal: - :param threshold: - :param fs: - :return: - """ - template = self.generate_template(fs=fs) - xcor = np.correlate(signal, template, mode="full") / np.sum(template) - idetect = np.where(xcor > threshold)[0] - iidetect = np.cumsum(np.diff(idetect, prepend=0) > 1) - nspacers = iidetect[-1] - tspacer = np.zeros(nspacers) - for i in range(nspacers): - ispacer = idetect[iidetect == i + 1] - imax = np.argmax(xcor[ispacer]) - tspacer[i] = (ispacer[imax] - template.size + 1) / fs - return tspacer diff --git a/iblrig/test/test_spacers.py b/iblrig/test/test_spacers.py deleted file mode 100644 index 385dcedee..000000000 --- a/iblrig/test/test_spacers.py +++ /dev/null @@ -1,34 +0,0 @@ -import numpy as np -import unittest - -from iblrig.spacer import Spacer - - -class TestSpacer(unittest.TestCase): - - def test_spacer(self): - spacer = Spacer(dt_start=.02, dt_end=.4, n_pulses=8, tup=.05) - np.testing.assert_equal(spacer.times.size, 15) - sig = spacer.generate_template(fs=1000) - ac = np.correlate(sig, sig, 'full') / np.sum(sig**2) - # import matplotlib.pyplot as plt - # plt.plot(ac) - # plt.show() - ac[sig.size - 100: sig.size + 100] = 0 # remove the main peak - # the autocorrelation side lobes should be less than 30% - assert np.max(ac) < .3 - - def test_find_spacers(self): - """ - Generates a fake signal with 2 spacers and finds them - :return: - """ - fs = 1000 - spacer = Spacer(dt_start=.02, dt_end=.4, n_pulses=8, tup=.05) - start_times = [4.38, 96.58] - template = spacer.generate_template(fs) - signal = np.zeros(int(start_times[-1] * fs + template.size * 2)) - for start_time in start_times: - signal[int(start_time * fs): int(start_time * fs) + template.size] = template - spacer_times = spacer.find_spacers(signal, fs=fs) - np.testing.assert_allclose(spacer_times, start_times) From ed2c8b84b0bf01c755309dad68672c33c9b05567 Mon Sep 17 00:00:00 2001 From: Florian Rau Date: Tue, 31 Oct 2023 16:23:48 +0000 Subject: [PATCH 3/5] Update session_creator.py --- iblrig/session_creator.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/iblrig/session_creator.py b/iblrig/session_creator.py index 62cb65dc4..a3c4870f8 100644 --- a/iblrig/session_creator.py +++ b/iblrig/session_creator.py @@ -132,10 +132,10 @@ def make_passiveCW_session_delays_ids(seed_num=None): # XXX # get the delays between the stims (add the first delay) sess_delays_out = np.insert(np.diff(sess_delays_cumsum), 0, sess_delays_cumsum[0]) tot_dur = ( - np.sum( - np.sum(g_len) + np.sum(n_len) + np.sum(t_len) + np.sum(v_len) + np.sum(sess_delays_out) - ) - / 60 + np.sum( + np.sum(g_len) + np.sum(n_len) + np.sum(t_len) + np.sum(v_len) + np.sum(sess_delays_out) + ) + / 60 ) # print(f'Stim IDs: {sess_labels_out}') From e8e26252f0d33fe0ef399fa8a0d40abfcda464d1 Mon Sep 17 00:00:00 2001 From: Florian Rau Date: Tue, 31 Oct 2023 17:24:02 +0000 Subject: [PATCH 4/5] disable skipping of initialization --- iblrig/hardware.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/iblrig/hardware.py b/iblrig/hardware.py index d8353f123..c6f281064 100644 --- a/iblrig/hardware.py +++ b/iblrig/hardware.py @@ -45,9 +45,9 @@ def __new__(cls, *args, **kwargs): return instance def __init__(self, *args, **kwargs): - # skip initialization if it has already been performed before - if self._is_initialized: - return + # # skip initialization if it has already been performed before + # if self._is_initialized: + # return # try to instantiate once for nothing try: From b2347ca7843d8584bf283b9cde04c104c89de059 Mon Sep 17 00:00:00 2001 From: Florian Rau Date: Wed, 1 Nov 2023 10:32:03 +0000 Subject: [PATCH 5/5] 8.12.2 --- CHANGELOG.md | 4 ++++ iblrig/__init__.py | 2 +- iblrig/hardware.py | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc83aa50d..c8754a9fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ Changelog --------- +8.12.2 +------ +* bugfix: rollback skipping of bpod initialization (possible source of integer overflow) +* removal of dead code 8.12.1 ------ diff --git a/iblrig/__init__.py b/iblrig/__init__.py index 1f801c620..1755f17e9 100644 --- a/iblrig/__init__.py +++ b/iblrig/__init__.py @@ -4,7 +4,7 @@ # 3) Check CI and eventually wet lab test # 4) Pull request to iblrigv8 # 5) git tag the release in accordance to the version number below (after merge!) -__version__ = '8.12.1' +__version__ = '8.12.2' # The following method call will try to get post-release information (i.e. the number of commits since the last tagged # release corresponding to the one above), plus information about the state of the local repository (dirty/broken) diff --git a/iblrig/hardware.py b/iblrig/hardware.py index c6f281064..96032b7e0 100644 --- a/iblrig/hardware.py +++ b/iblrig/hardware.py @@ -191,7 +191,8 @@ def set_status_led(self, state: bool) -> bool: return True except serial.SerialException: pass - self._arcom.serial_object.flush() + self._arcom.serial_object.reset_input_buffer() + self._arcom.serial_object.reset_output_buffer() log.error('Bpod device does not support control of the status LED. Please update firmware.') return False