OSCR

Measuring Electrophysiological Activity in Acute Brain Slices, Spheroids, and Organoids Using 3D High-Density Multielectrode Arrays.

Code ↔ Paper

The paper beside its authors' code: matches between them have not been computed for this paper yet.

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 · 1,693 lines · 58 KB · no license

  1. # -*- coding: utf-8 -*-
  2. """
  3. Created on Mon Jan 30 16:16:17 2023
  4. @author: Francesco Mainardi
  5. """
  6. from scipy import stats
  7. import customtkinter
  8. import numpy as np
  9. import pandas as pd
  10. import h5py
  11. import matplotlib.pyplot as plt
  12. import os
  13. from tkinter import PhotoImage
  14. import glob
  15. from scipy.signal import butter, filtfilt
  16. import json
  17. from tkinter import filedialog
  18. from tkinter import Tk
  19. from tkinter.filedialog import askdirectory
  20. import subprocess
  21. import pickle
  22. import matplotlib.cm as cm
  23. df=pd.read_csv(r'channels ids table.csv', header=None,sep=';')
  24. # df2=pd.DataFrame(np.zeros((1,64)))
  25. # df=pd.concat([df, df2])
  26. # df.insert(64, int(64), np.zeros(65))
  27. # df=df.shift(periods=1)
  28. # df=df.shift(periods=1, axis='columns')
  29. conditions='Y'
  30. fpathDATA =''
  31. fnBRW =''
  32. fnBXR=''
  33. saving_path=''
  34. dfPSTH=0
  35. time_window_total=0
  36. blind_time_ms=0
  37. lfpduration_ms=0
  38. N2A_N2B_resolution_ms=0
  39. stim_duration_ms=0
  40. precisionvalue_ms=1
  41. lowpassvalue=0
  42. min_peak=0
  43. max_peak=0
  44. min_peak_distance_ms=0
  45. time_sec=0
  46. check_time_ms=0
  47. time_window_division_sec=1
  48. i=0
  49. ROI=[]
  50. ROIstr=[]
  51. ID=0
  52. freqvalue=0
  53. stimtimes=0
  54. map_mea = np.zeros((64, 64))
  55. b_map=[]
  56. c_map=[]
  57. bin_size=0
  58. ISI_bin_size=0
  59. bin_max=0
  60. pre_stimulus=0
  61. post_stimulus=0
  62. num_permutations=0
  63. total_time=0
  64. tart_frames=np.zeros(10000)
  65. P_Values=np.zeros(len(ROI))
  66. BRpath=""
  67. ROIpath=""
  68. save_folder=""
  69. BXR=""
  70. BRW=""
  71. localize_array=np.zeros(4096)
  72. customtkinter.set_appearance_mode("dark")
  73. customtkinter.set_default_color_theme("dark-blue")
  74. root= customtkinter.CTk(className="Python Analysis software for HD-MEA", fg_color="black")
  75. root.geometry("1890x920")
  76. root.title("Python Analysis software for HD-MEA")
  77. root.resizable(width=False, height=False)
  78. #img_bg=PhotoImage(file="python.png")
  79. #labelb= customtkinter.CTkLabel(master=root, width=250, height=250,text="", image=img_bg, fg_color="transparent")
  80. #labelb.place(x=725, y=315)
  81. def set_nan_in_lfp_matrix(lfp_matrix, lfp_N2A):
  82. for i in range(lfp_matrix.shape[0]):
  83. if np.isnan(lfp_N2A[i]):
  84. lfp_matrix[i, :] = np.nan
  85. return lfp_matrix
  86. def testprint():
  87. print(localize_array)
  88. def apri_image_processing():
  89. global localize_array
  90. subprocess.call(["python", "ImageProcessing.py"])
  91. with open('array.dat', 'rb') as file:
  92. tag_array = pickle.load(file)
  93. localize_array=tag_array
  94. df
  95. def apri_concatenator():
  96. subprocess.call(["python", "concatenator_new_1.3_GUI.py"])
  97. def on_closing():
  98. root.destroy()
  99. root.quit()
  100. def browse_ROI():
  101. global ROIpath
  102. loadROIpath.delete(0,1000)
  103. ROIpath = filedialog.askopenfilename()
  104. loadROIpath.insert(0, ROIpath)
  105. def browse_BRW():
  106. global BRW
  107. pacefname.delete(0,1000)
  108. BRW = filedialog.askopenfilename()
  109. pacefname.insert(0, BRW)
  110. def browse_BXR():
  111. global BXR
  112. fnameBXR.delete(0,1000)
  113. BXR = filedialog.askopenfilename()
  114. fnameBXR.insert(0, BXR)
  115. def browse_save():
  116. global save_folder
  117. savepacepath.delete(0,1000)
  118. root = Tk()
  119. root.withdraw()
  120. save_folder = askdirectory()
  121. savepacepath.insert(0, save_folder)
  122. def converti_coordinate_in_id(riga, colonna, num_colonne):
  123. return (riga - 1) * num_colonne + (colonna - 1)
  124. def estrai_riga_colonna_da_json(file_json):
  125. righe = []
  126. colonne = []
  127. # Carica il file JSON
  128. with open(file_json) as f:
  129. data = json.load(f)
  130. # Estrai le righe e le colonne di ogni elemento
  131. for group in data['ChsGroups']:
  132. for elemento in group['Chs']:
  133. righe.append(elemento['Row'])
  134. colonne.append(elemento['Col'])
  135. return righe, colonne
  136. def ROI_from_ROI():
  137. righe, colonne = estrai_riga_colonna_da_json(loadROIpath.get())
  138. global ROI
  139. for i in range(len(righe)):
  140. ROI.append(converti_coordinate_in_id(righe[i], colonne[i], 64))
  141. ids.delete(0,1000)
  142. global ROIstr
  143. stringID=str(ROI[i])
  144. ROIstr.append('['+stringID+']')
  145. ids.insert(0, ROIstr)
  146. global map_mea
  147. global b_map
  148. global c_map
  149. num1=righe[i]
  150. num2=colonne[i]
  151. b_map.append(num1-1)
  152. c_map.append(num2-1)
  153. map_mea[b_map, c_map]=100
  154. plt.imshow(map_mea, cmap='brg', interpolation='nearest')
  155. plt.savefig('img.png', bbox_inches='tight', transparent=True, dpi=60)
  156. img_map=PhotoImage(file="img.png")
  157. frame = customtkinter.CTkFrame(master=root, width=120, height=120, fg_color="transparent")
  158. frame.place(x=1040, y=0)
  159. label= customtkinter.CTkLabel(master=frame, width=120, height=120, text="", image=img_map)
  160. label.pack(pady=2, padx=1)
  161. def add_to_list ():
  162. ids.delete(0,1000)
  163. global ROI
  164. global ROIstr
  165. ROI.append(ID)
  166. stringID=str(ID)
  167. ROIstr.append('['+stringID+']')
  168. ids.insert(0, ROIstr)
  169. global map_mea
  170. global b_map
  171. global c_map
  172. num1=int(entry1.get())
  173. num2=int(entry2.get())
  174. b_map.append(num1-1)
  175. c_map.append(num2-1)
  176. map_mea[b_map, c_map]=100
  177. plt.imshow(map_mea, cmap='brg', interpolation='nearest')
  178. plt.savefig('img.png', bbox_inches='tight', transparent=True, dpi=60)
  179. img_map=PhotoImage(file="img.png")
  180. frame = customtkinter.CTkFrame(master=root, width=120, height=120, fg_color="transparent")
  181. frame.place(x=1040, y=0)
  182. label= customtkinter.CTkLabel(master=frame, width=120, height=120, text="", image=img_map)
  183. label.pack(pady=2, padx=1)
  184. def ROI_clear ():
  185. global ROI
  186. ROI=[]
  187. global ROIstr
  188. ROIstr=[]
  189. global b_map
  190. global c_map
  191. global map_mea
  192. b_map=[]
  193. c_map=[]
  194. map_mea=np.zeros((64, 64))
  195. plt.imshow(map_mea, cmap='brg', interpolation='nearest')
  196. plt.savefig('img.png', bbox_inches='tight', transparent=True, dpi=60)
  197. img_map=PhotoImage(file="img.png")
  198. frame = customtkinter.CTkFrame(master=root, width=120, height=120, fg_color="transparent")
  199. frame.place(x=1040, y=0)
  200. label= customtkinter.CTkLabel(master=frame, width=120, height=120, text="", image=img_map)
  201. label.pack(pady=2, padx=1)
  202. ids.delete(0,1000)
  203. def assign_path_pace ():
  204. global fnBRW
  205. global saving_path
  206. global fnBXR
  207. fnBRW = str(pacefname.get())
  208. fnBXR = str(fnameBXR.get())
  209. saving_path = str(savepacepath.get())
  210. def conversion ():
  211. global ID
  212. RESULTS.delete(0, 10)
  213. num1=int(entry1.get())
  214. num2=int(entry2.get())
  215. #b_map.append(num1-1)
  216. #c_map.append(num2-1)
  217. ID=int(df.loc[num1, num2])
  218. RESULTS.insert(0, ID)
  219. def lfp_duration(value):
  220. VALUE.delete(0,25)
  221. global lfpduration_ms
  222. lfpduration_ms=value
  223. VALUE.insert(0, value)
  224. def N2resolution(value):
  225. VALUE1.delete(0,25)
  226. global N2A_N2B_resolution_ms
  227. N2A_N2B_resolution_ms=value
  228. VALUE1.insert(0, value)
  229. def blind_time(value):
  230. VALUE2.delete(0,25)
  231. global blind_time_ms
  232. blind_time_ms=round(value, 1)
  233. VALUE2.insert(0, round(value, 1))
  234. def lowpassfilter(value):
  235. VALUE3.delete(0,25)
  236. global lowpassvalue
  237. lowpassvalue=value
  238. VALUE3.insert(0, value)
  239. def set_freqvalue01():
  240. global freqvalue
  241. freqvalue=0.1
  242. def set_freqvalue6():
  243. global freqvalue
  244. freqvalue=6
  245. def set_freqvalue10():
  246. global freqvalue
  247. freqvalue=10
  248. def set_freqvalue20():
  249. global freqvalue
  250. freqvalue=20
  251. def set_freqvalue50():
  252. global freqvalue
  253. freqvalue=50
  254. def set_freqvalue100():
  255. global freqvalue
  256. freqvalue=100
  257. def set_stimvalue1():
  258. global stimtimes
  259. stimtimes=0
  260. def set_stimvalue5():
  261. global stimtimes
  262. stimtimes=5
  263. def set_stimvalue10():
  264. global stimtimes
  265. stimtimes=10
  266. def binvalue(value):
  267. VALUE5.delete(0,25)
  268. global bin_size
  269. bin_size=int(value)
  270. VALUE5.insert(0, value)
  271. def binvalue2(value):
  272. VALUE52.delete(0,25)
  273. global ISI_bin_size
  274. ISI_bin_size=int(value)
  275. VALUE52.insert(0, value)
  276. def bin_max_value(value):
  277. VALUE53.delete(0,25)
  278. global bin_max
  279. bin_max=int(value)
  280. VALUE53.insert(0, value)
  281. def total_time(value):
  282. VALUE54.delete(0,25)
  283. global total_time
  284. total_time=int(value)
  285. VALUE54.insert(0, value)
  286. def prevalue(value):
  287. VALUE6.delete(0,25)
  288. global pre_stimulus
  289. pre_stimulus=int(value)
  290. VALUE6.insert(0, value)
  291. def postvalue(value):
  292. VALUE7.delete(0,25)
  293. global post_stimulus
  294. post_stimulus=int(value)
  295. VALUE7.insert(0, value)
  296. def permvalue(value):
  297. VALUE8.delete(0,25)
  298. global num_permutations
  299. num_permutations=int(value)
  300. VALUE8.insert(0, value)
  301. def spont_activity():
  302. assign_path_pace()
  303. global fpathDATA
  304. global fnBRW
  305. global fnBXR
  306. global saving_path
  307. global ISI_bin_size
  308. global pre_stimulus
  309. global post_stimulus
  310. global num_permutations
  311. global dfPSTH
  312. global c
  313. global P_Values
  314. global time_window_total
  315. global localize_array
  316. global total_time_s
  317. global bin_max
  318. ROI.sort()
  319. plotfolder="ISI plots"
  320. plotpath=os.path.join(saving_path, plotfolder)
  321. os.mkdir(plotpath)
  322. f = h5py.File(fnBXR,mode='r')
  323. SpikeTimes=np.array(f['3BResults']['3BChEvents']['SpikeTimes'])
  324. SpikeChIDs=np.array(f['3BResults']['3BChEvents']['SpikeChIDs'])
  325. SpikeUnits=np.array(f['3BResults']['3BChEvents']['SpikeUnits'])
  326. ChEvents=[SpikeChIDs, SpikeTimes, SpikeUnits]
  327. df=pd.DataFrame(data=ChEvents)
  328. c=0
  329. limit = int(bin_max/ISI_bin_size)
  330. ISI3d = np.zeros((limit, int(len(ROI))))
  331. isi_bins = np.arange(0, bin_max, ISI_bin_size)
  332. ISIdf=pd.DataFrame(index=isi_bins)
  333. ISIdf.index.name='BIN (ms)'
  334. features_list=['AV. FREQ', 'MODE FREQ.', 'SEM', 'CV', 'CV2']
  335. ISIfeatures=pd.DataFrame(index=features_list)
  336. for _ in range(len(ROI)):
  337. valore_cercato = ROI[c]
  338. # Seleziona la prima riga
  339. prima_riga = df.iloc[0]
  340. # Filtra colonne mantenendo solo quella in cui compare il valore cercato
  341. colonna_mantenuta = prima_riga[prima_riga == valore_cercato]
  342. # Seleziona solo le colonne desiderate dal DataFrame originale
  343. df_filtrato = df[colonna_mantenuta.index]
  344. ultima_riga = df_filtrato.iloc[-1]
  345. # Filtra colonne mantenendo solo quella in cui compare il valore cercato
  346. colonna_mantenuta = ultima_riga[ultima_riga > 0]
  347. # Seleziona solo le colonne desiderate dal DataFrame originale
  348. df_filtrato = df_filtrato[colonna_mantenuta.index]
  349. ISI_values, features = calculate_isi_histogram(df_filtrato.iloc[1], ISI_bin_size, bin_max, valore_cercato, plotpath)
  350. dfISI=pd.DataFrame(data=ISI_values)
  351. dfISI = dfISI.rename(columns={0: 'bin', 1: 'probability'})
  352. dfISI.to_csv('%s/ISI_Channel_%s.csv'%(saving_path, ROI[c]), decimal=',', sep =';')
  353. array= ISI_values[:,1]
  354. if len(array) < (limit) :
  355. array=np.pad(array, (0, limit - len(array)), 'constant')
  356. else:
  357. array=array[:limit]
  358. ISI3d[:, c] = array
  359. ISIdf[ROI[c]]=array
  360. ISIfeatures[ROI[c]]=features
  361. c=c+1
  362. ISIdf.to_csv('%s/ISI_Master.csv'%(saving_path), decimal=',', sep =';')
  363. ISIfeatures.to_csv('%s/Units_Features.csv'%(saving_path), decimal=',', sep =';')
  364. i=0
  365. fig = plt.figure(figsize=(8, 6))
  366. ax = fig.add_subplot(111, projection='3d')
  367. #colors = np.tile(['r', 'g', 'b', 'c', 'm', 'y', 'k', 'w', 'orange', 'purple'], 10)[:100]
  368. #colors = plt.cm.tab20.colors[:100]
  369. num_colors = 300
  370. colors = [cm.hsv(i/num_colors) for i in range(num_colors)]
  371. color_hex = [cm.colors.to_hex(color) for color in colors]
  372. set_lim=len(ROI)
  373. #x = np.arange(0, limit)
  374. x=isi_bins
  375. ys = np.arange(len(ROI))
  376. z=np.zeros(len(x))
  377. dx=ISI_bin_size
  378. ax.set_ylim([0, set_lim])
  379. # Plottaggio delle barre
  380. for i in range(len(ys)):
  381. y=ys[i]
  382. dz = ISI3d[:, i]
  383. ax.bar3d(x, y, z, dx, 0.1, dz, color=colors[i])
  384. i=i+1
  385. ax.xaxis.pane.fill = False
  386. ax.yaxis.pane.fill = False
  387. ax.zaxis.pane.fill = False
  388. ax.set_zlim(0, 0.5)
  389. ax.set_zlabel('Probability density')
  390. plt.xlabel('ISI (ms)')
  391. plt.ylabel('Channel ID')
  392. plt.yticks(np.arange(len(ROI)), ROI, fontsize=3, rotation=-45)
  393. filename = "ISI_3D.png" # Il nome del file del grafico
  394. output_path = os.path.join(plotpath, filename) # Percorso completo del file di output
  395. plt.savefig(output_path)
  396. plt.close()
  397. #plt.show()
  398. fig = plt.figure(figsize=(8, 6))
  399. matrice = np.arange(64*64).reshape(64, 64)
  400. coordinate_x, coordinate_y = trova_coordinate(matrice, ROI)
  401. plt.gca().invert_yaxis()
  402. for id, x, y, colore in zip(ROI, coordinate_x, coordinate_y, colors):
  403. plt.scatter(y, x, color=colore, label=f'ID: {id}', s=8)
  404. plt.xlim(0, 63) # Imposta i limiti dell'asse x
  405. plt.ylim(0, 63) # Imposta i limiti dell'asse y
  406. plt.gca().set_aspect('equal', adjustable='box')
  407. plt.gca().invert_yaxis() # Inverti l'orientamento dell'asse Y
  408. plt.title('ISI map')
  409. filename = "ISI_map.png" # Il nome del file del grafico
  410. output_path = os.path.join(plotpath, filename) # Percorso completo del file di output
  411. plt.savefig(output_path)
  412. plt.close()
  413. #plt.show()
  414. def trova_coordinate(matrice, id_casuali):
  415. coordinate_x = []
  416. coordinate_y = []
  417. for id in id_casuali:
  418. indice = np.where(matrice == id)
  419. if len(indice[0]) > 0: # Verifica se l'ID è presente nella matrice
  420. coordinate_x.append(indice[0][0])
  421. coordinate_y.append(indice[1][0])
  422. return coordinate_x, coordinate_y
  423. def calculate_cv(data):
  424. # Calcola la deviazione standard dei dati
  425. std_dev = np.std(data)
  426. # Calcola la media dei dati
  427. mean = np.mean(data)
  428. # Calcola il coefficiente di variazione
  429. cv = std_dev / mean
  430. return cv
  431. def calculate_cv2(data):
  432. cv2_values = []
  433. for i in range(len(data) - 1):
  434. ISIn = data[i]
  435. ISIn_plus_1 = data[i + 1]
  436. numerator = 2 * abs(ISIn_plus_1 - ISIn)
  437. denominator = ISIn + ISIn_plus_1
  438. cv2_values.append(numerator / denominator)
  439. return np.average(cv2_values)
  440. def calculate_isi_histogram(spikes, bin_size, bin_max, channel_id, plotpath):
  441. global total_time
  442. spikes = spikes/17.8
  443. isi = np.diff(spikes)
  444. isi = [x for x in isi if x <= bin_max]
  445. sem=np.std(isi)
  446. cv=calculate_cv(isi)
  447. cv2=calculate_cv2(isi)
  448. # Crea un istogramma degli intervalli tra gli spike
  449. isi_bins = np.arange(0, np.max(isi), bin_size)
  450. isi_hist, _ = np.histogram(isi, bins=isi_bins)
  451. # Calcola la densità di probabilità dell'istogramma
  452. isi_pdf = isi_hist / (len(spikes)) #* bin_size)
  453. ind_mode=np.where(isi_pdf == max(isi_pdf))[0][0]
  454. isi_mode = isi_bins[ind_mode]
  455. modelimit=isi_mode+sem
  456. moda=1/((isi_bins[ind_mode])/1000)
  457. error=abs((1000/modelimit)-moda)
  458. moda=round(moda, 2)
  459. #calcola la frequenza di firing in modo brutale
  460. av_freq=len(spikes)/total_time
  461. av_freq=round(av_freq, 2)
  462. text_freq=str(moda)
  463. text_av= str (av_freq)
  464. text_cv=str(round(cv,2))
  465. text_cv2=str(round(cv2,2))
  466. text_sem=str(round(error, 2))
  467. # Crea un array di valori x per il grafico
  468. x = isi_bins[:-1] + bin_size / 2
  469. # Plotta l'istogramma
  470. plt.figure(figsize=(8, 6))
  471. plt.bar(x, isi_pdf, width=bin_size, color='black')
  472. plt.xlabel('ISI (ms)')
  473. plt.ylabel('Probability Density')
  474. plt.title('ISI ch. %s | Av. Freq. = %s ± %s Hz | Mode = %s Hz | CV = %s | CV2 = %s' % (channel_id,text_av, text_sem, text_freq, text_cv, text_cv2))
  475. #plt.text(120, 0.05, f'Freq: {av_freq:.3f} Hz', color='black')
  476. plt.xlim(0, bin_max)
  477. filename = "ISI_channel_%s.png" % (channel_id) # Il nome del file del grafico
  478. output_path = os.path.join(plotpath, filename) # Percorso completo del file di output
  479. plt.savefig(output_path)
  480. plt.locator_params(axis='x', nbins=15)
  481. #plt.show()
  482. plt.close()
  483. feature=[av_freq, moda, error, cv, cv2]
  484. return np.column_stack((x, isi_pdf)), feature
  485. def pacemakers():
  486. assign_path_pace()
  487. global fpathDATA
  488. global fnBRW
  489. global fnBXR
  490. global saving_path
  491. global bin_size
  492. global pre_stimulus
  493. global post_stimulus
  494. global num_permutations
  495. global dfPSTH
  496. global c
  497. global P_Values
  498. global ROI
  499. global localize_array
  500. bin_edges = np.arange(-pre_stimulus, post_stimulus + bin_size/2, bin_size)
  501. median_index=int(len(bin_edges)/2)
  502. plotfolder="PSTH plots"
  503. plotpath=os.path.join(saving_path, plotfolder)
  504. os.mkdir(plotpath)
  505. f = h5py.File(fnBXR,mode='r')
  506. SpikeTimes=np.array(f['3BResults']['3BChEvents']['SpikeTimes'])
  507. SpikeChIDs=np.array(f['3BResults']['3BChEvents']['SpikeChIDs'])
  508. SpikeUnits=np.array(f['3BResults']['3BChEvents']['SpikeUnits'])
  509. ChEvents=[SpikeChIDs, SpikeTimes, SpikeUnits]
  510. df=pd.DataFrame(data=ChEvents)
  511. num_frequenze=int((pre_stimulus+post_stimulus)/bin_size)
  512. SR=f['3BRecInfo']['3BRecVars']['SamplingRate'][0]
  513. index=[]
  514. i=0
  515. ST=np.delete(SpikeTimes, index)
  516. SCID=np.delete(SpikeChIDs, index)
  517. timeArt()
  518. abs_tart_frames=((tart_frames-100)/4096)/int(SR/1000)
  519. ROI.sort()
  520. colonne=np.array(ROI)
  521. dfPSTH=pd.DataFrame(columns=colonne)
  522. P_Values=np.zeros(len(ROI))
  523. c=0
  524. # Inizializza la matrice 3D vuota
  525. psth3d = np.zeros((num_frequenze, int(len(ROI))))
  526. # Loop sui canali nella ROI
  527. for _ in range(len(ROI)):
  528. valore_cercato = ROI[c]
  529. # Seleziona la prima riga
  530. prima_riga = df.iloc[0]
  531. # Filtra colonne mantenendo solo quella in cui compare il valore cercato
  532. colonna_mantenuta = prima_riga[prima_riga == valore_cercato]
  533. # Seleziona solo le colonne desiderate dal DataFrame originale
  534. df_filtrato = df[colonna_mantenuta.index]
  535. ultima_riga = df_filtrato.iloc[-1]
  536. # Filtra colonne mantenendo solo quella in cui compare il valore cercato
  537. colonna_mantenuta = ultima_riga[ultima_riga > 0]
  538. # Seleziona solo le colonne desiderate dal DataFrame originale
  539. df_filtrato = df_filtrato[colonna_mantenuta.index]
  540. indexID = df_filtrato.iloc[1]
  541. indexID = indexID / int(SR/1000)
  542. indexID=delete_stim_spike(indexID, abs_tart_frames, bin_size)
  543. psth = PSTH4(indexID, abs_tart_frames, ROI[c], bin_size, pre_stimulus, post_stimulus, num_permutations, plotpath, median_index)
  544. psth3d[:, c] = psth
  545. c=c+1
  546. i=0
  547. i=0
  548. fig = plt.figure()
  549. ax = fig.add_subplot(111, projection='3d')
  550. colors = np.tile(['r', 'g', 'b', 'c', 'm', 'y', 'k', 'w', 'orange', 'purple'], 10)[:100]
  551. set_lim=len(ROI)
  552. x = bin_edges[:-1]
  553. ys = np.arange(len(ROI))
  554. z=np.zeros(len(bin_edges[:-1]))
  555. dx=bin_size
  556. ax.set_ylim([0, set_lim])
  557. # Plottaggio delle barre
  558. for i in range(len(ys)):
  559. y=ys[i]
  560. dz = psth3d[:, i]
  561. ax.bar3d(x, y, z, dx, 0.1, dz, color=colors[i])
  562. i=i+1
  563. plt.xlabel('Bins')
  564. plt.ylabel('Channel ID')
  565. plt.yticks(np.arange(len(ROI)), ROI, fontsize=7, rotation=-45)
  566. filename = "PSTH_3D.png" # Il nome del file del grafico
  567. output_path = os.path.join(plotpath, filename) # Percorso completo del file di output
  568. plt.savefig(output_path)
  569. plt.show()
  570. repeatimes=2
  571. psth3dNx=np.repeat(psth3d, repeatimes, axis=1)
  572. i=0
  573. ticks = []
  574. tick_positions = []
  575. for i in range(len(ROI)):
  576. ticks.append(ROI[i])
  577. tick_positions.append((i*(repeatimes)+int(repeatimes/2)))
  578. plt.figure(figsize=(8, 6))
  579. plt.imshow(np.transpose(psth3dNx, (1, 0)), cmap='viridis', origin='upper', vmin=0, vmax=100)
  580. plt.xlabel('Time (ms)')
  581. labels=np.arange(len(psth3dNx[:,0])+1)
  582. labels=labels[:len(psth3dNx[:,0])+1:5]
  583. labels_bin = bin_edges[::5]
  584. plt.xticks(labels, labels_bin, fontsize=7)
  585. plt.ylabel('Channnel ID')
  586. plt.yticks(tick_positions, ticks, fontsize=7)
  587. plt.colorbar()
  588. filename = "PSTH_Heatmap.png" # Il nome del file del grafico
  589. output_path = os.path.join(plotpath, filename) # Percorso completo del file di output
  590. plt.savefig(output_path)
  591. plt.show()
  592. #dfPSTH.loc[len(dfPSTH)] = P_Values
  593. dfPSTH.to_csv('%s/PSTH_P-values.csv'%(saving_path), index = True, decimal=',', sep=';')
  594. dftag = pd.DataFrame(columns=ROI)
  595. dftag.loc[0] = localize_array[ROI]
  596. dftag.to_csv('%s/ROI tagged.csv'%(saving_path), index = True, decimal=',', sep=';')
  597. def delete_stim_spike(spike_times, stim_times, bin_size):
  598. global stimtimes
  599. global freqvalue
  600. n_pulses=stimtimes
  601. stim_distance_ms=1000/freqvalue
  602. if n_pulses == 1:
  603. indices_to_remove = []
  604. for stim_time in stim_times:
  605. start_time = stim_time - 1
  606. end_time = stim_time + 1.3
  607. indices = np.where((spike_times >= start_time) & (spike_times <= end_time))[0]
  608. indices_to_remove.extend(indices)
  609. else:
  610. indices_to_remove = []
  611. for stim_time in stim_times:
  612. i=0
  613. for _ in range(n_pulses):
  614. start_time = stim_time + i*stim_distance_ms - 1
  615. end_time = stim_time + i * stim_distance_ms + 1.3
  616. indices = np.where((spike_times >= start_time) & (spike_times <= end_time))[0]
  617. indices_to_remove.extend(indices)
  618. i=i+1
  619. # rimuovi gli elementi di spike_times indicizzati dagli indici trovati
  620. spike_times = spike_times.drop(spike_times.index[indices_to_remove])
  621. return spike_times
  622. def PSTH4(ST, abs_tart_frames, channel_id, bin_size, pre_stimulus, post_stimulus, num_permutations, plotpath, median_index):
  623. global bin_edges
  624. global dfPSTH
  625. global c
  626. global P_Values
  627. global bin_edges
  628. global localize_array
  629. # Crea un array di bin
  630. bin_edges = np.arange(-pre_stimulus, post_stimulus + bin_size/2, bin_size)
  631. # Inizia un loop sugli stimoli
  632. num_stimuli = len(abs_tart_frames)
  633. psth = np.zeros_like(bin_edges[:-1])
  634. for i in range(num_stimuli):
  635. # Seleziona il periodo di tempo da analizzare
  636. start_time = abs_tart_frames[i] - pre_stimulus
  637. end_time = abs_tart_frames[i] + post_stimulus
  638. # Conta quanti spike ci sono in ogni bin
  639. data_to_analyze = ST[(ST >= start_time) & (ST <= end_time)]
  640. counts, _ = np.histogram(data_to_analyze - abs_tart_frames[i], bins=bin_edges)
  641. psth += counts
  642. # Normalizza il PSTH dividendo per il numero di stimoli
  643. #psth = psth / num_stimuli
  644. psth = psth / num_stimuli / bin_size * 1000
  645. # Calcola la media della frequenza di scarica pre e post stimolo
  646. bin_removed_index = int(len(bin_edges) / 2)
  647. pre_stimulus_fr = np.mean(psth[:bin_removed_index])
  648. pre_stimulus_std = np.std(psth[:bin_removed_index])
  649. post_stimulus_fr = np.mean(psth[bin_removed_index+1:])
  650. post_stimulus_z_scores = (psth[bin_removed_index+1:] - pre_stimulus_fr) / pre_stimulus_std
  651. sig_bars = np.where(post_stimulus_z_scores > 2)[0]
  652. # Concatena i dati per il test delle permutazioni
  653. data = np.concatenate([psth[:bin_removed_index], psth[bin_removed_index+1:]])
  654. psth[median_index]=psth[median_index]*(bin_size/(bin_size-1.3))
  655. # Esegui il test delle permutazioni
  656. perm_fr_diffs = np.zeros(num_permutations)
  657. for i in range(num_permutations):
  658. np.random.shuffle(data)
  659. perm_pre_fr = np.mean(data[:bin_removed_index])
  660. perm_post_fr = np.mean(data[bin_removed_index:])
  661. perm_fr_diffs[i] = perm_post_fr - perm_pre_fr
  662. # Calcola il p-value
  663. observed_fr_diff = post_stimulus_fr - pre_stimulus_fr
  664. p_value = (np.sum(perm_fr_diffs >= observed_fr_diff) + 1) / (num_permutations + 1)
  665. print(f'p-value: {p_value}')
  666. # Imposta il colore di base di tutti i bin a blu
  667. bar_color = 'black'
  668. colors = [bar_color] * len(bin_edges[:-1])
  669. # Imposta il colore del bin dello stimolo su giallo
  670. stim_color = 'black'
  671. stim_bin = int(pre_stimulus / bin_size) # indice del bin corrispondente allo stimolo
  672. colors[stim_bin] = stim_color
  673. #psth[stim_bin]=0 #riduzione valore per migliorare l'impatto visivo
  674. plt.figure(figsize=(8, 6))
  675. # Visualizza il PSTH
  676. plt.bar(bin_edges[:-1], psth, width=bin_size, color=colors, align='edge')
  677. # Calcola la media e la deviazione standard del pre-stimolo
  678. pre_stimulus_data = data[:bin_removed_index]
  679. pre_stimulus_mean = np.mean(pre_stimulus_data)
  680. pre_stimulus_std = np.std(pre_stimulus_data)
  681. # Aggiungi l'asterisco sopra le barre significative del post-stimolo
  682. sig_bars = []
  683. for i, post_stimulus_fr in enumerate(psth[bin_removed_index:]):
  684. z_score = (post_stimulus_fr - pre_stimulus_mean) / pre_stimulus_std
  685. if z_score > 1.645 or z_score < -1.645: # 95% di confidenza a 2 code
  686. sig_bars.append(i)
  687. plt.text(bin_edges[bin_removed_index+1+i]-bin_size/2, post_stimulus_fr, '*', color='red', ha='center', va='bottom')
  688. # Imposta l'etichetta delle barre significative
  689. plt.xlabel('Time (ms)')
  690. plt.ylabel('Firing rate (Hz)')
  691. plt.title('Peri-Stimulus Time Histogram channel %s [ %s ]' % (channel_id, localize_array[channel_id]))
  692. #plt.text(10, 120, f'permutational test p-value: {p_value:.3f}', color='black')
  693. # Imposta i limiti dell'asse x e y in modo che lo stimolo sia al centro del grafico e l'asse y sia congruo
  694. plt.xlim([-pre_stimulus, post_stimulus])
  695. plt.ylim(bottom=0, top=150)
  696. plt.locator_params(axis='x', nbins=20)
  697. plt.axvline(x=0, color='yellow', linestyle='--')
  698. plt.tight_layout()
  699. filename = "PSTH_channel_%s.png" % (ROI[c]) # Il nome del file del grafico
  700. output_path = os.path.join(plotpath, filename) # Percorso completo del file di output
  701. plt.savefig(output_path)
  702. plt.show()
  703. dfPSTH[ROI[c]] = psth
  704. P_Values[c] = p_value
  705. return psth
  706. def timeArt():
  707. global stimtimes
  708. global freqvalue
  709. #directory of .brw file
  710. global fnBRW
  711. global precision
  712. f = h5py.File(fnBRW,mode='r')
  713. MinVolt=float(f['3BRecInfo']['3BRecVars']['MinVolt'][0])
  714. MaxVolt=float(f['3BRecInfo']['3BRecVars']['MaxVolt'][0])
  715. BitDepth=f['3BRecInfo']['3BRecVars']['BitDepth'][0]
  716. SignalInversion=float(f['3BRecInfo']['3BRecVars']['SignalInversion'][0])
  717. SR=f['3BRecInfo']['3BRecVars']['SamplingRate'][0]
  718. rawdata = (f['3BData']['Raw'])
  719. chid=100
  720. stim_duration_ms=1
  721. precision=int(stim_duration_ms)
  722. precision_frames=int(((200)*SR)/1000)
  723. frame=np.zeros(100000000)
  724. n_artifacts=10000
  725. tart=np.zeros(n_artifacts)
  726. global tart_frames
  727. k=0
  728. j=1
  729. t=0
  730. for i in range(chid, len(rawdata), 4096):
  731. ADCCountsToMV = SignalInversion * ((MaxVolt-MinVolt)/2**BitDepth)
  732. offset=SignalInversion*MinVolt
  733. AnalogValue = offset + rawdata[i] *ADCCountsToMV
  734. if AnalogValue > 1500:
  735. frame[j]=(i-chid)/4096
  736. if (frame[j]-frame [j-1])>precision_frames:
  737. sec = ((i-chid)/4096)/SR
  738. msec = sec
  739. print(k,') ', 'sec=', round(msec, 2) ,'peak=', AnalogValue)
  740. tart[t]=msec
  741. tart_frames[t]=i
  742. k=k+1
  743. j=j+1
  744. t=t+1
  745. else:
  746. j=j+1
  747. resize_factor=t
  748. tart_frames=np.resize(tart_frames, resize_factor)
  749. def lowpass_filter(trace, SR):
  750. global lowpassvalue
  751. freq_sampling = SR
  752. # Applicazione del filtro lowpass a 2000 Hz
  753. cutoff_hz = lowpassvalue # frequenza di taglio in Hz
  754. order = 4 # ordine del filtro
  755. nyquist_hz = 0.5 * freq_sampling # frequenza di Nyquist
  756. cutoff_norm = cutoff_hz / nyquist_hz # frequenza di taglio normalizzata
  757. b, a = butter(order, cutoff_norm, btype='lowpass', analog=False)
  758. filtered_trace = filtfilt(b, a, trace)
  759. return filtered_trace
  760. def LFP_analysis():
  761. directory1='N2A'
  762. directory2='N2B'
  763. plotfolder='LFPs Plots'
  764. #directories of .brw file
  765. assign_path_pace()
  766. global fpathDATA
  767. global fnBRW
  768. global saving_path
  769. global stimtimes
  770. global freqvalue
  771. global localize_array
  772. precisionvalue_ms=5
  773. global localize_array
  774. path1 = os.path.join(saving_path, directory1)
  775. path2 = os.path.join(saving_path, directory2)
  776. path3 = os.path.join(saving_path, plotfolder)
  777. os.mkdir(path1)
  778. os.mkdir(path2)
  779. os.mkdir(path3)
  780. #data extraction from .brw file
  781. f = h5py.File(fnBRW,mode='r')
  782. MinVolt=float(f['3BRecInfo']['3BRecVars']['MinVolt'][0])
  783. MaxVolt=float(f['3BRecInfo']['3BRecVars']['MaxVolt'][0])
  784. BitDepth=f['3BRecInfo']['3BRecVars']['BitDepth'][0]
  785. SignalInversion=float(f['3BRecInfo']['3BRecVars']['SignalInversion'][0])
  786. SR=f['3BRecInfo']['3BRecVars']['SamplingRate'][0]
  787. rawdata = (f['3BData']['Raw'])
  788. #params and initializatio (k,j,h, are counters), chid is channel id for detection and the precision is the
  789. #frame interval that the script use to detect the same time artifact in differents frames
  790. frame=np.zeros(100000)
  791. n_artifacts=10000
  792. tart=np.zeros(n_artifacts)
  793. tart_frames=np.zeros(n_artifacts)
  794. blind_time_ms
  795. lfpduration_ms
  796. N2A_N2B_resolution_ms
  797. # if stimtimes == 0 :
  798. # stim_duration_ms=0
  799. # else:
  800. # stimduration_tot=((stimtimes/freqvalue)*1000)
  801. # stim_duration_ms=stimduration_tot - stimduration_tot/stimtimes
  802. k=0
  803. j=1
  804. t=0
  805. c=0
  806. a=0
  807. precision_frames=int((precisionvalue_ms*SR)/1000)
  808. blind_frames=int((blind_time_ms*SR)/1000)
  809. lfpduration_frames=int((lfpduration_ms*SR)/1000)
  810. N2A_N2B_resolution=int((N2A_N2B_resolution_ms*SR)/1000)
  811. ROI
  812. ROI.sort()
  813. limit=len(ROI)
  814. chid=ROI[0]
  815. #the script extract 1 values every 4096 (due to the matrix organization)
  816. #to extract one single trace corresponding for a single channel
  817. #then convert the digital data in analog data (mV) using the 3Brain formula
  818. #for conversion and when the script find a maxima print the value and the time in ms.
  819. #to avoid to detect the same time artifact in two adjecent frames the script compare each
  820. #values with the previous one, and if the difference in frames is under the precision value does not consider it
  821. #for stimulation different by the 0.1 Hz (6 Hz, 20 Hz, ...), change the precision value (calculate how many frames
  822. #correspond to ms between two stimuli) to extract the last stimuli
  823. #stim artifact detection
  824. for i in range(chid, len(rawdata), 4096):
  825. ADCCountsToMV = SignalInversion * ((MaxVolt-MinVolt)/2**BitDepth)
  826. offset=SignalInversion*MinVolt
  827. AnalogValue = offset + rawdata[i] *ADCCountsToMV
  828. if AnalogValue > 1500:
  829. frame[j]=(i-chid)/4096
  830. if (frame[j]-frame [j-1])>precision_frames:
  831. sec = ((i-chid)/4096)/SR
  832. msec = sec
  833. print(k,') ', 'sec=', round(msec, 2) ,'peak=', AnalogValue)
  834. tart[t]=msec
  835. tart_frames[t]=i
  836. k=k+1
  837. j=j+1
  838. t=t+1
  839. else:
  840. j=j+1
  841. resize_factor=t
  842. lfptraces=np.zeros(resize_factor)
  843. lfp_N2A=np.zeros(resize_factor)
  844. lfp_N2B=np.zeros(resize_factor)
  845. tart=np.resize(tart, resize_factor)
  846. tart_frames=np.resize(tart_frames, resize_factor)
  847. posizioniN2A=np.zeros(resize_factor)
  848. posizioniN2B=np.zeros(resize_factor)
  849. lfp_array=np.zeros(lfpduration_frames)
  850. lfp_matrix=np.zeros((len(lfptraces),lfpduration_frames))
  851. #peaks_array=[]
  852. k=0
  853. #peaks
  854. #N2peaks=np.zeros((resize_factor,2))
  855. k=0
  856. j=1
  857. t=0
  858. g=0
  859. i=0
  860. for _ in ROI:
  861. chid=ROI[c]
  862. for i in tart_frames:
  863. t=0
  864. g=0
  865. for _ in range(lfpduration_frames):
  866. ADCCountsToMV = SignalInversion * ((MaxVolt-MinVolt)/2**BitDepth)
  867. offset=SignalInversion*MinVolt
  868. index=int(tart_frames[k]+(blind_frames+g)*4096)
  869. AnalogValue = offset + rawdata[index] *ADCCountsToMV
  870. lfp_array[t]= AnalogValue
  871. t=t+1
  872. g=g+1
  873. #lfp_matrix[k,:]=lowpass_filter(lfp_array, SR)
  874. lfp_matrix[k,:]=lfp_array
  875. k=k+1
  876. i=0
  877. #std_noise= [np.std(arr) for arr in lfp_matrix]
  878. for _ in range(len(lfp_N2A)):
  879. if len(lfp_matrix[i] > 0):
  880. lfp_N2A[i]=min(lfp_matrix[i,0:N2A_N2B_resolution])
  881. posizioniN2A[i] = np.where(lfp_matrix[i]==min(lfp_matrix[i,0:N2A_N2B_resolution]))[0][0]
  882. i=i+1
  883. else:
  884. lfp_N2A[i] = None
  885. posizioniN2A[i]=None
  886. i=0
  887. for i in range(len(lfp_N2A)):
  888. if lfp_N2A[i] > (np.mean(lfp_matrix[i,-100:])-3*np.std(lfp_matrix[i,-100:])):
  889. lfp_N2A[i] = np.nan
  890. print(c , ')', ROI[c])
  891. lfp_N2A=np.resize(lfp_N2A, resize_factor)
  892. print('lfp N2A values:', lfp_N2A)
  893. mean=np.nanmean(lfp_N2A)
  894. sem=np.nanstd(lfp_N2A) / np.sqrt(np.sum(~np.isnan(lfp_N2A)))
  895. print('lfp N2A avarege:', mean, '+- ', sem, 'pA')
  896. #N2peaks=np.zeros((resize_factor,2))
  897. i=0
  898. j=0
  899. for _ in range(len(lfp_N2B)):
  900. start= int (posizioniN2A[i])+int(N2A_N2B_resolution)
  901. if len(lfp_matrix[i, start:start+N2A_N2B_resolution]):
  902. lfp_N2B[i]=min(lfp_matrix[i, start:start+N2A_N2B_resolution])
  903. position = (np.where(lfp_matrix[i, start:start+N2A_N2B_resolution]== lfp_N2B[i])[0])+start
  904. posizioniN2B[i]=position[0]
  905. i=i+1
  906. else:
  907. lfp_N2B[i]=None
  908. i=i+1
  909. for i in range(len(lfp_N2B)):
  910. if lfp_N2B[i] > (np.mean(lfp_matrix[i,-100:])-2*np.std(lfp_matrix[i,-100:])):
  911. lfp_N2B[i] = np.nan
  912. lfp_N2B=np.resize(lfp_N2B, resize_factor)
  913. print('lfp N2B values:', lfp_N2B)
  914. mean=np.nanmean(lfp_N2B)
  915. sem=np.nanstd(lfp_N2B) / np.sqrt(np.sum(~np.isnan(lfp_N2B)))
  916. print('lfp N2B avarege:', mean, '+- ', sem, 'pA')
  917. for i in range(len(posizioniN2A)):
  918. mean_value = np.nanmean(posizioniN2A)
  919. if not (mean_value - 18 <= posizioniN2A[i] <= mean_value + 18):
  920. posizioniN2A[i] = np.nan
  921. posizioniN2B[i] = np.nan
  922. lfp_N2A[i] = np.nan
  923. lfp_N2B[i] = np.nan
  924. consecutive_threshold = 5
  925. value_threshold = 1000
  926. i=0
  927. # Iterare lungo l'asse 0 di lfp_matrix
  928. for i in range(lfp_matrix.shape[0]):
  929. consecutive_count = 0
  930. # Iterare lungo l'asse 1 di lfp_matrix
  931. for j in range(lfp_matrix.shape[1]):
  932. if lfp_matrix[i, j] > value_threshold:
  933. consecutive_count += 1
  934. if consecutive_count >= consecutive_threshold:
  935. # Impostare a NaN i valori corrispondenti in lfp_N2A e lfp_N2B
  936. lfp_N2A[i] = np.nan
  937. lfp_N2B[i] = np.nan
  938. posizioniN2A[i]= np.nan
  939. posizioniN2B[i]= np.nan
  940. else:
  941. consecutive_count = 0
  942. i=0
  943. for i in range(lfp_matrix.shape[0]):
  944. if np.isnan(lfp_N2A[i]):
  945. lfp_matrix[i, :] = np.nan
  946. i=0
  947. #plotting LFP
  948. plt.figure(figsize=(8, 6))
  949. for _ in range(len(lfp_matrix[:, 0:resize_factor])):
  950. plt.plot(lfp_matrix[i, :], "black", alpha=0.1)
  951. if not np.isnan(posizioniN2A[i]):
  952. plt.plot(int(posizioniN2A[i]), lfp_matrix[i, int(posizioniN2A[i])], "o", color= "#686AAD", markersize=2)
  953. if not np.isnan(posizioniN2B[i]):
  954. plt.plot(int(posizioniN2B[i]), lfp_matrix[i, int(posizioniN2B[i])], "o", color= "orange", markersize=2)
  955. i = i + 1
  956. lfp_matrixav = set_nan_in_lfp_matrix(lfp_matrix, lfp_N2A)
  957. lfp_mean=np.zeros(len(lfp_matrixav[1,:]))
  958. i=0
  959. #avarege lfp and avarage N2A and N2B finded, the last check
  960. for _ in range(len(lfp_matrixav[1,:])):
  961. lfp_mean[i]=np.nanmean(lfp_matrixav[:,i])
  962. i=i+1
  963. N2Amean_frame = np.nanmean(posizioniN2A)
  964. if np.isnan(N2Amean_frame):
  965. N2Amean_frame = 0
  966. else:
  967. N2Amean_frame = int(N2Amean_frame)
  968. N2Bmean_frame = np.nanmean(posizioniN2B)
  969. if np.isnan(N2Bmean_frame):
  970. N2Bmean_frame = 0
  971. else:
  972. N2Bmean_frame = int(N2Bmean_frame)
  973. plt.plot(lfp_mean, "black", alpha = 0.8)
  974. if N2Amean_frame != 0:
  975. plt.plot(N2Amean_frame, lfp_mean[N2Amean_frame], "o", color="#686AAD", markersize = 8)
  976. if N2Bmean_frame != 0:
  977. plt.plot(N2Bmean_frame, lfp_mean[N2Bmean_frame], "o", color = "orange", markersize = 8)
  978. current_lower, current_upper = plt.gca().get_ylim()
  979. plt.ylim(current_lower, 250)
  980. plt.title('LFPs channel %s [%s]' % (ROI[c], localize_array[ROI[c]]))
  981. plt.xlabel("Time (ms)")
  982. plt.ylabel("Amplitude (μV)")
  983. #x = range(len(lfp_matrix[0, :]))
  984. #indices = [i // 18 for i in range(len(x))]
  985. #[labels = [str(index) if i % 18 == 0 else '' for i, index in enumerate(indices)]
  986. x = np.arange(0, len(lfp_matrix[0, :]), 18)
  987. labels = np.arange(0,len(x))
  988. labels = [str(val) for val in labels]
  989. plt.xticks(x, labels)
  990. filename = "LFPs_channel_%s.png" % (ROI[c]) # Il nome del file del grafico
  991. output_path = os.path.join(path3, filename) # Percorso completo del file di output
  992. plt.savefig(output_path)
  993. #plt.show()
  994. #savings
  995. timeArt=pd.DataFrame(data=tart)
  996. timeArt.to_csv('%s/timeArtifact_master.csv'%(saving_path), index = False, header=False, decimal=',')
  997. N2A=pd.DataFrame(data=lfp_N2A)
  998. N2ind=str(chid)
  999. N2A.to_csv('%s/%s/N2A_amplitudes_%s.csv'%(saving_path, directory1, N2ind), index = False, header=False, decimal=',')
  1000. N2B=pd.DataFrame(data=lfp_N2B)
  1001. N2B.to_csv('%s/%s/N2B_amplitudes_%s.csv'%(saving_path,directory2, N2ind), index = False, header=False, decimal=',')
  1002. k=0
  1003. j=1
  1004. t=0
  1005. g=0
  1006. i=0
  1007. c=c+1
  1008. if a == (limit-1):
  1009. break
  1010. else:
  1011. tart_frames=tart_frames+(ROI[a+1]-ROI[a])
  1012. a=a+1
  1013. os.chdir(saving_path+'/'+directory1)
  1014. filenameG=[i for i in glob.glob('*.{}'.format("csv"))]
  1015. filenameG.sort(key=len)
  1016. dfTOTN2A = pd.concat([pd.read_csv(f, header=None) for f in filenameG], axis = 'columns')
  1017. dfTOTN2A.to_csv('%s/%s/N2A_master.csv'%(saving_path, directory1), index = False, header=ROI, decimal=',', sep =';')
  1018. os.chdir(saving_path+'/'+directory2)
  1019. filenameG2=[i for i in glob.glob('*.{}'.format("csv"))]
  1020. filenameG2.sort(key=len)
  1021. dfTOTN2B = pd.concat([pd.read_csv(f, header=None) for f in filenameG2], axis='columns')
  1022. dfTOTN2B.to_csv('%s/%s/N2B_master.csv'%(saving_path, directory2), index = False, header=ROI, decimal=',', sep=';')
  1023. prefix="N2A_amplitudes"
  1024. for filename in os.listdir(saving_path+"/N2A"):
  1025. if filename.startswith(prefix): # se il file inizia con il prefisso desiderato
  1026. file_path = os.path.join(saving_path +"/N2A", filename) # costruisci il percorso del file
  1027. os.remove(file_path) # elimina il file
  1028. prefix2="N2B_amplitudes"
  1029. for filename in os.listdir(saving_path+"/N2B"):
  1030. if filename.startswith(prefix2): # se il file inizia con il prefisso desiderato
  1031. file_path = os.path.join(saving_path +"/N2B", filename) # costruisci il percorso del file
  1032. os.remove(file_path) # elimina il file
  1033. os.chdir(saving_path)
  1034. #channel conversion GUI, ready
  1035. frame = customtkinter.CTkFrame(master=root, fg_color="transparent")
  1036. frame.place(x=10, y=0)
  1037. label= customtkinter.CTkLabel(master=frame, width=250, text="ID Conversion", font=("Arial Rounded MT Bold", 25), text_color='white')
  1038. label.pack(pady=2, padx=1)
  1039. entry1= customtkinter.CTkEntry(master=frame, width=250, placeholder_text="first value", font=("Arial Rounded MT Bold", 15),corner_radius=15)
  1040. entry1.pack(pady=2, padx=1, expand=True)
  1041. entry2= customtkinter.CTkEntry(master=frame, width=250, placeholder_text="second Value", font=("Arial Rounded MT Bold", 15), corner_radius=15)
  1042. entry2.pack(pady=2, padx=1)
  1043. button = customtkinter.CTkButton(master=frame, width=250, text="Conversion",font=("Arial Rounded MT Bold", 15), command=conversion,corner_radius=15 )
  1044. button.pack(pady=2, padx=1)
  1045. RESULTS = customtkinter.CTkEntry(master=frame, width=250, placeholder_text="", font=("Arial Rounded MT Bold", 15),corner_radius=15)
  1046. RESULTS.pack(pady=2, padx=1)
  1047. button_add = customtkinter.CTkButton(master=frame, width=250, text="Add to list",font=("Arial Rounded MT Bold", 15), command=add_to_list,corner_radius=15)
  1048. button_add.pack(pady=2, padx=1)
  1049. button_clear = customtkinter.CTkButton(master=frame, width=250, text="Clear ROI",font=("Arial Rounded MT Bold", 15), command=ROI_clear,corner_radius=15)
  1050. button_clear.pack(pady=2, padx=1)
  1051. #LFP analysis GUI, add parameters, function and ROI get
  1052. frame = customtkinter.CTkFrame(master=root, width=600, fg_color="transparent")
  1053. frame.place(x=10, y=280)
  1054. label= customtkinter.CTkLabel(master=frame, width=600, text="LFP Analysis", font=("Arial Rounded MT Bold", 25), text_color='white')
  1055. label.pack(pady=2, padx=1)
  1056. ######sliders##########
  1057. label= customtkinter.CTkLabel(master=frame, width=600, text="LFP extraction time (ms)", font=("Arial Rounded MT Bold", 15), text_color='white')
  1058. label.pack(pady=2, padx=1)
  1059. VALUE= customtkinter.CTkEntry(master=frame, width=600, placeholder_text="", font=("Arial Rounded MT Bold", 15),corner_radius=15)
  1060. VALUE.pack(pady=2, padx=1)
  1061. LFPduration= customtkinter.CTkSlider(master=frame, width=600, from_=0, to=25, number_of_steps=25, command=lfp_duration)
  1062. LFPduration.pack(pady=2, padx=2)
  1063. ####################
  1064. label1= customtkinter.CTkLabel(master=frame, width=600, text="N2A N2B time window (ms)", font=("Arial Rounded MT Bold", 15), text_color='white')
  1065. label1.pack(pady=2, padx=1)
  1066. VALUE1= customtkinter.CTkEntry(master=frame, width=600, placeholder_text="", font=("Arial Rounded MT Bold", 15),corner_radius=15)
  1067. VALUE1.pack(pady=2, padx=1)
  1068. resolution= customtkinter.CTkSlider(master=frame, width=600, from_=0, to=5, number_of_steps=10, command=N2resolution)
  1069. resolution.pack(pady=2, padx=1)
  1070. label2= customtkinter.CTkLabel(master=frame, width=600, text="blind time (ms)", font=("Arial Rounded MT Bold", 15), text_color='white')
  1071. label2.pack(pady=2, padx=1)
  1072. VALUE2= customtkinter.CTkEntry(master=frame, width=600, placeholder_text="", font=("Arial Rounded MT Bold", 15),corner_radius=15)
  1073. VALUE2.pack(pady=2, padx=1)
  1074. blind= customtkinter.CTkSlider(master=frame, width=600, from_=0, to=5, number_of_steps=50, command=blind_time)
  1075. blind.pack(pady=2, padx=1)
  1076. label3= customtkinter.CTkLabel(master=frame, width=600, text="low pass filter frequency, for noise (Hz)", font=("Arial Rounded MT Bold", 15), text_color='white')
  1077. label3.pack(pady=2, padx=1)
  1078. VALUE3= customtkinter.CTkEntry(master=frame, width=600, placeholder_text="", font=("Arial Rounded MT Bold", 15),corner_radius=15)
  1079. VALUE3.pack(pady=2, padx=1)
  1080. lowpass= customtkinter.CTkSlider(master=frame, width=600, from_=1000, to=10000, number_of_steps=9, command=lowpassfilter)
  1081. lowpass.pack(pady=2, padx=1)
  1082. frame = customtkinter.CTkFrame(master=root, width=600, fg_color="transparent")
  1083. frame.place(x=10, y=740)
  1084. button = customtkinter.CTkButton(master=frame, width=600, height=70, text="START LFP ANALYSIS", font=("Arial Rounded MT Bold", 25), command=LFP_analysis,corner_radius=30)
  1085. button.pack(padx=1, pady=1)
  1086. frame = customtkinter.CTkFrame(master=root, fg_color='transparent')
  1087. frame.place(x=335, y=0)
  1088. label= customtkinter.CTkLabel(master=frame, width=600, text="File Path", font=("Arial Rounded MT Bold", 25), text_color='white')
  1089. label.pack(pady=2, padx=1)
  1090. pacefname= customtkinter.CTkEntry(master=frame, width=600, placeholder_text="BRW file name with extension", font=("Arial Rounded MT Bold", 15), corner_radius=15)
  1091. pacefname.pack(pady=2, padx=1)
  1092. fnameBXR= customtkinter.CTkEntry(master=frame, width=600, placeholder_text="BXR file name with extension", font=("Arial Rounded MT Bold", 15),corner_radius=15)
  1093. fnameBXR.pack(pady=2, padx=1)
  1094. savepacepath= customtkinter.CTkEntry(master=frame, width=600, placeholder_text="save path", font=("Arial Rounded MT Bold", 15),corner_radius=15)
  1095. savepacepath.pack(pady=2, padx=1)
  1096. loadROIpath= customtkinter.CTkEntry(master=frame, width=600, placeholder_text="ROI file path and name", font=("Arial Rounded MT Bold", 15),corner_radius=15)
  1097. loadROIpath.pack(pady=2, padx=1)
  1098. button_ROI = customtkinter.CTkButton(master=frame, width=600, text="Load ROI",font=("Arial Rounded MT Bold", 15), command=ROI_from_ROI,corner_radius=15)
  1099. button_ROI.pack(pady=2, padx=1)
  1100. # segmbutton = customtkinter.CTkButton(master=frame, width=600, text="Start image segmentation",font=("Arial Rounded MT Bold", 15), command=apri_image_processing,corner_radius=30)
  1101. # segmbutton.pack(pady=2, padx=1)
  1102. # segmbutton = customtkinter.CTkButton(master=frame, width=600, text="Concatenate multiple files",font=("Arial Rounded MT Bold", 15), command=apri_concatenator,corner_radius=30)
  1103. # segmbutton.pack(pady=2, padx=1)
  1104. frame = customtkinter.CTkFrame(master=root, width=1890)
  1105. frame.place(x=0, y=260)
  1106. progressbar_1 = customtkinter.CTkProgressBar(master=frame, width=1890, progress_color="#00FF00")
  1107. progressbar_1.pack(pady=2, padx=1)
  1108. progressbar_1.configure(mode="indeterminnate")
  1109. progressbar_1.start()
  1110. #pacemakers GUI, insert parameters, function and ROI get
  1111. frame = customtkinter.CTkFrame(master=root, fg_color='transparent')
  1112. frame.place(x=645, y=280)
  1113. label= customtkinter.CTkLabel(master=frame, width=600, text="Autorhythmic Cells Evoked Activity Analysis", font=("Arial Rounded MT Bold", 25), text_color='white')
  1114. label.pack(pady=2, padx=1)
  1115. savepacepath.pack(pady=2, padx=1)
  1116. label5= customtkinter.CTkLabel(master=frame, width=600, text="bin size (ms)", font=("Arial Rounded MT Bold", 15), text_color='white')
  1117. label5.pack(pady=2, padx=1)
  1118. VALUE5= customtkinter.CTkEntry(master=frame, width=600, placeholder_text="", font=("Arial Rounded MT Bold", 15),corner_radius=15)
  1119. VALUE5.pack(pady=2, padx=1)
  1120. binslider= customtkinter.CTkSlider(master=frame, width=600, from_=1, to=50, number_of_steps=49, command=binvalue)
  1121. binslider.pack(pady=2, padx=1)
  1122. label6= customtkinter.CTkLabel(master=frame, width=600, text="pre-stimulus time window (ms)", font=("Arial Rounded MT Bold", 15), text_color='white')
  1123. label6.pack(pady=2, padx=1)
  1124. VALUE6= customtkinter.CTkEntry(master=frame, width=600, placeholder_text="", font=("Arial Rounded MT Bold", 15),corner_radius=15)
  1125. VALUE6.pack(pady=2, padx=1)
  1126. preslider= customtkinter.CTkSlider(master=frame, width=600, from_=50, to=1200, number_of_steps=23, command=prevalue)
  1127. preslider.pack(pady=2, padx=1)
  1128. label7= customtkinter.CTkLabel(master=frame, width=600, text="post-stimulus time window (ms)", font=("Arial Rounded MT Bold", 15), text_color='white')
  1129. label7.pack(pady=2, padx=1)
  1130. VALUE7= customtkinter.CTkEntry(master=frame, width=600, placeholder_text="", font=("Arial Rounded MT Bold", 15),corner_radius=15)
  1131. VALUE7.pack(pady=2, padx=1)
  1132. postslider= customtkinter.CTkSlider(master=frame, width=600, from_=50, to=1200, number_of_steps=23, command=postvalue)
  1133. postslider.pack(pady=2, padx=1)
  1134. label8= customtkinter.CTkLabel(master=frame, width=600, text="num permutation for significance test", font=("Arial Rounded MT Bold", 15), text_color='white')
  1135. label8.pack(pady=2, padx=1)
  1136. VALUE8= customtkinter.CTkEntry(master=frame, width=600, placeholder_text="", font=("Arial Rounded MT Bold", 15),corner_radius=15)
  1137. VALUE8.pack(pady=2, padx=1)
  1138. permslider= customtkinter.CTkSlider(master=frame, width=600, from_=1000, to=10000, number_of_steps=9, command=permvalue)
  1139. permslider.pack(pady=2, padx=1)
  1140. label9= customtkinter.CTkLabel(master=frame, width=600, height=47, text="", font=("Arial Rounded MT Bold", 15), text_color='white')
  1141. label9.pack(pady=2, padx=1)
  1142. frameC = customtkinter.CTkFrame(master=root, width=600, fg_color="transparent")
  1143. frameC.place(x=645, y=680)
  1144. ################################ STRING 1000!!!!! ################################
  1145. checkbox01Hz = customtkinter.CTkCheckBox(master=frameC, text="0.1 Hz", command=set_freqvalue01,
  1146. onvalue="on", offvalue="off", font=("Arial Rounded MT Bold", 15), checkbox_width=10, checkbox_height=10, corner_radius=30)
  1147. checkbox01Hz.grid(row=0, column=0)
  1148. checkbox6Hz = customtkinter.CTkCheckBox(master=frameC, text="6 Hz", command=set_freqvalue6,
  1149. onvalue="on", offvalue="off", font=("Arial Rounded MT Bold", 15), checkbox_width=10, checkbox_height=10, corner_radius=30)
  1150. checkbox6Hz.grid(row=0, column=1)
  1151. checkbox10Hz = customtkinter.CTkCheckBox(master=frameC, text="10 Hz", command=set_freqvalue10,
  1152. onvalue="on", offvalue="off", font=("Arial Rounded MT Bold", 15), checkbox_width=10, checkbox_height=10, corner_radius=30)
  1153. checkbox10Hz.grid(row=0, column=2)
  1154. checkbox20Hz = customtkinter.CTkCheckBox(master=frameC, text="20 Hz", command=set_freqvalue20,
  1155. onvalue="on", offvalue="off", font=("Arial Rounded MT Bold", 15), checkbox_width=10, checkbox_height=10, corner_radius=30)
  1156. checkbox20Hz.grid(row=0, column=3)
  1157. checkbox50Hz = customtkinter.CTkCheckBox(master=frameC, text="50 Hz", command=set_freqvalue50,
  1158. onvalue="on", offvalue="off", font=("Arial Rounded MT Bold", 15), checkbox_width=10, checkbox_height=10, corner_radius=30)
  1159. checkbox50Hz.grid(row=0, column=4)
  1160. checkbox100Hz = customtkinter.CTkCheckBox(master=frameC, text="100 Hz", command=set_freqvalue100,
  1161. onvalue="on", offvalue="off", font=("Arial Rounded MT Bold", 15), checkbox_width=10, checkbox_height=10, corner_radius=30)
  1162. checkbox100Hz.grid(row=0, column=5)
  1163. checkbox1pulse = customtkinter.CTkCheckBox(master=frameC, text="sing. pulse", command=set_stimvalue1,
  1164. onvalue="on", offvalue="off", font=("Arial Rounded MT Bold", 15), checkbox_width=10, checkbox_height=10, corner_radius=30)
  1165. checkbox1pulse.grid(row=1, column=0)
  1166. checkbox5pulse = customtkinter.CTkCheckBox(master=frameC, text="5 pulses", command=set_stimvalue5,
  1167. onvalue="on", offvalue="off", font=("Arial Rounded MT Bold", 15), checkbox_width=10, checkbox_height=10, corner_radius=30)
  1168. checkbox5pulse.grid(row=1, column=1)
  1169. checkbox10pulse = customtkinter.CTkCheckBox(master=frameC, text="10 pulses", command=set_stimvalue10,
  1170. onvalue="on", offvalue="off", font=("Arial Rounded MT Bold", 15), checkbox_width=10, checkbox_height=10, corner_radius=30)
  1171. checkbox10pulse.grid(row=1, column=2)
  1172. frame = customtkinter.CTkFrame(master=root, width=600, fg_color="transparent")
  1173. frame.place(x=645, y=740)
  1174. button = customtkinter.CTkButton(master=frame, width=600, height=70, text="START EVOKED ACTIVITY ANALYSIS",font=("Arial Rounded MT Bold", 25), command=pacemakers,corner_radius=30)
  1175. button.pack(pady=2, padx=1)
  1176. frame = customtkinter.CTkFrame(master=root, fg_color='transparent')
  1177. frame.place(x=10, y=820)
  1178. label= customtkinter.CTkLabel(master=frame, width=1870, text="Selected Channels", font=("Arial Rounded MT Bold", 25), corner_radius=15, text_color='white')
  1179. label.pack(pady=2, padx=1)
  1180. ids= customtkinter.CTkEntry(master=frame, width=1870, height=50, placeholder_text=ROIstr, font=("Arial Rounded MT Bold", 15), corner_radius=15)
  1181. ids.pack(pady=2, padx=1)
  1182. img=PhotoImage(file="NeuroPulseLogo.png")
  1183. frame = customtkinter.CTkFrame(master=root, width=120, height=120, fg_color="transparent")
  1184. frame.place(x=1300, y=40)
  1185. label= customtkinter.CTkLabel(master=frame, width=120, height=120,text="", image=img)
  1186. label.pack(pady=2, padx=1)
  1187. plt.imshow(map_mea, cmap='brg', interpolation='nearest')
  1188. plt.savefig('img.png', bbox_inches='tight', transparent=True, dpi=60)
  1189. img_map=PhotoImage(file="img.png")
  1190. frame = customtkinter.CTkFrame(master=root, width=120, height=120, fg_color="transparent")
  1191. frame.place(x=1040, y=0)
  1192. label= customtkinter.CTkLabel(master=frame, width=120, height=120, text="", image=img_map)
  1193. label.pack(pady=2, padx=1)
  1194. ##spont activity
  1195. frame = customtkinter.CTkFrame(master=root, fg_color='transparent')
  1196. frame.place(x=1280, y=280)
  1197. label= customtkinter.CTkLabel(master=frame, width=600, text="Autorhythmic Cells Spont. Activity Analysis", font=("Arial Rounded MT Bold", 25), text_color='white')
  1198. label.pack(pady=2, padx=1)
  1199. savepacepath.pack(pady=2, padx=1)
  1200. label52= customtkinter.CTkLabel(master=frame, width=600, text="bin size (ms)", font=("Arial Rounded MT Bold", 15), text_color='white')
  1201. label52.pack(pady=2, padx=1)
  1202. VALUE52= customtkinter.CTkEntry(master=frame, width=600, placeholder_text="", font=("Arial Rounded MT Bold", 15),corner_radius=15)
  1203. VALUE52.pack(pady=2, padx=1)
  1204. binslider2= customtkinter.CTkSlider(master=frame, width=600, from_=1, to=50, number_of_steps=49, command=binvalue2)
  1205. binslider2.pack(pady=2, padx=1)
  1206. label53= customtkinter.CTkLabel(master=frame, width=600, text="ISI bin limit (ms)", font=("Arial Rounded MT Bold", 15), text_color='white')
  1207. label53.pack(pady=2, padx=1)
  1208. VALUE53= customtkinter.CTkEntry(master=frame, width=600, placeholder_text="", font=("Arial Rounded MT Bold", 15),corner_radius=15)
  1209. VALUE53.pack(pady=2, padx=1)
  1210. total_time_slider= customtkinter.CTkSlider(master=frame, width=600, from_=100, to=3000, number_of_steps=29, command=bin_max_value)
  1211. total_time_slider.pack(pady=2, padx=1)
  1212. label54= customtkinter.CTkLabel(master=frame, width=600, text="total time (s)", font=("Arial Rounded MT Bold", 15), text_color='white')
  1213. label54.pack(pady=2, padx=1)
  1214. VALUE54= customtkinter.CTkEntry(master=frame, width=600, placeholder_text="", font=("Arial Rounded MT Bold", 15),corner_radius=15)
  1215. VALUE54.pack(pady=2, padx=1)
  1216. total_time_slider2= customtkinter.CTkSlider(master=frame, width=600, from_=0, to=1000, number_of_steps=100, command=total_time)
  1217. total_time_slider2.pack(pady=2, padx=1)
  1218. # label_tw= customtkinter.CTkLabel(master=frame, width=600, text="total time window (ms)", font=("Arial Rounded MT Bold", 15), text_color='white')
  1219. # label_tw.pack(pady=2, padx=1)
  1220. # VALUE_tw= customtkinter.CTkEntry(master=frame, width=600, placeholder_text="", font=("Arial Rounded MT Bold", 15),corner_radius=15)
  1221. # VALUE_tw.pack(pady=2, padx=1)
  1222. # twslider= customtkinter.CTkSlider(master=frame, width=600, from_=500, to=5000, number_of_steps=9, command=twvalue)
  1223. # twslider.pack(pady=2, padx=1)
  1224. frame = customtkinter.CTkFrame(master=root, width=600, fg_color="transparent")
  1225. frame.place(x=1280, y=740)
  1226. button = customtkinter.CTkButton(master=frame, width=600, height=70, text="START SPONT. ACTIVITY ANALYSIS",font=("Arial Rounded MT Bold", 25), command=spont_activity,corner_radius=30)
  1227. button.pack(pady=2, padx=1)
  1228. frame0 = customtkinter.CTkFrame(master=root, fg_color="transparent")
  1229. folderico = PhotoImage(file="folderico.png")
  1230. frame0.place(x=935, y=30)
  1231. buttonBrfile = customtkinter.CTkButton(master=frame0, width=25, text="", command=browse_BRW ,corner_radius=15, image=folderico, fg_color="transparent")
  1232. buttonBrfile.pack(pady=0, padx=0)
  1233. frame0.place(x=935, y=30)
  1234. buttonBrfile = customtkinter.CTkButton(master=frame0, width=25, text="", command=browse_BXR ,corner_radius=15, image=folderico, fg_color="transparent")
  1235. buttonBrfile.pack(pady=0, padx=0)
  1236. frame0 = customtkinter.CTkFrame(master=root, fg_color="transparent")
  1237. frame0.place(x=935, y=128)
  1238. buttonBrfile = customtkinter.CTkButton(master=frame0, width=25, text="", command=browse_ROI ,corner_radius=15, image=folderico, fg_color="transparent")
  1239. buttonBrfile.pack(pady=0, padx=0)
  1240. frame0 = customtkinter.CTkFrame(master=root, fg_color="transparent")
  1241. frame0.place(x=935, y=95)
  1242. buttonBrfile = customtkinter.CTkButton(master=frame0, width=25, text="", command=browse_save ,corner_radius=15, image=folderico, fg_color="transparent")
  1243. buttonBrfile.pack(pady=0, padx=0)
  1244. root.protocol("WM_DELETE_WINDOW", on_closing)
  1245. root.mainloop()

HD-MEA NEUROPulse.py, no license · at the source

Overview

Authors: Eleonora Pali1, Giorgia Pellavio1, Maria Conforti1, Arvin A. Sarkissian2,3, Berna Aliya2,3, Giacomo Sciacca4, Supriya S. Wariyar2,3, Francesco Mainardi4, Mariateresa Tedesco4, Ivan Verduci4, Gendenver Cadiao4, Chiara Cervetto5,6, Jimena Andersen2,3, Fikri Birey2,3, Alessandro Maccione4, Egidio D’Angelo1,7, Lisa Mapelli1
  1. Department of Brain and Behavioral Sciences, University of Pavia, Pavia, Italy
  2. Department of Human Genetics, Emory University School of Medicine, Atlanta, GA, USA
  3. Emory Brain Organoid Hub, Atlanta, GA, USA
  4. Brain AG, Pfäffikon, Switzerland
  5. Department of Pharmacology (DIFAR), University of Genoa, Genoa, Italy
  6. Interuniversity Center for the Promotion of the 3Rs Principles in Teaching and Research (Centro 3R), Italy
  7. Digital Neuroscience Center, IRCCS Mondino Foundation, Pavia, Italy
Institutions: University of Pavia (Italy); Emory University (United States); 3Brain (Switzerland) (Switzerland); University of Genoa (Italy)
Journal: Bio-protocol, volume 16, issue 11, article e5708
Dates: received 13 February 2026; accepted 29 April 2026; published online 5 June 2026
Type: Methods article · Language: English
License: CC BY-NC
Identifiers: DOI 10.21769/bioprotoc.5708 · PMID 42305135 · PMCID PMC13266469 · OpenAlex W7161173022
Open access: diamond, a free copy (OpenAlex)
Status: code verified
Methods: Statistics, Smoothing, state filtering, decompositions, Machine learning, Preprocessing, Spectral & time-frequency, Evoked potentials, fMRI & imaging, Single-unit activity, calcium imaging
Keywords: 3D high-density multielectrode array, Acute slices, Brain spheroids, Neural organoids, Electrophysiological recordings, Electrophysiological data analysis, Acute electrophysiological measurements
Journal subjects: Biology, Clinical Protocols
Topic: Neuroscience and Neural Engineering (Cellular and Molecular Neuroscience, Neuroscience), according to OpenAlex
Citations: not cited yet (Europe PMC); 39 references in the paper

Abstract

Animal and human stem cell–derived three-dimensional models to study physio-pathological brain functioning are becoming a gold standard for in vitro electrophysiology, as they enable the recapitulation of complex network properties by accounting for spatial architectural features that better reflect in vivo conditions than simpler 2D models. Standard planar multielectrode arrays (MEAs), typically providing tens of recording electrodes, are commonly used to record activity from 2D neuronal cultures. However, when adapted for use with 3D models, planar 2D MEAs showed limited effectiveness. The main issues are limited specimen adhesion to the chip, a low number of sensing elements, inability to retrieve signals from within the tissue, and reduced perfusion and vitality of the tissue in contact with sensors. To overcome these limitations, a new generation of microchip-based 3D high-density MEAs (3D HD-MEA) has been developed and validated in recent years. This technological advancement has improved the sensing capabilities and the vitality of 3D models, providing a tool tailored to maximize their potential. Here, we present an optimized protocol for neural network activity recordings in 3D models (including acute slices, brain spheroids, and organoids) from various brain regions using 3D HD-MEAs. First, we summarize the critical steps for 1) obtaining viable acute slices from the mouse cerebellum, cortico-hippocampal circuit, and prefrontal cortex, 2) establishing efficient coupling of the slices with the chip, and 3) performing recordings and analyses. We then describe the main procedures required to obtain human and animal brain spheroids and neural organoids, as well as standardized routines to perform effective recordings and analyses. For each section, we highlight the crucial steps, identify tips for specific applications, and propose troubleshooting procedures. For example, the same type of preparation (e.g., acute slices) requires different adjustments when working with different brain areas. The specific information provided here is intended to assist researchers in their daily efforts to obtain efficient and reproducible functional recordings from 3D models by using the cutting-edge technique of 3D HD-MEA.

Key features

• Comprehensive all-in-one guide covering the complete workflow for acquiring electrophysiological data from brain slices, neural region-specific organoids, and brain spheroids.

• Intuitive, step-by-step protocol for brain slice preparation, enriched with practical tips and expert recommendations to ensure high-quality tissue viability.

• Detailed instructions for optimal use of 3D HD-MEA technology, including proper handling of the sample holder for recordings from brain slices, neural organoids, and spheroids.

• In-depth guidance on BrainWave6 software, providing clear procedures for data acquisition, signal detection, and advanced electrophysiological analysis across all sample types.

Reproduced under the paper's license (CC BY-NC), from the paper cited above.

Repository

Its files are read in the Code ↔ Paper reader above.

Zenodo 13908319

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Languages: Python (1)
Size: 16 files, 1 script
Software Heritage: not checked
Found in: the references
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: h5py (1 file), Matplotlib (1 file), NumPy (1 file), pandas (1 file), SciPy (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers (HTTP 200)
  • 27 September 2026: the link answers (HTTP 200)
1 file
At the source:

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;
  • 1 script, each with its path and the digest of its content;
  • no match between paragraphs and code yet;
  • 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.

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 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 17 authors, 7 keywords, 38 references.

Cite

This paper

Pali, E., Pellavio, G., Conforti, M., Sarkissian, A. A., Aliya, B., Sciacca, G., Wariyar, S. S., Mainardi, F., Tedesco, M., Verduci, I., Cadiao, G., Cervetto, C., Andersen, J., Birey, F., Maccione, A., D’Angelo, E., & Mapelli, L. (2026). Measuring Electrophysiological Activity in Acute Brain Slices, Spheroids, and Organoids Using 3D High-Density Multielectrode Arrays. Bio-protocol, 16(11), e5708. https://doi.org/10.21769/bioprotoc.5708

BibTeX

@article{pali2026measuring,
author = {Pali, Eleonora and Pellavio, Giorgia and Conforti, Maria and Sarkissian, Arvin A. and Aliya, Berna and Sciacca, Giacomo and Wariyar, Supriya S. and Mainardi, Francesco and Tedesco, Mariateresa and Verduci, Ivan and Cadiao, Gendenver and Cervetto, Chiara and Andersen, Jimena and Birey, Fikri and Maccione, Alessandro and D’Angelo, Egidio and Mapelli, Lisa},
title = {{Measuring Electrophysiological Activity in Acute Brain Slices, Spheroids, and Organoids Using 3D High-Density Multielectrode Arrays}},
journal = {Bio-protocol},
year = {2026},
month = jun,
volume = {16},
number = {11},
pages = {e5708},
publisher = {Bio-protocol, LLC},
issn = {2331-8325},
doi = {10.21769/bioprotoc.5708},
url = {https://doi.org/10.21769/bioprotoc.5708},
pmid = {42305135},
pmcid = {PMC13266469}
}

RIS

TY - JOUR
AU - Pali, Eleonora
AU - Pellavio, Giorgia
AU - Conforti, Maria
AU - Sarkissian, Arvin A.
AU - Aliya, Berna
AU - Sciacca, Giacomo
AU - Wariyar, Supriya S.
AU - Mainardi, Francesco
AU - Tedesco, Mariateresa
AU - Verduci, Ivan
AU - Cadiao, Gendenver
AU - Cervetto, Chiara
AU - Andersen, Jimena
AU - Birey, Fikri
AU - Maccione, Alessandro
AU - D’Angelo, Egidio
AU - Mapelli, Lisa
TI - Measuring Electrophysiological Activity in Acute Brain Slices, Spheroids, and Organoids Using 3D High-Density Multielectrode Arrays
T2 - Bio-protocol
J2 - Bio Protoc
PY - 2026
DA - 2026/06/05
VL - 16
IS - 11
SP - e5708
SN - 2331-8325
PB - Bio-protocol, LLC
DO - 10.21769/bioprotoc.5708
UR - https://doi.org/10.21769/bioprotoc.5708
LA - en
ER -

CSL-JSON

{
"id": "10.21769/bioprotoc.5708",
"type": "article-journal",
"title": "Measuring Electrophysiological Activity in Acute Brain Slices, Spheroids, and Organoids Using 3D High-Density Multielectrode Arrays",
"container-title": "Bio-protocol",
"author": [
{
"family": "Pali",
"given": "Eleonora"
},
{
"family": "Pellavio",
"given": "Giorgia"
},
{
"family": "Conforti",
"given": "Maria"
},
{
"family": "Sarkissian",
"given": "Arvin A."
},
{
"family": "Aliya",
"given": "Berna"
},
{
"family": "Sciacca",
"given": "Giacomo"
},
{
"family": "Wariyar",
"given": "Supriya S."
},
{
"family": "Mainardi",
"given": "Francesco"
},
{
"family": "Tedesco",
"given": "Mariateresa"
},
{
"family": "Verduci",
"given": "Ivan"
},
{
"family": "Cadiao",
"given": "Gendenver"
},
{
"family": "Cervetto",
"given": "Chiara"
},
{
"family": "Andersen",
"given": "Jimena"
},
{
"family": "Birey",
"given": "Fikri"
},
{
"family": "Maccione",
"given": "Alessandro"
},
{
"family": "D’Angelo",
"given": "Egidio"
},
{
"family": "Mapelli",
"given": "Lisa"
}
],
"container-title-short": "Bio Protoc",
"volume": "16",
"issue": "11",
"page": "e5708",
"DOI": "10.21769/bioprotoc.5708",
"PMID": "42305135",
"PMCID": "PMC13266469",
"ISSN": "2331-8325",
"publisher": "Bio-protocol, LLC",
"URL": "https://doi.org/10.21769/bioprotoc.5708",
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
5
]
]
}
}

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.1002/adhm.202504889 [code]
Mapping the Cerebral Organoid Landscape: A Systematic Review of Preclinical 3D Models in Neuroscience.
Journal: Advanced healthcare materials
In common: pandas, SciPy, Matplotlib, 1 other tool, 6 references
[2] doi:10.1371/journal.pbio.3003757 [code]
Cell type-agnostic transcriptomic signatures enable uniform comparisons of neural maturation.
Journal: PLoS biology
In common: h5py, pandas, SciPy, 2 other tools, 3 references
[3] doi:10.1038/s41467-026-71458-0 [code]
Early differential impact of MeCP2 mutations on functional networks in Rett syndrome patient-derived human cortical organoids.
Journal: Nature communications
In common: h5py, SciPy, Matplotlib, 1 other tool, 2 references
[4] doi:10.7554/elife.110588 [code]
Opening the black box toward a modular approach to spike sorting.
Journal: eLife
In common: h5py, pandas, SciPy, 2 other tools, extracellular electrophysiology (units, LFP), methods / tools
[5] doi:10.1093/bioinformatics/btag570 [code]
CASCADE: criticality avalanche spike cross-platform analysis detection engine, a multi-manufacturer MEA bash analysis pipeline.
Journal: Bioinformatics (Oxford, England)
In common: h5py, pandas, SciPy, 2 other tools, extracellular electrophysiology (units, LFP), methods / tools
[6] doi:10.1002/advs.202522762 [code]
Enhancing Maturation of Human Neuromuscular Organoids via Electrical Stimulation.
Journal: Advanced science (Weinheim, Baden-Wurttemberg, Germany)
In common: h5py, pandas, SciPy, 2 other tools, 1 reference
[7] doi:10.1038/s41467-026-74320-5 [code]
Spatial architecture of autism pathogenesis reveals mosaic structural disarray during early development.
Journal: Nature communications
In common: h5py, pandas, SciPy, 2 other tools, 1 reference
[8] doi:10.1038/s41531-026-01531-4 [code]
Inconsistent subthalamic local field potential beta activity amid in- and antiphasic neuronal bursts.
Journal: NPJ Parkinson's disease
In common: h5py, pandas, SciPy, 2 other tools, extracellular electrophysiology (units, LFP)
[9] doi:10.1016/j.patter.2026.101590 [code]
Density-based longitudinal neuron tracking in high-density electrophysiological recordings.
Journal: Patterns (New York, N.Y.)
In common: h5py, pandas, SciPy, 2 other tools, extracellular electrophysiology (units, LFP)
[10] doi:10.1038/s41592-026-03076-z [code]
Neuropixels Opto: combining high-resolution electrophysiology and optogenetics.
Journal: Nature methods
In common: h5py, pandas, SciPy, 2 other tools, extracellular electrophysiology (units, LFP)

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.