Low-cost, open-source, full-stack software and Arduino-based hardware for control of commercially available animal behavior systems.
The 15 matches · 2 of them tie a paragraph to a whole file, not to given lines: weak matches, whose lines are not tinted
- [1] § Methods › Programmable microstimulator ↔ Flask Project Files/pythonBackend/serial_functions.py, lines 280–310 · score 0.92 · inter phase interval, change stimulation parameters, pulse width, pulse trains, deliver stimulation, aperiodic
- [2] § Operant conditioning tasks ↔ Flask Project Files/pythonBackend/serial_functions.py, lines 61–120 · score 0.78 · house light, left port, right port, initiated, unpausing, tone
- [3] § Flask micro-framework ↔ Flask Project Files/pythonBackend/application.py, lines 1–16 · score 0.78 · virtual environment, end service, Flask Project, GitHub, RESTful, py
- [4] § Methods › Angular framework ↔ Angular Files/src/app/device-startup/device-startup.component.ts, lines 1–62 · score 0.76 · device startup, BehaviorSubject, SSEService, FlaskService, tool, Angular
- [5] § Methods › Angular framework ↔ Angular Files/src/app/device-startup/sse.service.ts, the whole file · a weak match · score 0.76 · BehaviorSubject, event source, SSEService, RESTful, reconnect, error
- [6] § Methods › Angular framework ↔ Angular Files/src/app/device-startup/sse.service.ts, the whole file · a weak match · score 0.72 · device startup, BehaviorSubject, SSEService, RESTful, Angular, components
- [7] § Methods › Programmable microstimulator ↔ Flask Project Files/pythonBackend/application.py, lines 27–104 · score 0.72 · pulse width, stimulation parameters, aperiodic, coefficient, variation, inter
- [8] § Methods › Angular framework ↔ Angular Files/src/app/total.service.ts, lines 1–58 · score 0.69 · SSEService, FlaskService, RESTful, selection, POST, services
- [9] § Methods › Angular framework ↔ Angular Files/src/app/device-startup/device-startup.component.ts, lines 190–247 · score 0.68 · device startup, view port, selection, exporting, events, fields
- [10] § Methods › Operant chamber ↔ Arduino Files/operant_task_control/SPOUT.h, lines 9–28 · score 0.61 · solenoid valves, water reward, delivery, pin, operant, Arduino
- [11] § Flask micro-framework ↔ Flask Project Files/pythonBackend/application.py, lines 1–16 · score 0.61 · helper_functions.py, serial_functions.py, Python, Flask, services, logic
- [12] § Flask micro-framework ↔ Angular Files/src/app/device-startup/device-startup.component.ts, lines 64–125 · score 0.57 · button presses, RESTful, loop, client, stream, Flask
- [13] § Methods › Operant chamber ↔ Flask Project Files/pythonBackend/serial_functions.py, lines 61–120 · score 0.54 · reward delivery, operant chamber, sensor, components, Arduino
- [14] § Operant conditioning tasks ↔ Flask Project Files/pythonBackend/serial_functions.py, lines 142–278 · score 0.54 · left port response, right port response, incorrect, Forced, detection, stimulus
- [15] § Flask micro-framework ↔ Flask Project Files/pythonBackend/application.py, lines 27–104 · score 0.51 · simple welcome, alongside, serial port, Flask, services, Arduino
Paper
Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
Python · 516 lines · 22 KB · BSD-3-Clause · 4 matches
- # Functions for Arduino Communication
- import serial
- import serial.tools.list_ports as port_list
- import time
- import random
- def findPorts(): #finds and returns devices ocnnected to serial port
- ports = list(port_list.comports())
- return ports
- def changeSessionParams(ard, params, y):
- """ changes all rrelevant session parameters for running 2AFC experiments
- # Inputs:
- # ard - serial port object for task arduino
- # params - dict of paramaters to updated, ex: params = ['Initial Training', 'Detection', '60', '10', 'Yes', '3']
- # y - data stream variable for storing/sending display information to angular
- """
- com = []
- # sets session
- if params[0] == "Initial Training":
- ard.write('P42'.encode('utf-8'))
- else:
- ard.write('P41'.encode('utf-8'))
- # sets session length
- ard.write(('P1' + params[2]).encode('utf-8'))
- # sets response time
- ard.write(('P2' + params[3]).encode('utf-8'))
- # sets forced trials
- if params[4] == "Yes":
- ard.write('P51'.encode('utf-8'))
- else:
- ard.write('P50'.encode('utf-8'))
- # set consecutive error
- ard.write(('P3' + params[5]).encode('utf-8'))
- #def manualControlDic(ard,com):
- com_lookup = {'left-door-true': 'D',
- 'left-door-false': 'D',
- 'right-door-true': 'd',
- 'right-door-false': 'd',
- 'left-flush-true': 'L',
- 'left-flush-false': 'L',
- 'right-flush-true': 'R',
- 'right-flush-false': 'R',
- 'house-light-true': 'H',
- 'house-light-false':'H',
- 'buzzer-true': 'B1',
- 'buzzer-false': 'B0',
- 'test-sensors-true': 'J',
- 'test-sensors-false': 'K',
- 'pause-true': 'p',
- 'pause-false': 'u',
- 'stop-N/A': 'Q'}
- ## manualControl replaced by above dictionary. Thought that all the if-statements were causing a lot of lag
- # between converting the HTTP POST to serial command but it ended up being on the arduino program side.
- def manualControl(ard, component, state, y):
- """ Commands sent to arduino to control various components in the operant chamber.
- # Manual control is only enable before a session is started and during pauses (or fored trials)
- # may do something like: comp = {'left-door':{'true':'D1','false':'D0'},'right-door':{'true':'d1','false':'d0'},'left-flush':'L','right-flush':'R'}
- # Inputs:
- # ard - serial port object for task arduino
- # component - string indicating component to control (ex: "right-door")
- # state - string indicating button/switch state ("true", "false", or "NAN"
- """
- if component == "left-door": # open/close left door
- if state == "true":
- com = 'D1'
- else:
- com = 'D0'
- elif component == "right-door": # open/close right door
- if state == "true":
- com = 'd1'
- else:
- com = 'd0'
- elif component == "left-flush": # flushes/stop flushes left port
- com = 'L'
- elif component == "right-flush": # flushed/stop flushed right port
- com = 'R'
- elif component == "house-light": # turns houselight on/off
- if state == "true":
- com = 'H1'
- else:
- com = 'H0'
- elif component == "buzzer": # plays short/long buzzer tone
- if state == "true":
- com = 'B1' # short
- else:
- com = 'B0' # long
- elif component == "test-sensors": # allows for testing of sensors and reward delivery
- if state == "true":
- com = 'J' # initiates sensor testing
- else:
- com = 'K' # ends sensor testing
- elif component == "start": # starts session (isn't used here)
- com = 'b'
- elif component == "stop": # stops session
- com = 'Q'
- elif component == "pause": # pauses session
- if state == "true":
- com = 'p' # pause
- else:
- com = 'u' # unpause
- ard.write(com.encode('utf-8'))
- def randomizeAmplitude(gib, stimParams, y):
- """ function for randomizing amplitude of stimulation
- # Inputs:
- # gib - serial port object for stimulator
- # stimParams - dict of stimulation params (ex: line 76 in application.py
- # y - data stream variable for storing/sending display information to angular
- """
- if stimParams["amp_indx"] == len(stimParams["task_amps"]): #check if every amplitude in stimParams["task_amps"] has been used
- stimParams["amp_indx"] = 0 #reset index
- print("Amp_indx asaa: " + str(stimParams["amp_indx"]))
- stimParams["shuffled_amps"] = random.sample(
- stimParams["task_amps"],
- len(stimParams["task_amps"])) #reshuffle amplitudes
- y.append("amplitude index: " + str(stimParams["amp_indx"])) #update display in anglar
- stimParams["amplitude"] = stimParams["shuffled_amps"][stimParams["amp_indx"]]
- changeAmplitude(gib, stimParams["amplitude"], y)
- stimParams["amp_indx"] = stimParams["amp_indx"] + 1
- def arduinoTask(ard, gib, y, sessionData, currentTrialData, stimParams):
- """serial port "listener" to perform specific actions depending on what arduino writes to port
- # Inputs:
- # ard - serial port object for task arduino
- # gib - serial port object for stimulator
- # y - data stream variable for storing/sending display information to angular
- # sessionData - dict to store relevant behavioral data during session
- # currentTrialData - dict to update trial table in angular
- # stimParams - dict containing parameters of stimulation
- """
- break_flag = 0 # flag to break external while loop if present
- while ard.in_waiting == 0: #do nothing until bytes are available to read
- pass
- x = ard.read_until(expected=b'\r\n').decode("utf").rstrip()
- print(x)
- data = x.split(',')
- if data[0] == "Connected":
- print("Arduino " + data[0])
- print("Manual Control Enabled")
- y.append("Arduino " + data[0])
- y.append("Manual Control Enabled")
- break_flag = 1
- elif data[0] == "Start":
- print("Beginning Session")
- y.append("Beginning Session")
- elif data[0] == "Trial":
- print("Trial time: " + data[1])
- print("Trial Number: " + data[2])
- y.append("Trial Number: " + data[2])
- trial_time = int(data[1]) / 1000 # to seconds
- ## update data
- currentTrialData["sess_time"] = str(round(trial_time / 60,2)) #to minutes
- currentTrialData["trial_n"] = data[2]
- currentTrialData["trial_type"] = "-"
- currentTrialData["stim_A"] = "-"
- currentTrialData["stim_fre"] = "-"
- currentTrialData["CV"] = "-"
- currentTrialData["trial_res"] = "-"
- sessionData["trial_time"].append(trial_time)
- sessionData["trial_number"].append(int(data[2]))
- elif data[0] == "Type":
- if data[1] == "2": #right trial, no stimulation for detection experiment
- sessionData["amplitude"].append([])
- sessionData["frequency"].append([])
- sessionData["CV"].append([])
- else: #left trial, stimulation if CV experiment is selected
- if stimParams["stim_enable"] == 1:
- if stimParams["randomize"] == 1:
- randomizeAmplitude(gib, stimParams, y)
- currentTrialData["stim_A"] = str(stimParams["amplitude"])
- currentTrialData["stim_fre"] = str(stimParams["frequency"])
- currentTrialData["CV"] = str(stimParams["CV"])
- sessionData["amplitude"].append(stimParams["amplitude"])
- sessionData["frequency"].append(stimParams["frequency"])
- sessionData["CV"].append(stimParams["CV"])
- else:
- sessionData["amplitude"].append([])
- sessionData["frequency"].append([])
- sessionData["CV"].append([])
- datStr = ard.read_until(expected=b'\r\n').decode("utf").rstrip()
- y.append(datStr)
- currentTrialData["trial_type"] = data[1]
- currentTrialData["forced"] = data[2]
- sessionData["trial_type"].append(int(data[1]))
- sessionData["forced"].append(int(data[2]))
- sessionData["randomized"].append(stimParams["randomize"])
- sessionData["response"].append([])
- sessionData["response_time"].append([])
- sessionData["correct"].append([])
- sessionData["percent"].append([])
- elif data[0] == "Stim":
- if data[1] == "1": #left trial
- if gib.is_open == 1: #stimParams["stimEnable"] == 1
- stimulate(gib, stimParams, y)
- print("Stim")
- y.append("Stim")
- else:
- print("No stim")
- y.append("No stim")
- elif data[0] == "Response":
- res_time = int(data[1]) / 1000
- if data[3] == "1":
- datStr = "correct."
- elif data[3] == "0":
- datStr = "incorrect."
- elif data[3] == "5":
- #data[3] = "0"
- datStr = "forced."
- print("Response Time: " + str(res_time) + "sec")
- if data[2] == "1":
- datStr = "Left Port Response, " + datStr
- elif data[2] == "2":
- datStr = "Right Port Response, " + datStr
- elif data[2] == "5":
- datStr = "No response."
- print(datStr)
- y.append(datStr)
- currentTrialData["trial_res"] = data[2]
- sessionData["response_time"][-1] = res_time
- sessionData["response"][-1] = int(data[2])
- sessionData["correct"][-1] = int(data[3])
- elif data[0] == "Percent":
- percent = float(data[1]) * 100
- currentTrialData["per_cor"] = str(percent)
- sessionData["percent"][-1] = percent
- y.append("Running percentage correct: " + str(percent) + "%")
- elif data[0] == "End":
- print("Session Ended")
- y.append("Session Ended")
- break_flag = 1
- elif data[0] == "Wait":
- print("Manual Control Enabled")
- y.append("Manual Control Enabled")
- #break_flag = 1
- elif data[0] == "Wait for Response":
- print("Waiting for response...")
- y.append("Waiting for response...")
- #elif data[0] == "Paused":
- # print("Paused")
- # y.append(data[0])
- else:
- y.append(data[0])
- return break_flag
- '''
- The following functions are used for communication with the gibson stimulator:
- - 'waitForGibson' = establishes connection to stimulator
- - 'readGibData' = reads/clears data written by stimulator on serial port
- - 'convertToBytes' = configures command to stimulator to change parameters
- - 'currentConfig' = configures current for writing to gibson
- - 'changePulseNumber' = changes the number of pulses in stimulation pulse-train
- - 'changePulseWidth' = changes the pulse-width of stimulation pulse-train (us)
- - 'changeIPI' = changes the inter-phase interval (us)
- - 'changeFrequency' = changes frequency (Hz)
- - 'changeAmplitude' = changes amplitude of pulse-train (uA)
- - 'changeCV' = changes the coefficient of variation (aperiodicity) of pulse-train (can be 0, 0.1, 0.2,..., 1.0)
- - 'changeStimParams' = changes all stimulation parameters, primarily used for initial setup
- - 'stimulate' = sends command to deliver stimulation based on parameters set by the previous functions
- To change stimulation parameters:
- 1. the stimulation channel must first be selected (ch)
- -In the case here, only a single channel is used (channel 0) and is hard coded. This may be changed for future applications
- 2. the parameter to be changed is selected (val_ind)
- -pulse number -> 1
- -pulse width -> 2
- -inter-phase interval -> 3
- -frequency -> 4
- -amplitude requires sequence of three bytes:
- -> 5, 6, 7
- -CV -> 8
- 3. the corresponding parameter value is written (val)
- -ex: for pulse width -> 256
- -ex: for frequency -> 50
- 4. Function 'convertToBytes' takes the 3 aformentioned values and appropriately configures them for writing to serial port
- '''
- def waitForGibson(gib, y):
- """ establishes connection to stimulator
- Inputs:
- gib - serial port object for stimulator
- y - data stream variable for storing/sending display information to angular
- """
- gib.write((1).to_bytes(1, byteorder="big"))#write_uint8(gib,0)
- msg = ""
- while msg.find("Connected") == -1:
- while gib.in_waiting == 0:
- pass
- msg = gib.read_until(expected=b'\r\n').decode("utf").rstrip()
- print(msg)
- y.append("Gibson " + msg)
- def readGibData(gib, r_max, y):
- """ reads serial port of stimulator
- Inputs:
- gib - serial port object for stimulator
- r_max - indicate number of reponses (lines) from device
- y - data stream variable for storing/sending display information to angular
- """
- while gib.in_waiting == 0: # wait until bytes are on the serial port
- pass
- i = 0
- while i < r_max:
- x = gib.read_until(expected=b'\r\n').decode("utf").rstrip()
- print(x)
- y.append(x)
- i = i + 1
- def convertToBytes(ch, val_ind, val):
- """ Converts channel (ch), parameter index (val_ind), and parameter value (val) to
- bytes and joins them together.
- Inputs:
- ch - stimulation channel, zero-index (0-3, but 0 is only in use)
- val_ind - value specifying the parameter to be changed
- val - value of parameter to change
- """
- CH = ch.to_bytes(1, byteorder="big")
- VAL_IND = val_ind.to_bytes(1, byteorder="big")
- if val_ind == 1: #pulse number can be greater than 255
- VAL = val.to_bytes(2, byteorder="little") #give pulse number 2 bytes
- else:
- VAL = val.to_bytes(1, byteorder="big")
- COM = b''.join([CH, VAL_IND, VAL])
- return COM
- def currentConfig(current):
- '''
- -converted from matlab script 'currentconfig.m'
- 0-800 for current <---> 0-4095 decimal
- 800 / 4095 = 5.11875
- value = current * 5.11875 (round to positive int)
- convert to 12-bit binary
- '''
- dac_volt = format(int(round(current * 5.11875)), '012b')
- '''
- Correction factor required? Add here ------
- Take last 8-bits of dac_volt, convert to uint8 dec and assign to ab_byte
- '''
- ab_byte = int(dac_volt[4:12], 2).to_bytes(1, byteorder='big')
- # Take 1st 4-bits of dac_volt and add to back of dac_A configuration
- # Convert to uint8 dec and assign to a_byte
- dacAconfig = format(1, '04b') # 1 = 0001
- a_byte = int(dacAconfig + dac_volt[0:4], 2).to_bytes(1, byteorder='big')
- # Take 1st 4-bits of dac_volt and add to back of dac_B configuration
- # Convert to uint8 dec and assign to b_byte
- dacBconfig = format(9, '04b') # 9 = 1001
- b_byte = int(dacBconfig + dac_volt[0:4], 2).to_bytes(1, byteorder='big')
- return a_byte, b_byte, ab_byte
- def changePulseNumber(gib, pnVal, y):
- """ changes pulse number value.
- # Inputs:
- # gib - serial port object for stimulator
- # pnVal - number of pulses, string (ex: '300')
- # y - data stream variable for storing/sending display information to angular
- """
- COM = convertToBytes(0, 1, int(pnVal))
- gib.write(COM)
- readGibData(gib, 2, y)
- def changePulseWidth(gib, pwVal, y):
- """ changes pulse width.
- # Inputs:
- # gib - serial port object for stimulator
- # pwVal - pulse width in us, string (ex: '200')
- # y - data stream variable for storing/sending display information to angular
- """
- COM = convertToBytes(0, 2, int(pwVal))
- gib.write(COM)
- readGibData(gib, 2, y)
- def changeIPI(gib, ipiVal, y):
- """ changes inter-phase interval.
- # Inputs:
- # gib - serial port object for stimulator
- # ipiVal - inter-phase interval in us, string (ex: '50')
- # y - data stream variable for printing reads from serial port
- """
- COM = convertToBytes(0, 3, int(ipiVal))
- gib.write(COM)
- readGibData(gib, 2, y)
- def changeFrequency(gib, freqVal, y):
- """ changes frequency.
- # Inputs:
- # gib - serial port object for stimulator
- # freqVal - frequency in Hz, string (ex: '50')
- # y - data stream variable for storing/sending display information to angular
- """
- COM = convertToBytes(0, 4, int(freqVal))
- gib.write(COM)
- readGibData(gib, 2, y)
- def changeAmplitude(gib, ampVal, y):
- """ changes amplitde.
- # Inputs:
- # gib - serial port object for stimulator
- # ampVal - amplitude in uA, string (ex: '250')
- # y - data stream variable for storing/sending display information to angular
- """
- a_byte, b_byte, ab_byte = currentConfig(int(ampVal))
- a_int = int.from_bytes(a_byte, byteorder='big')
- b_int = int.from_bytes(b_byte, byteorder='big')
- ab_int = int.from_bytes(ab_byte, byteorder='big')
- aCOM = convertToBytes(0, 5, a_int)
- gib.write(aCOM)
- bCOM = convertToBytes(0, 6, b_int)
- gib.write(bCOM)
- abCOM = convertToBytes(0, 7, ab_int)
- gib.write(abCOM)
- readGibData(gib, 3, y)
- def changeCV(gib, cvVal, y):
- """ changes CV value.
- Inputs:
- gib - serial port object for stimulator
- cvVal - coeficient of variation value, string (ex: '0.8')
- y - data stream variable for storing/sending display information to angular
- """
- COM = convertToBytes(0, 8, int(float(cvVal) * 10)) #cv is read as 0-10 so multiply by 10
- gib.write(COM)
- readGibData(gib, 2, y)
- def changeStimParams(gib, params, stimParams, y):
- """ sequentially changes all stimulation parameters
- Inputs:
- gib - serial port object for stimulator
- params - list of parameters as strings sent from angular to be changed
- stimParams - dict containing parameters of stimulation
- y - data stream variable for storing/sending display information to angular
- """
- ampStr = params[0]
- stimParams["amplitude"] = int(ampStr)
- stimParams["base_amp"] = int(ampStr)
- freqStr = params[1]
- stimParams["frequency"] = int(freqStr)
- pwStr = params[2]
- stimParams["pulse_width"] = int(pwStr)
- ipiStr = params[3]
- stimParams["ipi"] = int(ipiStr)
- pnStr = params[4]
- stimParams["pulse_num"] = int(pnStr)
- cvStr = params[5]
- stimParams["CV"] = float(cvStr)
- if stimParams["CV"] > 0:
- stimParams["periodic"] = 1
- else:
- stimParams["periodic"] = 0
- changeAmplitude(gib, ampStr, y)
- changeFrequency(gib, freqStr, y)
- changePulseWidth(gib, pwStr, y)
- changeIPI(gib, ipiStr, y)
- changePulseNumber(gib, pnStr, y)
- changeCV(gib, cvStr, y)
- def stimulate(gib, stimParams, y):
- """ delivers stimulation based on set stimulation parameters
- Inputs:
- gib - serial port object for stimulator
- stimParams - dict containing parameters of stimulation
- y - data stream variable for storing/sending display information to angular
- """
- gib.write((0 + 4).to_bytes(1, "big")) # '0' is the channel, stim command is channel + 4
- if stimParams["periodic"] == 0:
- readGibData(gib, 1, y)
- else:
- readGibData(gib, 2, y)
serial_functions.py at commit 5ff84c9, under BSD-3-Clause · at the source
Overview
- Department of Biomedical Engineering, University of Michigan,Ann Arbor, MI USA
- Lampe Joint Department of Biomedical Engineering, University of North Carolina at Chapel Hill and North Carolina State University,Chapel Hill, NC USA
- Neurosurgery, School of Medicine, University of North Carolina at Chapel Hill,Chapel Hill, NC USA
- Neuroscience Center, School of Medicine, University of North Carolina at Chapel Hill,Chapel Hill, NC USA
Abstract
Behavioral neuroscience relies heavily on controlled environments, such as operant chambers or “Skinner boxes,” to characterize relationships between external stimuli and the resulting animal behavior. Increasingly, these methodologies are critical for the development of neural interfaces which seek to provide or restore sensations via electrical stimulation. To conduct behavioral experiments, researchers have commonly trusted commercial systems, like those from Med Associates, Inc. While offering reliability, high costs and limited customizability have motivated a push towards open-source alternatives, which often involve the use of inexpensive microcontrollers, custom printed circuit boards (PCBs), and freely available codebases. However, despite these developments, there is a lack of comprehensive software solutions that can integrate seamlessly with commercial or custom hardware for behavioral experiments. In this study, we developed a full-stack application utilizing Angular and Flask frameworks to conduct two-alternative forced choice (2AFC) tasks controlled by an Arduino which interfaces with Med Associates, Inc. operant chamber equipment via a custom PCB. The system was tested by conducting a simple operant conditioning procedure and a spinal cord stimulation (SCS) sensory detection experiment using a custom microstimulator in rodents. The analyzed data demonstrated appropriate behavioral learning and sensory detection thresholds, in alignment with previous SCS behavioral studies which utilized commercial or single-tier systems for control of operant chambers. This work demonstrates the effective integration of an open-source full-stack application with existing commercial hardware that can provide adaptable and scalable means for conducting behavioral experiments, crucial for advancing neural interface technologies.
Reproduced under the paper's license (CC BY), from the paper cited above.
Repository
Its files are read in the Code ↔ Paper reader above, with 15 matches between paragraphs and lines of code.
yadavlabs/open-source-rat-behavior
5ff84c93c6b6700e4e5e8fd4360d87e728144505, 7 May 2026Availability: 1 check, the latest on 27 September 2026: the link answers
- 27 September 2026: the link answers
35 files
- Angular Files/
src/ , TypeScript, 31 linesapp/ app.component.spec.ts - Angular Files/
src/ , TypeScript, 16 linesapp/ app.component.ts - Angular Files/
src/ , TypeScript, 45 linesapp/ app.module.ts - Angular Files/
src/ , TypeScript, 16 linesapp/ device-startup/ CurrentTrialData.ts - Angular Files/
src/ , TypeScript, 11 linesapp/ device-startup/ DropDownInfo.ts - Angular Files/
src/ , TypeScript, 23 linesapp/ device-startup/ device-startup.component .spec.ts - Angular Files/
src/ , TypeScript, 300 lines, 3 matchesapp/ device-startup/ device-startup.component .ts - Angular Files/
src/ , TypeScript, 62 lines, 2 matchesapp/ device-startup/ sse.service.ts - Angular Files/
src/ , TypeScript, 16 linesapp/ postDic.ts - Angular Files/
src/ , TypeScript, 23 linesapp/ run-trial/ run-trial.component.spec .ts - Angular Files/
src/ , TypeScript, 71 linesapp/ run-trial/ run-trial.component.ts - Angular Files/
src/ , TypeScript, 185 lines, 1 matchapp/ total.service.ts - Angular Files/
src/ , TypeScript, 7 linesmain.ts - Arduino Files/
operant_task_control/ , C++, 36 linesDOOR.cpp - Arduino Files/
operant_task_control/ , C/C++, 26 linesDOOR.h - Arduino Files/
operant_task_control/ , C++, 35 linesLIGHT.cpp - Arduino Files/
operant_task_control/ , C/C++, 25 linesLIGHT.h - Arduino Files/
operant_task_control/ , C++, 93 linesSPOUT.cpp - Arduino Files/
operant_task_control/ , C/C++, 30 lines, 1 matchSPOUT.h - Flask Project Files/
pythonBackend/ , Python, 380 lines, 4 matchesapplication.py - Flask Project Files/
pythonBackend/ , Python, 66 lineshelper_functions.py - Flask Project Files/
pythonBackend/ , Python, 516 lines, 4 matchesserial_functions.py - Testing/
ComArduino2.py , Python, 174 lines - Testing/
application.bak.py , Python, 121 lines - Testing/
application.py , Python, 311 lines - Testing/
application2.bak.py , Python, 161 lines - Testing/
application3.bak.py , Python, 267 lines - Testing/
ard_functions.bak.py , Python, 38 lines - Testing/
multiserial_test.py , Python, 23 lines - Testing/
serial_coms_testing.py , Python, 16 lines - Testing/
serial_coms_testing_func , Python, 26 linestions.py - Testing/
serial_functions.bak.py , Python, 337 lines - Testing/
test_serial.py , Python, 34 lines - LICENSE, License, 28 lines
- README.md, Text, 105 lines
Code availability
Code is available at the repository (GitHub Repository (https://
Reproduced under the paper's license (CC BY), from the paper cited above.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 33 scripts, each with its path and the digest of its content;
- 15 matches between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
No dataset and no data link were found in the paper.
Data availability
Design files, user manuals, and documentation are available at the repository (GitHub Repository (https://
Reproduced under the paper's license (CC BY), from the paper cited above.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 2, 28 September 2026
- Publisher: n/a → Springer Science+Business Media
Version 1, 27 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 3 authors, 5 keywords, 8 MeSH terms, 2 funders, 28 references.
Cite
This paper
Miller, S., Slack, J. C., & Yadav, A. P. (2026). Low-cost, open-source, full-stack software and Arduino-based hardware for control of commercially available animal behavior systems. Behavior research methods, 58(8), 211. https://
BibTeX
@article{miller2026low,
author = {Miller, Scott and Slack, Jacob C. and Yadav, Amol P.},
title = {{Low-cost, open-source, full-stack software and Arduino-based hardware for control of commercially available animal behavior systems}},
journal = {Behavior research methods},
year = {2026},
month = jun,
volume = {58},
number = {8},
pages = {211},
publisher = {Springer Science+Business Media},
issn = {1554-351X},
doi = {10.3758/
url = {https://
pmid = {42373986},
pmcid = {PMC13315479}
}
RIS
TY - JOUR
AU - Miller, Scott
AU - Slack, Jacob C.
AU - Yadav, Amol P.
TI - Low-cost, open-source, full-stack software and Arduino-based hardware for control of commercially available animal behavior systems
T2 - Behavior research methods
J2 - Behav Res Methods
PY - 2026
DA - 2026/
VL - 58
IS - 8
SP - 211
SN - 1554-351X
PB - Springer Science+Business Media
DO - 10.3758/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.3758/
"type": "article-journal",
"title": "Low-cost, open-source, full-stack software and Arduino-based hardware for control of commercially available animal behavior systems",
"container-title": "Behavior research methods",
"author": [
{
"family": "Miller",
"given": "Scott"
},
{
"family": "Slack",
"given": "Jacob C."
},
{
"family": "Yadav",
"given": "Amol P."
}
],
"container-title-short":
"volume": "58",
"issue": "8",
"page": "211",
"DOI": "10.3758/
"PMID": "42373986",
"PMCID": "PMC13315479",
"ISSN": "1554-351X",
"publisher": "Springer Science+Business Media",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
29
]
]
}
}
The tracing map gets a citation of its own once an author has validated it and it has a DOI.
Similar papers
The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.
- [1] doi:10.1038/s41467-026-76109-y [code]
- Assistive algorithms influence neural representations in motor brain-computer interfaces.Journal: Nature communicationsIn common: pandas, 2 references
- [2] doi:10.1126/sciadv.aef0343 [code]
- Learning induces activation-mechanism-dep
endent neural plasticity in an intracortical microstimulation task. Journal: Science advancesIn common: pandas, 2 references - [3] doi:10.1002/brb3.71654 [code]
- HIF-1α and HIF-2α Are Upregulated in the Hippocampus but Not in the Medial Prefrontal Cortex in Experimental PTSD.Journal: Brain and behaviorIn common: pandas, behavior only, rat
- [4] doi:10.1126/sciadv.adw3876 [code]
- Intracortical brain-computer interface for navigation in virtual reality in macaque monkeys.Journal: Science advancesIn common: pandas, 1 reference
- [5] doi: [code]
- Real-time closed-loop feedback system for mouse mesoscale cortical signal and movement controlJournal: eLifeIn common: pandas, 1 reference
- [6] doi:10.1016/j.celrep.2026.117420 [code]
- Neural population dynamics of direct electrical stimulation of neocortex.Journal: Cell reportsIn common: pandas, 1 reference
- [7] doi:10.1016/j.crmeth.2026.101421 [code]
- EthoPy provides an accessible platform for reproducible behavioral neuroscience.Journal: Cell reports methodsIn common: pandas, behavior only, methods / tools
- [8] doi:10.1117/1.nph.13.s3.s32603 [code]
- SNIRF2BIDS: a GUI-based tool for converting functional near-infrared spectroscopy data to the Brain Imaging Data Structure in R.Journal: NeurophotonicsIn common: methods / tools, 1 reference
- [9] doi:10.1007/s12021-026-09814-0 [code]
- Application of Machine Learning Models to Identify Differences in Neural Electrophysiological Properties Across Estrous Cycle Phases.Journal: NeuroinformaticsIn common: pandas, rat, methods / tools
- [10] doi:10.1038/s41467-026-76939-w [code]
- HIPPIE: a generative model for electrophysiological analysis across species, technologies, and modalities.Journal: Nature communicationsIn common: pandas, rat, methods / tools
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 1 repository of the authors' code, each at its verified commit and with its license, 33 scripts, and 15 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:fad0a9a28b2a3694…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
Request its removal
To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).
Discussion, reproductions, activity
Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.
Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
