OSCR

Low-cost, open-source, full-stack software and Arduino-based hardware for control of commercially available animal behavior systems.

Code ↔ Paper

15 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

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. [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. [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. [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. [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. [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. [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. [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. [8] § Methods › Angular framework ↔ Angular Files/src/app/total.service.ts, lines 1–58 · score 0.69 · SSEService, FlaskService, RESTful, selection, POST, services
  9. [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. [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. [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. [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. [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. [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. [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

  1. # Functions for Arduino Communication
  2. import serial
  3. import serial.tools.list_ports as port_list
  4. import time
  5. import random
  6. def findPorts(): #finds and returns devices ocnnected to serial port
  7. ports = list(port_list.comports())
  8. return ports
  9. def changeSessionParams(ard, params, y):
  10. """ changes all rrelevant session parameters for running 2AFC experiments
  11. # Inputs:
  12. # ard - serial port object for task arduino
  13. # params - dict of paramaters to updated, ex: params = ['Initial Training', 'Detection', '60', '10', 'Yes', '3']
  14. # y - data stream variable for storing/sending display information to angular
  15. """
  16. com = []
  17. # sets session
  18. if params[0] == "Initial Training":
  19. ard.write('P42'.encode('utf-8'))
  20. else:
  21. ard.write('P41'.encode('utf-8'))
  22. # sets session length
  23. ard.write(('P1' + params[2]).encode('utf-8'))
  24. # sets response time
  25. ard.write(('P2' + params[3]).encode('utf-8'))
  26. # sets forced trials
  27. if params[4] == "Yes":
  28. ard.write('P51'.encode('utf-8'))
  29. else:
  30. ard.write('P50'.encode('utf-8'))
  31. # set consecutive error
  32. ard.write(('P3' + params[5]).encode('utf-8'))
  33. #def manualControlDic(ard,com):
  34. com_lookup = {'left-door-true': 'D',
  35. 'left-door-false': 'D',
  36. 'right-door-true': 'd',
  37. 'right-door-false': 'd',
  38. 'left-flush-true': 'L',
  39. 'left-flush-false': 'L',
  40. 'right-flush-true': 'R',
  41. 'right-flush-false': 'R',
  42. 'house-light-true': 'H',
  43. 'house-light-false':'H',
  44. 'buzzer-true': 'B1',
  45. 'buzzer-false': 'B0',
  46. 'test-sensors-true': 'J',
  47. 'test-sensors-false': 'K',
  48. 'pause-true': 'p',
  49. 'pause-false': 'u',
  50. 'stop-N/A': 'Q'}
  51. ## manualControl replaced by above dictionary. Thought that all the if-statements were causing a lot of lag
  52. # between converting the HTTP POST to serial command but it ended up being on the arduino program side.
  53. def manualControl(ard, component, state, y):
  54. """ Commands sent to arduino to control various components in the operant chamber.
  55. # Manual control is only enable before a session is started and during pauses (or fored trials)
  56. # may do something like: comp = {'left-door':{'true':'D1','false':'D0'},'right-door':{'true':'d1','false':'d0'},'left-flush':'L','right-flush':'R'}
  57. # Inputs:
  58. # ard - serial port object for task arduino
  59. # component - string indicating component to control (ex: "right-door")
  60. # state - string indicating button/switch state ("true", "false", or "NAN"
  61. """
  62. if component == "left-door": # open/close left door
  63. if state == "true":
  64. com = 'D1'
  65. else:
  66. com = 'D0'
  67. elif component == "right-door": # open/close right door
  68. if state == "true":
  69. com = 'd1'
  70. else:
  71. com = 'd0'
  72. elif component == "left-flush": # flushes/stop flushes left port
  73. com = 'L'
  74. elif component == "right-flush": # flushed/stop flushed right port
  75. com = 'R'
  76. elif component == "house-light": # turns houselight on/off
  77. if state == "true":
  78. com = 'H1'
  79. else:
  80. com = 'H0'
  81. elif component == "buzzer": # plays short/long buzzer tone
  82. if state == "true":
  83. com = 'B1' # short
  84. else:
  85. com = 'B0' # long
  86. elif component == "test-sensors": # allows for testing of sensors and reward delivery
  87. if state == "true":
  88. com = 'J' # initiates sensor testing
  89. else:
  90. com = 'K' # ends sensor testing
  91. elif component == "start": # starts session (isn't used here)
  92. com = 'b'
  93. elif component == "stop": # stops session
  94. com = 'Q'
  95. elif component == "pause": # pauses session
  96. if state == "true":
  97. com = 'p' # pause
  98. else:
  99. com = 'u' # unpause
  100. ard.write(com.encode('utf-8'))
  101. def randomizeAmplitude(gib, stimParams, y):
  102. """ function for randomizing amplitude of stimulation
  103. # Inputs:
  104. # gib - serial port object for stimulator
  105. # stimParams - dict of stimulation params (ex: line 76 in application.py
  106. # y - data stream variable for storing/sending display information to angular
  107. """
  108. if stimParams["amp_indx"] == len(stimParams["task_amps"]): #check if every amplitude in stimParams["task_amps"] has been used
  109. stimParams["amp_indx"] = 0 #reset index
  110. print("Amp_indx asaa: " + str(stimParams["amp_indx"]))
  111. stimParams["shuffled_amps"] = random.sample(
  112. stimParams["task_amps"],
  113. len(stimParams["task_amps"])) #reshuffle amplitudes
  114. y.append("amplitude index: " + str(stimParams["amp_indx"])) #update display in anglar
  115. stimParams["amplitude"] = stimParams["shuffled_amps"][stimParams["amp_indx"]]
  116. changeAmplitude(gib, stimParams["amplitude"], y)
  117. stimParams["amp_indx"] = stimParams["amp_indx"] + 1
  118. def arduinoTask(ard, gib, y, sessionData, currentTrialData, stimParams):
  119. """serial port "listener" to perform specific actions depending on what arduino writes to port
  120. # Inputs:
  121. # ard - serial port object for task arduino
  122. # gib - serial port object for stimulator
  123. # y - data stream variable for storing/sending display information to angular
  124. # sessionData - dict to store relevant behavioral data during session
  125. # currentTrialData - dict to update trial table in angular
  126. # stimParams - dict containing parameters of stimulation
  127. """
  128. break_flag = 0 # flag to break external while loop if present
  129. while ard.in_waiting == 0: #do nothing until bytes are available to read
  130. pass
  131. x = ard.read_until(expected=b'\r\n').decode("utf").rstrip()
  132. print(x)
  133. data = x.split(',')
  134. if data[0] == "Connected":
  135. print("Arduino " + data[0])
  136. print("Manual Control Enabled")
  137. y.append("Arduino " + data[0])
  138. y.append("Manual Control Enabled")
  139. break_flag = 1
  140. elif data[0] == "Start":
  141. print("Beginning Session")
  142. y.append("Beginning Session")
  143. elif data[0] == "Trial":
  144. print("Trial time: " + data[1])
  145. print("Trial Number: " + data[2])
  146. y.append("Trial Number: " + data[2])
  147. trial_time = int(data[1]) / 1000 # to seconds
  148. ## update data
  149. currentTrialData["sess_time"] = str(round(trial_time / 60,2)) #to minutes
  150. currentTrialData["trial_n"] = data[2]
  151. currentTrialData["trial_type"] = "-"
  152. currentTrialData["stim_A"] = "-"
  153. currentTrialData["stim_fre"] = "-"
  154. currentTrialData["CV"] = "-"
  155. currentTrialData["trial_res"] = "-"
  156. sessionData["trial_time"].append(trial_time)
  157. sessionData["trial_number"].append(int(data[2]))
  158. elif data[0] == "Type":
  159. if data[1] == "2": #right trial, no stimulation for detection experiment
  160. sessionData["amplitude"].append([])
  161. sessionData["frequency"].append([])
  162. sessionData["CV"].append([])
  163. else: #left trial, stimulation if CV experiment is selected
  164. if stimParams["stim_enable"] == 1:
  165. if stimParams["randomize"] == 1:
  166. randomizeAmplitude(gib, stimParams, y)
  167. currentTrialData["stim_A"] = str(stimParams["amplitude"])
  168. currentTrialData["stim_fre"] = str(stimParams["frequency"])
  169. currentTrialData["CV"] = str(stimParams["CV"])
  170. sessionData["amplitude"].append(stimParams["amplitude"])
  171. sessionData["frequency"].append(stimParams["frequency"])
  172. sessionData["CV"].append(stimParams["CV"])
  173. else:
  174. sessionData["amplitude"].append([])
  175. sessionData["frequency"].append([])
  176. sessionData["CV"].append([])
  177. datStr = ard.read_until(expected=b'\r\n').decode("utf").rstrip()
  178. y.append(datStr)
  179. currentTrialData["trial_type"] = data[1]
  180. currentTrialData["forced"] = data[2]
  181. sessionData["trial_type"].append(int(data[1]))
  182. sessionData["forced"].append(int(data[2]))
  183. sessionData["randomized"].append(stimParams["randomize"])
  184. sessionData["response"].append([])
  185. sessionData["response_time"].append([])
  186. sessionData["correct"].append([])
  187. sessionData["percent"].append([])
  188. elif data[0] == "Stim":
  189. if data[1] == "1": #left trial
  190. if gib.is_open == 1: #stimParams["stimEnable"] == 1
  191. stimulate(gib, stimParams, y)
  192. print("Stim")
  193. y.append("Stim")
  194. else:
  195. print("No stim")
  196. y.append("No stim")
  197. elif data[0] == "Response":
  198. res_time = int(data[1]) / 1000
  199. if data[3] == "1":
  200. datStr = "correct."
  201. elif data[3] == "0":
  202. datStr = "incorrect."
  203. elif data[3] == "5":
  204. #data[3] = "0"
  205. datStr = "forced."
  206. print("Response Time: " + str(res_time) + "sec")
  207. if data[2] == "1":
  208. datStr = "Left Port Response, " + datStr
  209. elif data[2] == "2":
  210. datStr = "Right Port Response, " + datStr
  211. elif data[2] == "5":
  212. datStr = "No response."
  213. print(datStr)
  214. y.append(datStr)
  215. currentTrialData["trial_res"] = data[2]
  216. sessionData["response_time"][-1] = res_time
  217. sessionData["response"][-1] = int(data[2])
  218. sessionData["correct"][-1] = int(data[3])
  219. elif data[0] == "Percent":
  220. percent = float(data[1]) * 100
  221. currentTrialData["per_cor"] = str(percent)
  222. sessionData["percent"][-1] = percent
  223. y.append("Running percentage correct: " + str(percent) + "%")
  224. elif data[0] == "End":
  225. print("Session Ended")
  226. y.append("Session Ended")
  227. break_flag = 1
  228. elif data[0] == "Wait":
  229. print("Manual Control Enabled")
  230. y.append("Manual Control Enabled")
  231. #break_flag = 1
  232. elif data[0] == "Wait for Response":
  233. print("Waiting for response...")
  234. y.append("Waiting for response...")
  235. #elif data[0] == "Paused":
  236. # print("Paused")
  237. # y.append(data[0])
  238. else:
  239. y.append(data[0])
  240. return break_flag
  241. '''
  242. The following functions are used for communication with the gibson stimulator:
  243. - 'waitForGibson' = establishes connection to stimulator
  244. - 'readGibData' = reads/clears data written by stimulator on serial port
  245. - 'convertToBytes' = configures command to stimulator to change parameters
  246. - 'currentConfig' = configures current for writing to gibson
  247. - 'changePulseNumber' = changes the number of pulses in stimulation pulse-train
  248. - 'changePulseWidth' = changes the pulse-width of stimulation pulse-train (us)
  249. - 'changeIPI' = changes the inter-phase interval (us)
  250. - 'changeFrequency' = changes frequency (Hz)
  251. - 'changeAmplitude' = changes amplitude of pulse-train (uA)
  252. - 'changeCV' = changes the coefficient of variation (aperiodicity) of pulse-train (can be 0, 0.1, 0.2,..., 1.0)
  253. - 'changeStimParams' = changes all stimulation parameters, primarily used for initial setup
  254. - 'stimulate' = sends command to deliver stimulation based on parameters set by the previous functions
  255. To change stimulation parameters:
  256. 1. the stimulation channel must first be selected (ch)
  257. -In the case here, only a single channel is used (channel 0) and is hard coded. This may be changed for future applications
  258. 2. the parameter to be changed is selected (val_ind)
  259. -pulse number -> 1
  260. -pulse width -> 2
  261. -inter-phase interval -> 3
  262. -frequency -> 4
  263. -amplitude requires sequence of three bytes:
  264. -> 5, 6, 7
  265. -CV -> 8
  266. 3. the corresponding parameter value is written (val)
  267. -ex: for pulse width -> 256
  268. -ex: for frequency -> 50
  269. 4. Function 'convertToBytes' takes the 3 aformentioned values and appropriately configures them for writing to serial port
  270. '''
  271. def waitForGibson(gib, y):
  272. """ establishes connection to stimulator
  273. Inputs:
  274. gib - serial port object for stimulator
  275. y - data stream variable for storing/sending display information to angular
  276. """
  277. gib.write((1).to_bytes(1, byteorder="big"))#write_uint8(gib,0)
  278. msg = ""
  279. while msg.find("Connected") == -1:
  280. while gib.in_waiting == 0:
  281. pass
  282. msg = gib.read_until(expected=b'\r\n').decode("utf").rstrip()
  283. print(msg)
  284. y.append("Gibson " + msg)
  285. def readGibData(gib, r_max, y):
  286. """ reads serial port of stimulator
  287. Inputs:
  288. gib - serial port object for stimulator
  289. r_max - indicate number of reponses (lines) from device
  290. y - data stream variable for storing/sending display information to angular
  291. """
  292. while gib.in_waiting == 0: # wait until bytes are on the serial port
  293. pass
  294. i = 0
  295. while i < r_max:
  296. x = gib.read_until(expected=b'\r\n').decode("utf").rstrip()
  297. print(x)
  298. y.append(x)
  299. i = i + 1
  300. def convertToBytes(ch, val_ind, val):
  301. """ Converts channel (ch), parameter index (val_ind), and parameter value (val) to
  302. bytes and joins them together.
  303. Inputs:
  304. ch - stimulation channel, zero-index (0-3, but 0 is only in use)
  305. val_ind - value specifying the parameter to be changed
  306. val - value of parameter to change
  307. """
  308. CH = ch.to_bytes(1, byteorder="big")
  309. VAL_IND = val_ind.to_bytes(1, byteorder="big")
  310. if val_ind == 1: #pulse number can be greater than 255
  311. VAL = val.to_bytes(2, byteorder="little") #give pulse number 2 bytes
  312. else:
  313. VAL = val.to_bytes(1, byteorder="big")
  314. COM = b''.join([CH, VAL_IND, VAL])
  315. return COM
  316. def currentConfig(current):
  317. '''
  318. -converted from matlab script 'currentconfig.m'
  319. 0-800 for current <---> 0-4095 decimal
  320. 800 / 4095 = 5.11875
  321. value = current * 5.11875 (round to positive int)
  322. convert to 12-bit binary
  323. '''
  324. dac_volt = format(int(round(current * 5.11875)), '012b')
  325. '''
  326. Correction factor required? Add here ------
  327. Take last 8-bits of dac_volt, convert to uint8 dec and assign to ab_byte
  328. '''
  329. ab_byte = int(dac_volt[4:12], 2).to_bytes(1, byteorder='big')
  330. # Take 1st 4-bits of dac_volt and add to back of dac_A configuration
  331. # Convert to uint8 dec and assign to a_byte
  332. dacAconfig = format(1, '04b') # 1 = 0001
  333. a_byte = int(dacAconfig + dac_volt[0:4], 2).to_bytes(1, byteorder='big')
  334. # Take 1st 4-bits of dac_volt and add to back of dac_B configuration
  335. # Convert to uint8 dec and assign to b_byte
  336. dacBconfig = format(9, '04b') # 9 = 1001
  337. b_byte = int(dacBconfig + dac_volt[0:4], 2).to_bytes(1, byteorder='big')
  338. return a_byte, b_byte, ab_byte
  339. def changePulseNumber(gib, pnVal, y):
  340. """ changes pulse number value.
  341. # Inputs:
  342. # gib - serial port object for stimulator
  343. # pnVal - number of pulses, string (ex: '300')
  344. # y - data stream variable for storing/sending display information to angular
  345. """
  346. COM = convertToBytes(0, 1, int(pnVal))
  347. gib.write(COM)
  348. readGibData(gib, 2, y)
  349. def changePulseWidth(gib, pwVal, y):
  350. """ changes pulse width.
  351. # Inputs:
  352. # gib - serial port object for stimulator
  353. # pwVal - pulse width in us, string (ex: '200')
  354. # y - data stream variable for storing/sending display information to angular
  355. """
  356. COM = convertToBytes(0, 2, int(pwVal))
  357. gib.write(COM)
  358. readGibData(gib, 2, y)
  359. def changeIPI(gib, ipiVal, y):
  360. """ changes inter-phase interval.
  361. # Inputs:
  362. # gib - serial port object for stimulator
  363. # ipiVal - inter-phase interval in us, string (ex: '50')
  364. # y - data stream variable for printing reads from serial port
  365. """
  366. COM = convertToBytes(0, 3, int(ipiVal))
  367. gib.write(COM)
  368. readGibData(gib, 2, y)
  369. def changeFrequency(gib, freqVal, y):
  370. """ changes frequency.
  371. # Inputs:
  372. # gib - serial port object for stimulator
  373. # freqVal - frequency in Hz, string (ex: '50')
  374. # y - data stream variable for storing/sending display information to angular
  375. """
  376. COM = convertToBytes(0, 4, int(freqVal))
  377. gib.write(COM)
  378. readGibData(gib, 2, y)
  379. def changeAmplitude(gib, ampVal, y):
  380. """ changes amplitde.
  381. # Inputs:
  382. # gib - serial port object for stimulator
  383. # ampVal - amplitude in uA, string (ex: '250')
  384. # y - data stream variable for storing/sending display information to angular
  385. """
  386. a_byte, b_byte, ab_byte = currentConfig(int(ampVal))
  387. a_int = int.from_bytes(a_byte, byteorder='big')
  388. b_int = int.from_bytes(b_byte, byteorder='big')
  389. ab_int = int.from_bytes(ab_byte, byteorder='big')
  390. aCOM = convertToBytes(0, 5, a_int)
  391. gib.write(aCOM)
  392. bCOM = convertToBytes(0, 6, b_int)
  393. gib.write(bCOM)
  394. abCOM = convertToBytes(0, 7, ab_int)
  395. gib.write(abCOM)
  396. readGibData(gib, 3, y)
  397. def changeCV(gib, cvVal, y):
  398. """ changes CV value.
  399. Inputs:
  400. gib - serial port object for stimulator
  401. cvVal - coeficient of variation value, string (ex: '0.8')
  402. y - data stream variable for storing/sending display information to angular
  403. """
  404. COM = convertToBytes(0, 8, int(float(cvVal) * 10)) #cv is read as 0-10 so multiply by 10
  405. gib.write(COM)
  406. readGibData(gib, 2, y)
  407. def changeStimParams(gib, params, stimParams, y):
  408. """ sequentially changes all stimulation parameters
  409. Inputs:
  410. gib - serial port object for stimulator
  411. params - list of parameters as strings sent from angular to be changed
  412. stimParams - dict containing parameters of stimulation
  413. y - data stream variable for storing/sending display information to angular
  414. """
  415. ampStr = params[0]
  416. stimParams["amplitude"] = int(ampStr)
  417. stimParams["base_amp"] = int(ampStr)
  418. freqStr = params[1]
  419. stimParams["frequency"] = int(freqStr)
  420. pwStr = params[2]
  421. stimParams["pulse_width"] = int(pwStr)
  422. ipiStr = params[3]
  423. stimParams["ipi"] = int(ipiStr)
  424. pnStr = params[4]
  425. stimParams["pulse_num"] = int(pnStr)
  426. cvStr = params[5]
  427. stimParams["CV"] = float(cvStr)
  428. if stimParams["CV"] > 0:
  429. stimParams["periodic"] = 1
  430. else:
  431. stimParams["periodic"] = 0
  432. changeAmplitude(gib, ampStr, y)
  433. changeFrequency(gib, freqStr, y)
  434. changePulseWidth(gib, pwStr, y)
  435. changeIPI(gib, ipiStr, y)
  436. changePulseNumber(gib, pnStr, y)
  437. changeCV(gib, cvStr, y)
  438. def stimulate(gib, stimParams, y):
  439. """ delivers stimulation based on set stimulation parameters
  440. Inputs:
  441. gib - serial port object for stimulator
  442. stimParams - dict containing parameters of stimulation
  443. y - data stream variable for storing/sending display information to angular
  444. """
  445. gib.write((0 + 4).to_bytes(1, "big")) # '0' is the channel, stim command is channel + 4
  446. if stimParams["periodic"] == 0:
  447. readGibData(gib, 1, y)
  448. else:
  449. readGibData(gib, 2, y)

serial_functions.py at commit 5ff84c9, under BSD-3-Clause · at the source

Overview

Authors: Scott Miller1, Jacob C. Slack2, Amol P. Yadav2,3,4
ORCID iDs: Jacob C. Slack
  1. Department of Biomedical Engineering, University of Michigan,Ann Arbor, MI USA
  2. Lampe Joint Department of Biomedical Engineering, University of North Carolina at Chapel Hill and North Carolina State University,Chapel Hill, NC USA
  3. Neurosurgery, School of Medicine, University of North Carolina at Chapel Hill,Chapel Hill, NC USA
  4. Neuroscience Center, School of Medicine, University of North Carolina at Chapel Hill,Chapel Hill, NC USA
Institutions: University of Michigan (United States); North Carolina State University (United States); University of North Carolina at Chapel Hill (United States)
Journal: Behavior research methods, volume 58, issue 8, article 211
Dates: received 10 March 2025; accepted 27 May 2026; published online 29 June 2026; in print 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.3758/s13428-026-03096-9 · PMID 42373986 · PMCID PMC13315479 · OpenAlex W7166509320
Open access: hybrid, a free copy (OpenAlex)
Status: code verified
Categories: behavior only (modality), rat (organism), methods / tools (subfield)
Methods: Spectral & time-frequency, Statistics
Keywords: Open-source, Operant conditioning, Behavioral experiments, Full stack, Arduino
MeSH: Behavior, Animal*, Behavioral Research*, Conditioning, Operant*, Software*, Animals, Electric Stimulation, Male, Rats (* major topic)
Journal subjects: Original Manuscript
Topic: EEG and Brain-Computer Interfaces (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Funding: NIH (7DP2NS136872-02); Indiana State Department of Health
Citations: not cited yet (Europe PMC); 31 references in the paper

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

License: BSD-3-Clause
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 5ff84c93c6b6700e4e5e8fd4360d87e728144505, 7 May 2026
Languages: Python (14), TypeScript (13), C++ (3), C/C++ (3)
Size: 79 files, 33 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, license file, environment (requirements.txt, Testing/requirements.txt), tests, documentation
Not found: CITATION.cff, continuous integration
Tools: pandas (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
35 files

Code availability

Code is available at the repository (GitHub Repository (https://github.com/yadavlabs/open-source-rat-behavior)).

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://github.com/yadavlabs/open-source-rat-behavior)). Links to specific locations in the repository are referenced throughout the paper when applicable. Behavioral data can be made available upon reasonable request.

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://doi.org/10.3758/s13428-026-03096-9

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/s13428-026-03096-9},
url = {https://doi.org/10.3758/s13428-026-03096-9},
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/06/29
VL - 58
IS - 8
SP - 211
SN - 1554-351X
PB - Springer Science+Business Media
DO - 10.3758/s13428-026-03096-9
UR - https://doi.org/10.3758/s13428-026-03096-9
LA - en
ER -

CSL-JSON

{
"id": "10.3758/s13428-026-03096-9",
"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": "Behav Res Methods",
"volume": "58",
"issue": "8",
"page": "211",
"DOI": "10.3758/s13428-026-03096-9",
"PMID": "42373986",
"PMCID": "PMC13315479",
"ISSN": "1554-351X",
"publisher": "Springer Science+Business Media",
"URL": "https://doi.org/10.3758/s13428-026-03096-9",
"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 communications
In common: pandas, 2 references
[2] doi:10.1126/sciadv.aef0343 [code]
Learning induces activation-mechanism-dependent neural plasticity in an intracortical microstimulation task.
Journal: Science advances
In 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 behavior
In 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 advances
In common: pandas, 1 reference
[5] doi: [code]
Real-time closed-loop feedback system for mouse mesoscale cortical signal and movement control
Journal: eLife
In common: pandas, 1 reference
[6] doi:10.1016/j.celrep.2026.117420 [code]
Neural population dynamics of direct electrical stimulation of neocortex.
Journal: Cell reports
In 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 methods
In 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: Neurophotonics
In 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: Neuroinformatics
In 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 communications
In 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.

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.