Long-term editing of brain circuits using an engineered electrical synapse.
The 13 matches · 4 of them tie a paragraph to a whole file, not to given lines: weak matches, whose lines are not tinted
- [1] § Methods › Automated FETCH output processing pipeline ↔ FETCH.py, lines 270–279 · score 0.75 · optimal bandwidth, kernel density, cross validation, fit, FETCH
- [2] § Methods › Quantifying MD single-unit responses to direct IL activation ↔ LinCx_Opto_Stim_mkcg.m, the whole file · a weak match · score 0.69 · Laser Power, light pulses, intervals, optical, width, stimulation
- [3] § Methods › Neural data acquisition and analysis in IL→MD circuit-edited mice ↔ OptoLinCx_Analysis_v2.ipynb, lines 232–298 · score 0.67 · pre stimulus dominant, outliers, voltage, window, peaks, mice
- [4] § Methods › Quantifying MD single-unit responses to direct IL activation ↔ LinCx_Opto_Stim.m, the whole file · a weak match · score 0.66 · Laser Power, light pulses, intervals, optical, stimulation, analog
- [5] § Methods › Automated FETCH output processing pipeline ↔ FETCH.py, lines 446–497 · score 0.64 · FETCH score, Q2, Q4, Q1, Q3, cells
- [6] § Cx34.7(M1)–Cx35(M1) potentiates a long-range circuit ↔ OptoLinCx_Analysis_v2.ipynb, lines 1499–1573 · score 0.62 · medial dorsal thalamus, infralimbic cortex, Cx34.7, MD, Cx35, IL
- [7] § Methods › Automated FETCH output processing pipeline ↔ FETCH.py, lines 500–554 · score 0.62 · mApple, FITC, PE, SSC, fcs, FSC
- [8] § Methods › Neural data acquisition and analysis in IL→MD circuit-edited mice ↔ OptoLinCx_Analysis.ipynb, lines 194–216 · score 0.60 · pre stimulus dominant, window, peaks, filtered, channel
- [9] § Methods › Neural data acquisition and analysis in IL→MD circuit-edited mice ↔ LinCx_Opto_Stim_mkcg.m, the whole file · a weak match · score 0.57 · baseline recording, intensities, pulse, intervals, laser, stimulated
- [10] § Cx34.7(M1)–Cx35(M1) potentiates a long-range circuit ↔ OptoLinCx_Analysis_v2.ipynb, lines 1354–1497 · score 0.54 · MD channel, IL channel, Cx34.7, box, width, GFP
- [11] § Methods › Automated FETCH output processing pipeline ↔ FETCH.py, lines 282–326 · score 0.53 · kernel density, Gaussian, bandwidth, fitted, gate, FETCH
- [12] § Methods › Characterizing gap junction biophysical properties using Xenopus oocytes ↔ OptoLinCx_Analysis_v2.ipynb, lines 1057–1067 · score 0.51 · Cx34.7M1, Cx35M1
- [13] § Methods › Neural data acquisition and analysis in IL→MD circuit-edited mice ↔ LinCx_Opto_Stim.m, the whole file · a weak match · score 0.51 · baseline recording, pulse, intervals, laser, stimulated, analog
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 · 841 lines · 40 KB · no license · 4 matches
- #e.g. $python FETCH.py -f example -p my_cool_project -s FC114_A2_A02_002.fcs FC114_C1_C01_025.fcs
- import argparse
- import matplotlib
- import matplotlib.pyplot as plt
- from matplotlib.pyplot import figure
- import scipy
- import warnings
- import random
- def _centered(arr, newsize):
- # Return the center newsize portion of the array.
- newsize = np.asarray(newsize)
- currsize = np.array(arr.shape)
- startind = (currsize - newsize) // 2
- endind = startind + newsize
- myslice = [slice(startind[k], endind[k]) for k in range(len(endind))]
- return arr[tuple(myslice)]
- scipy.signal.signaltools._centered = _centered
- import flowkit as fk
- import numpy as np
- from numpy.linalg import norm
- import scipy.stats as st
- from os.path import join
- import os
- #these are still libraries
- from sklearn.neighbors import KernelDensity
- from sklearn.model_selection import GridSearchCV
- from numpy.linalg import eig, inv
- from matplotlib.patches import Ellipse
- from matplotlib.path import Path
- from sklearn.model_selection import GridSearchCV
- from matplotlib.offsetbox import AnchoredText
- from matplotlib import path
- import pandas as pd
- import seaborn as sns
- plt.switch_backend('agg')
- plt.ioff()
- #plot formatting
- #def = define function; in this case... allows argument input into terminal..
- def parse_arguments():
- ap = argparse.ArgumentParser(description="Parse arguments")
- ap.add_argument(
- "-f", "--folder",
- default="",
- help="The path to a directory containing your .fcs files",
- type=str )
- ap.add_argument(
- "-p", "--project",
- default="Untitled_project",
- help="The project name",
- type=str )
- ap.add_argument(
- "-s", "--skip_renaming",
- default='',
- help="This script expects fcs files to be formatted as 'FC114_A1_A01_001.fcs'; if just a few files are not formatted like that, pass them as arguments in this function to avoid errors; otherwise edit summarize function too parse your filenames correctly", nargs='*')
- ap.add_argument(
- "-l", "--legacy_analysis",
- default="False",
- help ="An optional argument that sets a more narrow first gate and quantile cutoff in the last gate for replicability of past analysis",
- type=str )
- ap.add_argument(
- "-n", "--negative_control",
- default="None",
- help ="An optional argument: negative control file name w/o fcs; draws the third gate against a known negative control instead of automatic gating",
- type=str )
- ap.add_argument(
- "-pn", "--predefined_negative_control",
- default="None",
- help ="An optional argument: can specify x axis and y axis precise values to draw the third gate in the format e.g.: -pn 100,240",
- type=str )
- ap.add_argument(
- "-g", "--log_gate",
- default="None",
- help ="An optional argument: one uses log transform on the first gate for the unorthodox FETCH template",
- type=str )
- return ap.parse_args()
- #this function writes gatefiles that can be opened in flowjo specifically
- def gate_writer(vertices1, vertices2, boundaries, filename, channame1=None, channame2=None, fluorophore1=None, fluorophore2=None):
- gate_text = ['<?xml version="1.0" encoding="UTF-8"?>',
- '<gating:Gating-ML',
- ' xmlns:gating="http://www.isac-net.org/std/Gating-ML/v2.0/gating"',
- ' xmlns:data-type="http://www.isac-net.org/std/Gating-ML/v2.0/datatypes">']
- polygon1 = [' <gating:PolygonGate gating:id="Polygon1">',
- ' <gating:dimension gating:compensation-ref="uncompensated">',
- ' <data-type:fcs-dimension data-type:name="SSC-A" />',
- ' </gating:dimension>',
- ' <gating:dimension gating:compensation-ref="uncompensated">',
- ' <data-type:fcs-dimension data-type:name="FSC-A" />',
- ' </gating:dimension>']
- for vertex in vertices1:
- vertex = [' <gating:vertex>',
- ' <gating:coordinate data-type:value="' + str(vertex[0]) + '" />',
- ' <gating:coordinate data-type:value="' + str(vertex[1]) + '" />',
- ' </gating:vertex>']
- polygon1 = polygon1 + vertex
- polygon1 = polygon1 + [' </gating:PolygonGate>']
- if vertices2 is not None:
- polygon2 = [' <gating:PolygonGate gating:id="Polygon2">',
- ' <gating:dimension gating:compensation-ref="uncompensated">',
- ' <data-type:fcs-dimension data-type:name="FSC-A" />',
- ' </gating:dimension>',
- ' <gating:dimension gating:compensation-ref="uncompensated">',
- ' <data-type:fcs-dimension data-type:name="FSC-H" />',
- ' </gating:dimension>']
- for vertex in vertices2:
- vertex = [' <gating:vertex>',
- ' <gating:coordinate data-type:value="' + str(vertex[0]) + '" />',
- ' <gating:coordinate data-type:value="' + str(vertex[1]) + '" />',
- ' </gating:vertex>']
- polygon2 = polygon2 + vertex
- polygon2 = polygon2 + [' </gating:PolygonGate>']
- and_gate = [' <gating:BooleanGate gating:id="And1">',
- ' <data-type:custom_info>',
- ' Only keep results satisfying both Polygon gates',
- ' </data-type:custom_info>',
- ' <gating:and>',
- ' <gating:gateReference gating:ref="Polygon1" />',
- ' <gating:gateReference gating:ref="Polygon2" />',
- ' </gating:and>',
- ' </gating:BooleanGate>']
- if boundaries is not None:
- quadrants = [' <gating:QuadrantGate gating:id="Quadrant1" gating:parent_id="And1">',
- ' <gating:divider gating:id="A" gating:compensation-ref="uncompensated">',
- ' <data-type:fcs-dimension data-type:name="' + channame1 + '" />',
- ' <gating:value>' + str(boundaries[1]) + '</gating:value>',
- ' </gating:divider>',
- ' <gating:divider gating:id="B" gating:compensation-ref="uncompensated">',
- ' <data-type:fcs-dimension data-type:name="' + channame2 + '" />',
- ' <gating:value>' + str(boundaries[0]) + '</gating:value>',
- ' </gating:divider>',
- ' <gating:Quadrant gating:id="Untransfected">',
- ' <gating:position gating:divider_ref="A" gating:location="' + str(boundaries[1] - 1) + '" />',
- ' <gating:position gating:divider_ref="B" gating:location="' + str(boundaries[0] - 1) + '" />',
- ' </gating:Quadrant>',
- ' <gating:Quadrant gating:id="Double-Positive">',
- ' <gating:position gating:divider_ref="A" gating:location="' + str(boundaries[1] + 1) + '" />',
- ' <gating:position gating:divider_ref="B" gating:location="' + str(boundaries[0] + 1) + '" />',
- ' </gating:Quadrant>',
- ' <gating:Quadrant gating:id="fluorophorea">',
- ' <gating:position gating:divider_ref="A" gating:location="' + str(boundaries[1] + 1) + '" />',
- ' <gating:position gating:divider_ref="B" gating:location="' + str(boundaries[0] - 1) + '" />',
- ' </gating:Quadrant>',
- ' <gating:Quadrant gating:id="fluorophoreb">',
- ' <gating:position gating:divider_ref="A" gating:location="' + str(boundaries[1] - 1) + '" />',
- ' <gating:position gating:divider_ref="B" gating:location="' + str(boundaries[0] + 1) + '" />',
- ' </gating:Quadrant>',
- ' </gating:QuadrantGate>']
- second_and_gate = [' <gating:BooleanGate gating:id="And2Untransfected">',
- ' <data-type:custom_info>',
- ' Only keep results satisfying both Polygon gates and Untransfected',
- ' </data-type:custom_info>',
- ' <gating:and>',
- ' <gating:gateReference gating:ref="And1" />',
- ' <gating:gateReference gating:ref="Untransfected" />',
- ' </gating:and>',
- ' </gating:BooleanGate>']
- third_and_gate = [' <gating:BooleanGate gating:id="And3DoublePositive">',
- ' <data-type:custom_info>',
- ' Only keep results satisfying both Polygon gates and Double-Positive',
- ' </data-type:custom_info>',
- ' <gating:and>',
- ' <gating:gateReference gating:ref="And1" />',
- ' <gating:gateReference gating:ref="Double-Positive" />',
- ' </gating:and>',
- ' </gating:BooleanGate>']
- fourth_and_gate = [' <gating:BooleanGate gating:id="And4fluorophorea">',
- ' <data-type:custom_info>',
- ' Only keep results satisfying both Polygon gates and fluorophorea',
- ' </data-type:custom_info>',
- ' <gating:and>',
- ' <gating:gateReference gating:ref="And1" />',
- ' <gating:gateReference gating:ref="fluorophorea" />',
- ' </gating:and>',
- ' </gating:BooleanGate>']
- fifth_and_gate = [' <gating:BooleanGate gating:id="And5fluorophoreb">',
- ' <data-type:custom_info>',
- ' Only keep results satisfying both Polygon gates and fluorophoreb',
- ' </data-type:custom_info>',
- ' <gating:and>',
- ' <gating:gateReference gating:ref="And1" />',
- ' <gating:gateReference gating:ref="fluorophoreb" />',
- ' </gating:and>',
- ' </gating:BooleanGate>']
- gate_text = gate_text + polygon1 + polygon2 + and_gate + quadrants + \
- second_and_gate + third_and_gate + fourth_and_gate + fifth_and_gate + ['</gating:Gating-ML>']
- else:
- gate_text = gate_text + polygon1 + polygon2 + and_gate + ['</gating:Gating-ML>']
- else:
- gate_text = gate_text + polygon1 + ['</gating:Gating-ML>']
- with open(filename, "w") as f:
- for line in gate_text:
- if line not in ['\n', '\r\n']:
- f.write("%s\n" % line)
- #selection for the first gate
- def fitEllipse(x,y):
- x = x[:,np.newaxis]
- y = y[:,np.newaxis]
- D = np.hstack((x*x, x*y, y*y, x, y, np.ones_like(x)))
- S = np.dot(D.T,D)
- C = np.zeros([6,6])
- C[0,2] = C[2,0] = 2; C[1,1] = -1
- E, V = eig(np.dot(inv(S), C))
- n = np.argmax(np.abs(E))
- a = V[:,n]
- b,c,d,f,g,a=a[1]/2., a[2], a[3]/2., a[4]/2., a[5], a[0]
- num=b*b-a*c
- cx=(c*d-b*f)/num
- cy=(a*f-b*d)/num
- angle=0.5*np.arctan(2*b/(a-c))*180/np.pi
- up = 2*(a*f*f+c*d*d+g*b*b-2*b*d*f-a*c*g)
- down1=(b*b-a*c)*( (c-a)*np.sqrt(1+4*b*b/((a-c)*(a-c)))-(c+a))
- down2=(b*b-a*c)*( (a-c)*np.sqrt(1+4*b*b/((a-c)*(a-c)))-(c+a))
- a=np.sqrt(abs(up/down1))
- b=np.sqrt(abs(up/down2))
- ell=Ellipse((cx,cy),a*2.,b*2.,angle=angle)
- ell_coord=ell.get_verts()
- return [ell_coord, cx, cy, a*2, b*2, angle]
- #kde = kernel density estimation; matching density to color
- def make_kde(points):
- x = points[:, 0]
- y = points[:, 1]
- # Define the borders
- deltaX = (max(x) - min(x))/1000
- deltaY = (max(y) - min(y))/1000
- xmin = min(x) - deltaX
- xmax = max(x) + deltaX
- ymin = min(y) - deltaY
- ymax = max(y) + deltaY
- # Create meshgrid
- xx, yy = np.mgrid[xmin:xmax:50j, ymin:ymax:50j]
- positions = np.vstack([xx.ravel(), yy.ravel()])
- values = np.vstack([x, y])
- kernel = st.gaussian_kde(values)
- f = np.reshape(kernel(positions).T, xx.shape)
- fig = plt.figure(figsize=(16,16))
- ax = fig.gca()
- cset1 = ax.contour(xx, yy, f, levels=50, colors='k')
- figure_centre = [(xmin + xmax)/2, (ymin + ymax)/2]
- plt.cla()
- plt.clf()
- return [cset1.allsegs, figure_centre, x, y]
- #similar to above
- def getKernelDensityEstimation(values, x, bandwidth = 0.2, kernel = 'gaussian'):
- model = KernelDensity(kernel = kernel, bandwidth=bandwidth)
- model.fit(values[:, np.newaxis])
- log_density = model.score_samples(x[:, np.newaxis])
- return np.exp(log_density)
- #a helper function for kde color matching
- def bestBandwidth(data, minBandwidth = 0.1, maxBandwidth = 2, nb_bandwidths = 30, cv = 30):
- """
- Run a cross validation grid search to identify the optimal bandwidth for the kernel density
- estimation.
- """
- model = GridSearchCV(KernelDensity(),
- {'bandwidth': np.linspace(minBandwidth, maxBandwidth, nb_bandwidths)}, cv=cv, n_jobs=-1)
- model.fit(data)
- return model.best_params_['bandwidth']
- #possibly for defining the last gate
- def z(samplename, Z, a, vertices1, vertices2, dest, sample, fluorophore1, fluorophore2, channame1, channame2, neg_cntrl, leg_g1):
- fluorophores = fluorophore1 + '_' + fluorophore2
- filename = join(dest, 'gates.xml')
- d = Z[a]
- #Take a pseudorandom subsample of d if it is > 10000 points:
- # if d.shape[0] > 10000:
- # random.seed(42)
- # print(d.shape)
- # rand_idx = random.sample(range(d.shape[0]), k = 10000)
- # d = d[rand_idx, :]
- if len(d) == 0:
- warnings.warn("No sample for last gate")
- return [samplename, 0, None, 0, [0, 0], [0, 0, 0, 0]]
- new_Z = np.array([d[:, 0] + abs(min(d[:, 0])) + 1, d[:, 1] + abs(min(d[:, 1])) + 1])
- new_Z= new_Z.T
- Z_log = np.log(new_Z)
- try:
- [alls, figure_centre, x, y] = make_kde(Z_log)
- except ValueError:
- warnings.warn("Can't make kde")
- return [samplename, 0, None, 0, [0, 0], [0, 0, 0, 0]]
- max_area = 0
- best_top_point = None
- best_right_point = None
- xy = np.vstack([x,y])
- cv_bandwidth = bestBandwidth(xy.T)
- kde_model = KernelDensity(kernel='gaussian', bandwidth=cv_bandwidth).fit(xy.T)
- kde = np.exp(kde_model.score_samples(xy.T))
- idx = kde.argsort()
- candidates = []
- #These are variables for the negative control gating
- nc_top_point = 0
- nc_right_point = 0
- if neg_cntrl[0] != "None" and samplename.rsplit('.fcs')[0] != neg_cntrl[0]:
- best_top_point, best_right_point = neg_cntrl[1]
- else:
- for j in range(len(alls)):
- for ii, seg in enumerate(alls[j]):
- #To find the best points for negative control, identify the rightmost and topmost points on a contour
- if neg_cntrl[0] != "None" and samplename.rsplit('.fcs')[0] == neg_cntrl[0]:
- if seg[0][0] == seg[-1][0] and seg[0][1] == seg[-1][1]:
- top_point_log = max(seg[:,1])
- right_point_log = max(seg[:,0])
- top_point = np.exp(top_point_log) - abs(min(d[:, 1])) - 1
- right_point = np.exp(right_point_log) - abs(min(d[:, 0])) - 1
- if top_point > nc_top_point:
- nc_top_point = top_point
- if right_point > nc_right_point:
- nc_right_point = right_point
- else:
- #The following applies to FETCH, isn't relevant for negative control
- p = Path(seg) # make a polygon
- grid = p.contains_points(xy.T)
- mean_kde = np.mean(kde[grid])
- top_point_log = max(seg[:,1])
- right_point_log = max(seg[:,0])
- top_point = np.exp(top_point_log) - abs(min(d[:, 1])) - 1
- right_point = np.exp(right_point_log) - abs(min(d[:, 0])) - 1
- if leg_g1:
- transfected_cells_x = right_point >= 500
- transfected_cells_y = top_point >= 500
- else:
- transfected_cells_x = right_point >= 1100
- transfected_cells_y = top_point >= 1100
- plt.plot(seg[:,0], seg[:,1], '.-')
- if transfected_cells_x or transfected_cells_y:
- continue
- area = 0.5*np.abs(np.dot(seg[:,0],np.roll(seg[:,1],1))-np.dot(seg[:,1],np.roll(seg[:,0],1)))
- if area > max_area:
- candidates.append([area, kde[grid], top_point, right_point])
- if neg_cntrl[0] == "None":
- largest_cand = [0]
- if len(candidates) == 0:
- warnings.warn("Pipeline error on this file")
- return [samplename, 0, None, 0, [0, 0], [0, 0, 0, 0]]
- for cand in candidates:
- if cand[0] > largest_cand[0]:
- largest_cand = cand
- h_plt = plt.hist(largest_cand[1], bins=100)
- bin_count = h_plt[0]
- cutoff = h_plt[1]
- #this if/else is not currently used, but could be used to make the last gate more stringent
- if leg_g1:
- quantile_cutoff_val = 0.60
- else:
- quantile_cutoff_val = 0.30
- quantile_cutoff = np.quantile(cutoff, quantile_cutoff_val)
- for cand in candidates:
- if np.mean(cand[1]) < quantile_cutoff:
- continue
- if cand[0] > max_area:
- max_area = cand[0]
- best_top_point = cand[2]
- best_right_point = cand[3]
- #Plot kde lines on the log-transformed data for debugging:
- plt.figure(num=None, figsize=(16, 16), dpi=80, facecolor='w', edgecolor='k')
- for j in range(len(alls)):
- for ii, seg in enumerate(alls[j]):
- plt.plot(seg[:,0], seg[:,1], '.-')
- plt.scatter(Z_log[:, 0], Z_log[:, 1], s=12.5)
- if best_top_point != None:
- best_log_top = np.log(best_top_point + abs(min(d[:, 1])) + 1)
- best_log_right = np.log(best_right_point + abs(min(d[:, 0])) + 1)
- plt.plot([min(Z_log[:, 0]), max(Z_log[:, 0])], [best_log_top, best_log_top], c='black')
- plt.plot([best_log_right, best_log_right], [min(Z_log[:, 1]), max(Z_log[:, 1])], c='black')
- print(channame1)
- print(channame2)
- plt.savefig(join(dest, channame1 + '_' + channame2 + '_debug_third_gate.pdf'), format='pdf', bbox_inches='tight')
- plt.cla()
- plt.clf()
- boundaries = [best_right_point, best_top_point]
- if len(sample.channels) == 7:
- gname = join(dest, fluorophores + '_gates.xml')
- else:
- gname = join(dest, 'gates.xml')
- if neg_cntrl[0] != "None":
- if neg_cntrl[0] == samplename.rsplit('.fcs')[0]:
- best_top_point = nc_top_point
- best_right_point = nc_right_point
- boundaries = [best_right_point, best_top_point]
- else:
- best_right_point, best_top_point = neg_cntrl[1]
- boundaries = [best_right_point, best_top_point]
- samplename = samplename + "_" + channame1 + "_" + channame2
- gate_writer(vertices1, vertices2, boundaries, gname, channame1, channame2, fluorophore1, fluorophore2)
- g_strat = fk.parse_gating_xml(gname)
- gs_results = g_strat.gate_sample(sample)
- e = gs_results.get_gate_membership('And3DoublePositive')
- fig = figure(num=None, figsize=(16, 16), dpi=80, facecolor='w', edgecolor='k')
- xy = d
- x = d[:, 0]
- y = d[:, 1]
- x_sorted, y_sorted, kde_sorted = x[idx], y[idx], kde[idx]
- # parameters of the main output plot
- plt.scatter(x, y, c=kde, cmap = 'turbo', s=15)
- plt.yscale('symlog', linthresh=1000)
- plt.xscale('symlog', linthresh=1000)
- # plt.scatter(new_Z[:, 0], new_Z[:, 1], c=kde, cmap = 'turbo', s=15)
- # plt.yscale('log')
- # plt.xscale('log')
- #these are gate lines; min, max are the range of point values; best points define position of the gate
- plt.plot([min(x), max(x)], [best_top_point, best_top_point], c='black')
- plt.plot([best_right_point, best_right_point], [min(y), max(y)], c='black')
- #df is a table format for... parsed.. data..
- df = gs_results.report
- df = df.reset_index()
- #numbers of each individual quadrant
- double_positives = list(df.loc[df['gate_name'] == 'And3DoublePositive']['count'])[0]
- green = list(df.loc[df['gate_name'] == 'And5fluorophoreb']['count'])[0]
- red = list(df.loc[df['gate_name'] == 'And4fluorophorea']['count'])[0]
- untransfected = list(df.loc[df['gate_name'] == 'And2Untransfected']['count'])[0]
- #for each sample, for each pair of colors in it, export a dataframe with fluorescence values for each cell in it
- color_df = pd.DataFrame(columns = [channame2, channame1])
- color_df[channame2] = x
- color_df[channame1] = y
- color_df.to_csv(join(dest, channame1 + "_" + channame2 + ".csv"))
- #this is a contingency for blank samples
- if neg_cntrl[0] == "None" and double_positives + green + red == 0:
- print("Only untransfected cells found")
- return [samplename, 0, None, 0, boundaries, [untransfected, red, green, double_positives]]
- #defining the FETCH score
- try:
- FETCH_score = double_positives/(double_positives + green + red)
- except ZeroDivisionError:
- FETCH_score = 0
- #another contingency
- if neg_cntrl[0] == "None" and (FETCH_score > 0.90 or untransfected/(double_positives + green + red + untransfected) > 0.90):
- print("FETCH score unreasonably high -- something went wrong")
- return [samplename, 0, None, double_positives + green + red + untransfected, boundaries, [untransfected, red, green, double_positives]]
- try:
- r_g = red/green
- except ZeroDivisionError:
- print("Can't calculate the proportion of red to green: there is no green cells")
- r_g = 0
- ax = plt.gca()
- minor = matplotlib.ticker.LogLocator(base = 10.0, subs = np.arange(1.0, 10.0) * 0.1, numticks = 10)
- ax.yaxis.set_minor_locator(minor)
- ax.yaxis.set_minor_formatter(matplotlib.ticker.NullFormatter())
- ax.xaxis.set_minor_locator(minor)
- ax.xaxis.set_minor_formatter(matplotlib.ticker.NullFormatter())
- ax.tick_params(which='minor', length=10, width=2)
- ax.tick_params(which='major', length=20, width=3)
- plt.setp(ax.get_xticklabels(), rotation=45, ha="right", rotation_mode="anchor", fontsize=14)
- plt.setp(ax.get_yticklabels(), fontsize=14)
- #text boxes in the output plot
- txt1 = AnchoredText('Q1\n' + str(round(100*red/(double_positives + green + red + untransfected), 1)), loc="upper left", pad=0.4, borderpad=0, prop={"fontsize":14})
- txt2 = AnchoredText('Q2\n' + str(round(100*double_positives/(double_positives + green + red + untransfected), 1)), loc="upper right", pad=0.4, borderpad=0, prop={"fontsize":14})
- txt3 = AnchoredText('Q3\n' + str(round(100*green/(double_positives + green + red + untransfected), 1)), loc="lower right", pad=0.4, borderpad=0, prop={"fontsize":14})
- txt4 = AnchoredText('Q4\n' + str(round(100*untransfected/(double_positives + green + red + untransfected), 1)), loc="lower left", pad=0.4, borderpad=0, prop={"fontsize":14})
- #this puts texts boxes onto the plot
- ax.add_artist(txt1)
- ax.add_artist(txt2)
- ax.add_artist(txt3)
- ax.add_artist(txt4)
- ax.set_title("FETCH Score: " + str(FETCH_score))
- #look into the bbox, bounding box
- plt.savefig(join(dest, channame1 + '_' + channame2 + '_double_positive_final.pdf'), format='pdf', bbox_inches='tight')
- #clear axes and figure to plot next
- plt.cla()
- plt.clf()
- return [samplename, FETCH_score, r_g, double_positives + green + red + untransfected, boundaries, [untransfected, red, green, double_positives]]
- #Above, the helper files were added, Below here, the actual processing, central functions are listed
- def FETCH_analysis(inputlist):
- plt.close('all')
- fcs_path, samplename, dest, leg_g1, neg_cntrl, log_gate = inputlist
- #make a directly for an FCS file and use flowkit to parse that, to get variable called sample
- os.mkdir(dest)
- plt.grid(visible=None)
- sample = fk.Sample(fcs_path)
- fsc_a_loc = sample.channels.loc[sample.channels['pnn'].str.contains('FSC-A')].index[0]
- ssc_a_loc = sample.channels.loc[sample.channels['pnn'].str.contains('SSC-A')].index[0]
- fsc_h_loc = sample.channels.loc[sample.channels['pnn'].str.contains('FSC-H')].index[0]
- remaining_rows = [r for r in range(sample.channels.shape[0]) if r not in [fsc_a_loc, ssc_a_loc, fsc_h_loc]]
- other_chans = list(sample.channels.iloc[remaining_rows]['pnn'])
- other_chans = [chn for chn in other_chans if 'Time' not in chn]
- fluor_chan_n = len(other_chans)
- arr1 = sample.get_channel_events(fsc_a_loc, source='raw', subsample=False) #FSC-A
- arr2 = sample.get_channel_events(ssc_a_loc, source='raw', subsample=False) #SSC-A
- arr3 = sample.get_channel_events(fsc_h_loc, source='raw', subsample=False) #FSC-H
- if 'FITC' in other_chans or '1-A' in other_chans: #always have green along x axis
- if 'FITC' in other_chans:
- grn_ch_name = 'FITC'
- else:
- grn_ch_name = '1-A'
- x_ax_index = [chn for chn in other_chans if grn_ch_name in chn][0]
- x_chan_name = list(sample.channels.loc[sample.channels['pnn'].str.contains(grn_ch_name)]['pns'])[0]
- green_loc = sample.channels.loc[sample.channels['pnn'].str.contains(grn_ch_name)].index[0]
- other_chans = [chn for chn in other_chans if grn_ch_name not in chn]
- arr4 = sample.get_channel_events(green_loc, source='raw', subsample=False) #1-A or FITC-A(Emerald)
- else:
- x_ax_index = other_chans[0]
- x_chan_name = list(sample.channels.loc[sample.channels['pnn'].str.contains(other_chans[0])]['pns'])[0]
- first_loc = sample.channels.loc[sample.channels['pnn'].str.contains(other_chans[0])].index[0]
- other_chans = [chn for chn in other_chans if other_chans[0] not in chn]
- arr4 = sample.get_channel_events(first_loc, source='raw', subsample=False) #1-A or FITC-A(Emerald)
- if x_chan_name == '':
- x_chan_name = x_ax_index
- if len(other_chans) == 0: #If only got one fluorescent channel, use it as x and FSC-A as y
- y_chan_name = 'FSC-A'
- y_ax_index = 'FSC-A'
- Z = np.stack((arr4, arr1), axis=1)
- else:
- second_loc = sample.channels.loc[sample.channels['pnn'].str.contains(other_chans[0])].index[0]
- y_ax_index = other_chans[0]
- y_chan_name = list(sample.channels.loc[sample.channels['pnn'].str.contains(other_chans[0])]['pns'])[0]
- if y_chan_name == '':
- y_chan_name = y_ax_index
- arr5 = sample.get_channel_events(second_loc, source='raw', subsample=False) #5-A(RFP670), PE-Texas Red-A(mCherry), PE-A (mApple), or any other color
- Z = np.stack((arr4, arr5), axis=1)
- if fluor_chan_n == 3: # have 3 fluorescent channels
- third_loc = sample.channels.loc[sample.channels['pnn'].str.contains(other_chans[1])].index[0]
- z_chan_name = list(sample.channels.loc[sample.channels['pnn'].str.contains(other_chans[1])]['pns'])[0]
- z_ax_index = other_chans[1]
- arr6 = sample.get_channel_events(third_loc, source='raw', subsample=False)
- Z_ea = np.stack((arr4, arr5), axis=1)
- Z_er = np.stack((arr4, arr6), axis=1)
- Z_ar = np.stack((arr5, arr6), axis=1)
- elif fluor_chan_n > 3:
- raise Exception("Something is wrong with your channel number")
- #arr = array, plot.. x = 1st gate and y = 2nd gate
- if log_gate:
- alter_X = np.array([arr2 + abs(min(arr2)) + 1, arr1 + abs(min(arr1)) + 1])
- alter_X = alter_X.T
- X = np.log(alter_X)
- else:
- X = np.stack((arr2, arr1), axis=1)
- Y = np.stack((arr1, arr3), axis=1)
- #this loop goes through the contours of the first gate
- [alls, figure_centre, x, y] = make_kde(X)
- max_area = 0
- best_seg = None
- point_num = None
- plt.figure(num=None, figsize=(16, 16), dpi=80, facecolor='w', edgecolor='k')
- seg_list = []
- min_points = 4000
- for j in range(len(alls)):
- for ii, seg in enumerate(alls[j]):
- non_single_cells_x = min(seg[:,0]) <= 25000
- non_single_cells_y = min(seg[:,1]) <= 25000
- out_of_bounds_x = max(seg[:,0]) > (max(x) - 10000)
- out_of_bounds_y = max(seg[:,1]) > (max(y) - 10000)
- plt.plot(seg[:,0], seg[:,1], '.-')
- area = 0.5*np.abs(np.dot(seg[:,0],np.roll(seg[:,1],1))-np.dot(seg[:,1],np.roll(seg[:,0],1)))
- p = path.Path(seg)
- mask = p.contains_points(X)
- num_points = X[mask].shape[0]
- if num_points >= min_points:
- seg_list.append([j, area, ii])
- if non_single_cells_x or non_single_cells_y or out_of_bounds_x or out_of_bounds_y or len(seg[:,0])<10:
- continue
- if area > max_area:
- max_area = area
- best_seg = seg
- point_num = num_points
- if best_seg is None or point_num < min_points:
- if len(seg_list) == 0:
- print('No cells in the first gate')
- return [samplename, 0, None, 0]
- newbest = None
- smallest_area = np.inf
- for item in seg_list:
- if item[1] < smallest_area:
- smallest_area = item[1]
- best_seg = alls[item[0]][item[2]]
- #fitting an elipse to our identified best fit contour
- if log_gate:
- return_X = np.array([np.exp(X[:, 0]), np.exp(X[:, 1])])
- return_X = return_X.T
- X = np.array([return_X[:, 0] - abs(min(arr2)) - 1, return_X[:, 1] - abs(min(arr1)) - 1])
- X = X.T
- return_best_seg = np.array([np.exp(best_seg[:, 0]), np.exp(best_seg[:, 1])])
- return_best_seg = return_best_seg.T
- best_seg = np.array([return_best_seg[:, 0] - abs(min(arr2)) - 1, return_best_seg[:, 1] - abs(min(arr1)) - 1])
- best_seg = best_seg.T
- ell_coord, el_cx, el_cy, el_w, el_h, el_angle = fitEllipse(best_seg[:,0],best_seg[:,1])
- vertices1 = np.round(ell_coord, 0)
- if not leg_g1: #Keep the definition of vertices1 only if replicating old data
- # Step 1: Filter points whose y values are within 1000 of the target value
- filtered_points = X[np.abs(X[:, 1] - min(vertices1[:, 1])) <= 1000]
- thresh = 1000
- while np.shape(filtered_points)[0] == 0:
- thresh += 500
- filtered_points = X[np.abs(X[:, 1] - min(vertices1[:, 1])) <= thresh]
- # # Step 2: Sort the filtered points based on x values
- sorted_points = filtered_points[np.argsort(filtered_points[:, 0])]
- # # Step 3: Select the point with the smallest x value from the sorted array
- left_low = sorted_points[0]
- # # # Step 4: Filter points whose y values are at least 10 more than the left_low's
- # filtered_points2 = X[(X[:, 1] > (left_low[1] + 10)) & (X[:, 0] > left_low[0])]
- # # # Step5: get the left_mid point to get the slope of the left bound
- # left_mid = filtered_points2[np.argsort(filtered_points2[:, 0])][0]
- # # left_mid = [left_mid[1], left_mid[0]]
- # slope = (left_mid[1] - left_low[1]) / (left_mid[0] - left_low[0])
- # y_intercept = left_low[1] - slope * left_low[0]
- # top_left = [(max(X[:, 1]) - 10 - y_intercept) / slope, max(X[:, 1]) -10]
- top_left = [left_low[0], max(X[:, 1]) -10]
- right_top = [max(X[:, 0]) -10, max(X[:, 1]) -10]
- # #Step 6: get the line parallel to the ellipse's angle and perpendicular to its second principal component to define left bound
- rad90 = np.radians(90)
- if el_angle < 0:
- el_angle = el_angle + 90
- ell_side_point = [el_cx + el_h/2*np.sin(np.radians(el_angle))/np.sin(rad90), el_cy - el_h/2*np.sin(np.radians(90 - el_angle))/np.sin(rad90)]
- slope_right = np.tan(np.radians(el_angle))
- y_intercept_right = ell_side_point[1] - slope_right * ell_side_point[0]
- mid_right = [max(X[:, 0]) -10, slope_right*(max(X[:, 0]) -10) + y_intercept_right]
- low_right = [(left_low[1] - y_intercept_right)/slope_right, left_low[1]]
- vertices1 = np.round([left_low, top_left, right_top, mid_right, low_right], 0)
- #generates the .xml file
- filename = join(dest, 'gates.xml')
- gate_writer(vertices1, None, None, filename)
- g_strat = fk.parse_gating_xml(filename)
- gs_results = g_strat.gate_sample(sample)
- #gets the indices of selected cells to move into gate 2
- a = gs_results.get_gate_membership('Polygon1')
- b = Y[a]
- #len= length; a contingency
- if len(b) == 0:
- print('No cells in the second gate')
- return [samplename, 0, None, 0]
- plt.scatter(X[:, 0], X[:, 1], c=a, s=12.5)
- ax = plt.gca()
- ax.set_xlabel('SSC-A', fontsize=36)
- ax.set_ylabel('FSC-A', fontsize=36)
- ax.set_title("First Gate", fontsize=30)
- plt.setp(ax.get_xticklabels(), rotation=45, ha="right", rotation_mode="anchor", fontsize=36)
- plt.setp(ax.get_yticklabels(), fontsize=36)
- plt.savefig(join(dest, 'first_gate_KDE.pdf'), format='pdf', bbox_inches='tight')
- plt.cla()
- plt.clf()
- coefficients = np.polyfit(b[:, 0], b[:, 1], 1)
- poly = np.poly1d(coefficients)
- new_x = np.linspace(min(b[:, 0]), max(b[:, 0]), 2)
- new_y = poly(new_x)
- norms = []
- p1 = np.array([new_x[0], new_y[0]])
- p2 = np.array([new_x[1], new_y[1]])
- for point in b:
- d = norm(np.cross(p2-p1, p1-point))/norm(p2-p1)
- norms.append(d)
- std = np.array(norms).std()
- mask2 = []
- for i in range(len(b)):
- if norms[i] > 4*std:
- mask2.append(False)
- else:
- mask2.append(True)
- coefficients2 = np.polyfit(b[:, 0], b[:, 1], 1)
- poly2 = np.poly1d(coefficients)
- new_x2 = np.linspace(min(b[:, 0]), max(b[:, 0]), 2)
- new_y2 = poly(new_x)
- factor = 4*std
- plt.figure(num=None, figsize=(16, 16), dpi=80, facecolor='w', edgecolor='k')
- plt.scatter(b[:, 0], b[:, 1], c=mask2, s=12.5)
- plt.plot(new_x.tolist(), new_y.tolist(), marker = "o", c='red')
- plt.plot(new_x, [new_y[0] - factor, new_y[1] - factor], marker = "o", c='black')
- plt.plot(new_x, [new_y[0] + factor, new_y[1] + factor], marker = "o", c='black')
- ax = plt.gca()
- ax.set_xlabel('FSC-A', fontsize=36)
- ax.set_ylabel('FSC-H', fontsize=36)
- ax.set_title("Second Gate", fontsize=36)
- plt.setp(ax.get_xticklabels(), rotation=45, ha="right", rotation_mode="anchor", fontsize=36)
- plt.setp(ax.get_yticklabels(), fontsize=36)
- plt.savefig(join(dest, 'secondgate.pdf'), format='pdf', bbox_inches='tight')
- plt.cla()
- plt.clf()
- vertices2 = np.array([[new_x[0], new_y[0] - factor],
- [new_x[1], new_y[1] - factor],
- [new_x[1], new_y[1] + factor],
- [new_x[0], new_y[0] + factor]])
- if np.isnan(vertices1).any() or np.isnan(vertices2).any():
- return [samplename, 0, None, 0]
- gate_writer(vertices1, vertices2, None, filename)
- g_strat = fk.parse_gating_xml(filename)
- gs_results = g_strat.gate_sample(sample)
- a = gs_results.get_gate_membership('And1')
- c = Y[a]
- fluorescent_chan_names = list(sample.channels['pns'].unique())
- if fluor_chan_n == 1 or fluor_chan_n == 2:
- return z(samplename, Z, a, vertices1, vertices2, dest, sample, y_chan_name, x_chan_name, y_ax_index, x_ax_index, [neg_cntrl[0], neg_cntrl[1][0]], leg_g1)
- elif len(sample.channels) == 7:
- first = z(samplename, Z_ea, a, vertices1, vertices2, dest, sample, y_chan_name, x_chan_name, y_ax_index, x_ax_index, [neg_cntrl[0], neg_cntrl[1][0]], leg_g1)
- second = z(samplename, Z_er, a, vertices1, vertices2, dest, sample, z_chan_name, x_chan_name, z_ax_index, x_ax_index, [neg_cntrl[0], neg_cntrl[1][1]], leg_g1)
- third = z(samplename, Z_ar, a, vertices1, vertices2, dest, sample, z_chan_name, y_chan_name, z_ax_index, y_ax_index, [neg_cntrl[0], neg_cntrl[1][2]], leg_g1)
- return [first, second, third]
- def summarize(outputs, fcs_folder, project_name, skip_renaming):
- dataf = pd.DataFrame(outputs)
- print(dataf)
- dataf = dataf.rename({0: "File", 1: "FETCH score", 2 : "r_g", 3:"n_tot", 4: "3rd_gate_coord", 5:"raw_counts"}, axis='columns')
- dataf['Dubious?'] = [False for i in range(dataf.shape[0])]
- dataf['FETCH score'] = dataf['FETCH score'].astype(float)
- dataf['r_g'] = dataf['r_g'].astype(float)
- dataf['n_tot'] = dataf['n_tot'].astype(int)
- dataf['Dubious?'] = dataf.apply(lambda row : 'yes' if ((row['r_g'] >= 2) or (row['r_g'] <= 0.5) or (row['n_tot'] < 500) or np.isnan(row['r_g'])) else 'no',
- axis=1)
- dataf = dataf.drop(['r_g'], axis=1)
- try:
- dataf["File"] = dataf["File"].apply(lambda x: x.rsplit('_')[2] + '_' + x.rsplit('_')[0] + '_' + x.rsplit('_')[1] + '_' + x.rsplit('_')[3] if x not in skip_renaming else x)
- except IndexError:
- pass
- dataf = dataf.sort_values(by='File')
- dataf['Numbername'] = [i for i in range(dataf.shape[0])]
- sns.set(font_scale=2)
- figure(num=None, figsize=(32, 16), dpi=80, facecolor='w', edgecolor='k')
- colors = [(0, 0, 0) if dataf["Dubious?"].iloc[i] == 'no' else (1, 0, 0) for i in range(dataf["File"].unique().shape[0])]
- sns.set_style('ticks')
- sns.catplot(x='File', y='FETCH score', palette=colors, capsize=.2, kind="point", ci="sd", data=dataf, height=10, aspect=2)
- g = sns.swarmplot(x='File', y='FETCH score', data=dataf, color="purple", size=5)
- ax = plt.gca()
- ax.set_xlabel('FETCH_id')
- ax.set_ylabel('FETCH Score')
- plt.setp(ax.get_xticklabels(), rotation=45, ha="right", rotation_mode="anchor", fontsize=8)
- plt.setp(ax.get_yticklabels(), fontsize=26)
- ax.set(facecolor = "white")
- ax.set_title(project_name, fontsize=26)
- dataf['FETCH score'] = dataf['FETCH score'].fillna(0)
- ax.set(ylim=(0, max(dataf['FETCH score'])+0.1))
- plt.savefig(join(fcs_folder, project_name + ".pdf"), format='pdf', bbox_inches='tight')
- plt.cla()
- plt.clf()
- plt.close()
- dataf = dataf.set_index("File")
- dataf.to_csv(join(fcs_folder, project_name + ".csv"))
- #identify which folder contains our fcs files, etc.
- def main(args):
- fcs_folder = args.folder
- project_name = args.project
- leg_g1 = not(args.legacy_analysis == 'False') #for replicability of past analysis, add an optional argument that sets a more narrow first gate
- skip_renaming = args.skip_renaming
- if skip_renaming == '':
- skip_renaming = []
- negative_control = args.negative_control
- predefined_negative_control = args.predefined_negative_control
- log_gate = args.log_gate
- if log_gate != 'None':
- log_gate = True
- else:
- log_gate = False
- plt.cla()
- plt.clf()
- plt.close()
- plt.style.use('default')
- inpts = [[join(fcs_folder, samplename), samplename,
- join(fcs_folder, samplename.rsplit('.')[0]), leg_g1,
- [negative_control, [[None, None], [None, None], [None, None]]], log_gate] if
- (samplename != '.DS_Store' and not os.path.isdir(join(fcs_folder, samplename.rsplit('.')[0])))
- else None for samplename in os.listdir(fcs_folder)]
- inpts = list(filter(None, inpts))
- outstuff = []
- if negative_control != "None":
- for i_pos, el in enumerate(inpts):
- if el[1].rsplit('.fcs')[0] == negative_control:
- neg_outpt = FETCH_analysis(inpts.pop(i_pos))
- if len(neg_outpt) == 3: #the three fluorescent channels condition
- first_res = neg_outpt[0][4]
- second_res = neg_outpt[1][4]
- third_res = neg_outpt[2][4]
- for subel in neg_outpt:
- outstuff.append(subel)
- inpts = [[el[0], el[1], el[2], el[3], [el[4], [first_res, second_res, third_res]], log_gate] for el in inpts]
- else:
- outstuff.append(neg_outpt)
- inpts = [[el[0], el[1], el[2], el[3], [el[4], [neg_outpt[4], [None, None], [None, None]]], log_gate] for el in inpts]
- break
- if predefined_negative_control != "None":
- x_ax_thresh, y_ax_thresh = [int(val) for val in predefined_negative_control.split(',')]
- inpts = [[el[0], el[1], el[2], el[3], [el[4], [[x_ax_thresh, y_ax_thresh], [None, None], [None, None]]], log_gate] for el in inpts]
- for inp in inpts:
- res = FETCH_analysis(inp)
- if len(res) == 3:
- for subel in res:
- outstuff.append(subel)
- else:
- outstuff.append(res)
- #draws the aggregate plot figure and table comparing FETCH scores
- summarize(outstuff, fcs_folder, project_name, skip_renaming)
- #this is where the code actually starts; runs 'main', above
- if __name__ == '__main__':
- args = parse_arguments()
- main(args)
FETCH.py at commit 89e9901, no license · at the source
Overview
- Howard Hughes Medical Institute,Chevy Chase, MD USA
- Deparment of Psychiatry and Behavioral Sciences, Duke University Medical Center,Durham, NC USA
- Department of Neurobiology, Duke University Medical Center,Durham, NC USA
- Department of Neuroscience and Department of Cell Biology, Program in Cellular Neuroscience, Neurodegeneration and Repair, Yale University School of Medicine,New Haven, CT USA
- Department of Biomedical Engineering, Duke University,Durham, NC USA
- Salk Institute for Biological Studies,La Jolla, CA USA
- Department of Neuroscience, University of Connecticut School of Medicine,Farmington, CT USA
- Instituto de Neurobiología, Recinto de Ciencias Médicas, Universidad de Puerto Rico,San Juan, Puerto Rico
- Department of Molecular Physiology and Biophysics, Department of Psychiatry, University of Iowa,Iowa City, IA USA
- Department of Neurosurgery, Duke University Medical Center,Durham, NC USA
Abstract
Electrical signalling across distinct populations of brain cells underpins cognitive and emotional function. However, approaches that selectively regulate electrical signalling between two cellular components of a mammalian neural circuit remain sparse. Here we engineered an electrical synapse composed of two connexin proteins1 found in Morone americana (white perch fish)—connexin 34.7 and connexin 35—to accomplish mammalian circuit modulation. By exploiting protein mutagenesis, devising a new in vitro system for assaying connexin hemichannel docking, and performing computational modelling of hemichannel interactions, we uncovered a structural motif that contributes to electrical synapse formation. Targeting this motif, we designed connexin 34.7 and connexin 35 hemichannels that dock with each other to form an electrical synapse but not with other major connexins expressed in the mammalian central nervous system. We validated this electrical synapse in vivo using worms (Caenorhabditis elegans) and mice (Mus musculus). We demonstrate that it can strengthen communication across neural circuits composed of pairs of distinct cell types and modify behaviour accordingly. Thus, we establish ‘long-term integration of circuits using connexins’ (LinCx) for precision circuit editing in mammals.
Reproduced under the paper's license (CC BY), from the paper cited above.
Repositories
Its files are read in the Code ↔ Paper reader above, with 13 matches between paragraphs and lines of code.
carlson-lab/FETCH
89e99011dd920cb70f1ea87cf26c894bf9b4e3c3, 21 July 2024Availability: 1 check, the latest on 28 September 2026: the link answers
- 28 September 2026: the link answers
carlson-lab/VMD-and-NAMD-Connexin-Protein-Simulation-Protocol
0c48394e07514b514bf671a383587a89017c46e8, 15 August 2025Availability: 1 check, the latest on 28 September 2026: the link answers
- 28 September 2026: the link answers
2 files
- LICENSE.txt, License, 397 lines
- README.md, Text, 539 lines
carlson-lab/OptoLinCx
fa62a83d2fe30cfd0e3b744174fb6cf5fcae5be4, 4 November 2024Availability: 1 check, the latest on 28 September 2026: the link answers
- 28 September 2026: the link answers
70 files
- LinCx_Opto_Stim.m, MATLAB, 106 lines, 2 matches
- LinCx_Opto_Stim_mkcg.m, MATLAB, 141 lines, 2 matches
- NPMK/
@KTFigure/ , MATLAB, 48 linesKTFigure.m - NPMK/
@KTFigureAxis/ , MATLAB, 79 linesKTFigureAxis.m - NPMK/
@KTNEVComments/ , MATLAB, 131 linesKTNEVComments.m - NPMK/
@KTNSPOnline/ , MATLAB, 470 linesKTNSPOnline.m - NPMK/
@KTUEAImpedanceFile/ , MATLAB, 249 linesKTUEAImpedanceFile.m - NPMK/
@KTUEAMapFile/ , MATLAB, 277 linesKTUEAMapFile.m - NPMK/
Dependent Functions/ , MATLAB, 85 linesgetFile.m - NPMK/
Dependent Functions/ , MATLAB, 39 linesgetFolder.m - NPMK/
Dependent Functions/ , MATLAB, 29 linesgetSettingFileFullPath.m - NPMK/
Dependent Functions/ , MATLAB, 56 linesparseCommand.m - NPMK/
Dependent Functions/ , MATLAB, 64 linessyncPatternDetectNEV.m - NPMK/
Dependent Functions/ , MATLAB, 67 linessyncPatternDetectNSx.m - NPMK/
Dependent Functions/ , MATLAB, 73 linessyncPatternFinderNSx.m - NPMK/
LoadingEngines/ , MATLAB, 193 linesMClust/ BlackrockNEVLoadingEngin e.m - NPMK/
LoadingEngines/ , C, 700 linesnsNEVLIbrary 3.05/ NevLIb-3-05/ ns/ ns.c - NPMK/
LoadingEngines/ , C/C++, 776 linesnsNEVLIbrary 3.05/ NevLIb-3-05/ ns/ ns.h - NPMK/
NEV Utilities/ , MATLAB, 91 linesfindEventTimes.m - NPMK/
NEV Utilities/ , MATLAB, 76 linesmergeNEV.m - NPMK/
NEV Utilities/ , MATLAB, 819 linesopenNEVTracking.m - NPMK/
NEV Utilities/ , MATLAB, 646 linessaveNEV.m - NPMK/
NEV Utilities/ , MATLAB, 137 linessaveNEVSpikes.m - NPMK/
NEV Utilities/ , MATLAB, 129 linessaveNEVSubSpikes.m - NPMK/
NEV Utilities/ , MATLAB, 115 linessortNEV.m - NPMK/
NEV Utilities/ , MATLAB, 95 linessplitNEVResets.m - NPMK/
NPMKverChecker.m , MATLAB, 61 lines - NPMK/
NSx Utilities/ , MATLAB, 53 linesNSxPowerSpectrum.m - NPMK/
NSx Utilities/ , MATLAB, 88 linesNSxToHL.m - NPMK/
NSx Utilities/ , MATLAB, 38 linescalcTimeDelay.m - NPMK/
NSx Utilities/ , MATLAB, 114 linescombineNSxNEV.m - NPMK/
NSx Utilities/ , MATLAB, 168 linesfindSpikes.m - NPMK/
NSx Utilities/ , MATLAB, 148 linesmatrixToNSx.m - NPMK/
NSx Utilities/ , MATLAB, 233 linesmergeNSxNEV.m - NPMK/
NSx Utilities/ , MATLAB, 118 linesopenNSxHL.m - NPMK/
NSx Utilities/ , MATLAB, 115 linesplotAverageWaveforms.m - NPMK/
NSx Utilities/ , MATLAB, 93 linesremoveNSxData.m - NPMK/
NSx Utilities/ , MATLAB, 150 linesrethresholdNSx.m - NPMK/
NSx Utilities/ , MATLAB, 223 linessaveChNSx.m - NPMK/
NSx Utilities/ , MATLAB, 391 linessaveNSx.m - NPMK/
NSx Utilities/ , MATLAB, 8 linesseparatePausedNSx.m - NPMK/
NSx Utilities/ , MATLAB, 89 linesseparatePausedNSx_old.m - NPMK/
NSx Utilities/ , MATLAB, 129 linessplitNSx.m - NPMK/
NSx Utilities/ , MATLAB, 133 linessplitNSxPauses.m - NPMK/
NTrode Utilities/ , MATLAB, 28 linesntrodeGroups.m - NPMK/
NTrode Utilities/ , MATLAB, 80 linessaveNEVTetrodes.m - NPMK/
NTrode Utilities/ , MATLAB, 75 linessplitNEVNTrode.m - NPMK/
NTrode Utilities/ , MATLAB, 139 linessplitNSxNTrode.m - NPMK/
Other tools/ , MATLAB, 84 linesedgeDetect.m - NPMK/
Other tools/ , MATLAB, 21 lineskshuffle.m - NPMK/
Other tools/ , MATLAB, 16 linesoffline2Struct.m - NPMK/
Other tools/ , MATLAB, 576 linesopenCCF.m - NPMK/
Other tools/ , MATLAB, 148 linesparseCCF.m - NPMK/
Other tools/ , MATLAB, 37 linesperiEventPlot.m - NPMK/
Other tools/ , MATLAB, 94 linesplaySound.m - NPMK/
Other tools/ , MATLAB, 58 linessettingsManager.m - NPMK/
installNPMK.m , MATLAB, 43 lines - NPMK/
openNEV.m , MATLAB, 1,024 lines - NPMK/
openNSx.m , MATLAB, 1,010 lines - NPMK/
openNSxSync.m , MATLAB, 80 lines - OptoLinCx_Analysis.ipynb
, Jupyter, 1,109 lines, 1 match - OptoLinCx_Analysis_v2.ip
ynb , Jupyter, 3,066 lines, 4 matches - calc_max_resp.m, MATLAB, 49 lines
- laser_durations.m, MATLAB, 26 lines
- manual_laser_tuning.m, MATLAB, 21 lines
- openNSx.m, MATLAB, 1,073 lines
- recursive_search.m, MATLAB, 20 lines
- stim_at_v.m, MATLAB, 19 lines
- tune_laser.m, MATLAB, 53 lines
- README.md, Text, 4 lines
Code availability
The following codes for the methods implemented in this study are available from GitHub: (1) https://
Reproduced under the paper's license (CC BY), from the paper cited above.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 3 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 70 scripts, each with its path and the digest of its content;
- 13 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
All data generated in support of the findings of this study are available from the corresponding author for academic purposes upon reasonable request. Such data will be made available under a material transfer agreement. Connexin gene information was procured from the National Center for Biotechnology Information (https://
Reproduced under the paper's license (CC BY), from the paper cited above.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 2, 28 September 2026
- Publisher: n/a → Nature Portfolio
Version 1, 28 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 19 authors, 2 keywords, 9 MeSH terms, 5 funders, 95 references.
Cite
This paper
Ransey, E., Thomas, G. E., Wisdom, E. M., Almoril-Porras, A., Bowman, R., Adamson, E., Walder-Christensen, K. K., White, J. A., Hughes, D. N., Schwennesen, H., Ferguson, C., Tye, K. M., Mague, S. D., Niu, L., Wang, Z.-W., Colón-Ramos, D., Hultman, R., Bursac, N., & Dzirasa, K. (2026). Long-term editing of brain circuits using an engineered electrical synapse. Nature, 655(8123), 703-715. https://
BibTeX
@article{ransey2026long,
author = {Ransey, Elizabeth and Thomas, Gwenaëlle E. and Wisdom, Elias M. and Almoril-Porras, Agustin and Bowman, Ryan and Adamson, Elise and Walder-Christensen, Kathryn K. and White, Jesse A. and Hughes, Dalton N. and Schwennesen, Hannah and Ferguson, Caly and Tye, Kay M. and Mague, Stephen D. and Niu, Longgang and Wang, Zhao-Wen and Colón-Ramos, Daniel and Hultman, Rainbo and Bursac, Nenad and Dzirasa, Kafui},
title = {{Long-term editing of brain circuits using an engineered electrical synapse}},
journal = {Nature},
year = {2026},
month = may,
volume = {655},
number = {8123},
pages = {703--715},
publisher = {Nature Portfolio},
issn = {0028-0836},
doi = {10.1038/
url = {https://
pmid = {42129559},
pmcid = {PMC13372691}
}
RIS
TY - JOUR
AU - Ransey, Elizabeth
AU - Thomas, Gwenaëlle E.
AU - Wisdom, Elias M.
AU - Almoril-Porras, Agustin
AU - Bowman, Ryan
AU - Adamson, Elise
AU - Walder-Christensen, Kathryn K.
AU - White, Jesse A.
AU - Hughes, Dalton N.
AU - Schwennesen, Hannah
AU - Ferguson, Caly
AU - Tye, Kay M.
AU - Mague, Stephen D.
AU - Niu, Longgang
AU - Wang, Zhao-Wen
AU - Colón-Ramos, Daniel
AU - Hultman, Rainbo
AU - Bursac, Nenad
AU - Dzirasa, Kafui
TI - Long-term editing of brain circuits using an engineered electrical synapse
T2 - Nature
J2 - Nature
PY - 2026
DA - 2026/
VL - 655
IS - 8123
SP - 703
EP - 715
SN - 0028-0836
PB - Nature Portfolio
DO - 10.1038/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1038/
"type": "article-journal",
"title": "Long-term editing of brain circuits using an engineered electrical synapse",
"container-title": "Nature",
"author": [
{
"family": "Ransey",
"given": "Elizabeth"
},
{
"family": "Thomas",
"given": "Gwenaëlle E."
},
{
"family": "Wisdom",
"given": "Elias M."
},
{
"family": "Almoril-Porras",
"given": "Agustin"
},
{
"family": "Bowman",
"given": "Ryan"
},
{
"family": "Adamson",
"given": "Elise"
},
{
"family": "Walder-Christensen",
"given": "Kathryn K."
},
{
"family": "White",
"given": "Jesse A."
},
{
"family": "Hughes",
"given": "Dalton N."
},
{
"family": "Schwennesen",
"given": "Hannah"
},
{
"family": "Ferguson",
"given": "Caly"
},
{
"family": "Tye",
"given": "Kay M."
},
{
"family": "Mague",
"given": "Stephen D."
},
{
"family": "Niu",
"given": "Longgang"
},
{
"family": "Wang",
"given": "Zhao-Wen"
},
{
"family": "Colón-Ramos",
"given": "Daniel"
},
{
"family": "Hultman",
"given": "Rainbo"
},
{
"family": "Bursac",
"given": "Nenad"
},
{
"family": "Dzirasa",
"given": "Kafui"
}
],
"container-title-short":
"volume": "655",
"issue": "8123",
"page": "703-715",
"DOI": "10.1038/
"PMID": "42129559",
"PMCID": "PMC13372691",
"ISSN": "0028-0836",
"publisher": "Nature Portfolio",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
5,
13
]
]
}
}
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/s42003-026-10464-w [code]
- Quantifying electrostatic control of docking and binding energetics in functional Cx36 gap junctions.Journal: Communications biologyIn common: seaborn, scikit-learn, pandas, 3 other tools, cellular / molecular, 10 references
- [2] doi:10.7554/elife.106496
- AFD thermosensory neurons mediate tactile-dependent locomotion modulation in &
lt;i& gt;C. elegans& lt;/ i& gt;. Journal: eLifeIn common: C. elegans, cellular / molecular, 7 references - [3] doi:10.1016/j.celrep.2026.117590 [code]
- Impaired behavioral inhibition in Fmr1 KO mice is linked to disrupted visual cortex theta oscillations.Journal: Cell reportsIn common: Neo, Pingouin, statsmodels, 7 other tools, mouse, 1 reference
- [4] 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: Neo, Signal Processing Toolbox, statsmodels, 7 other tools, 1 reference
- [5] doi:10.1126/sciadv.aef0343 [code]
- Learning induces activation-mechanism-dep
endent neural plasticity in an intracortical microstimulation task. Journal: Science advancesIn common: Neo, Signal Processing Toolbox, statsmodels, 7 other tools - [6] doi:10.1016/j.isci.2026.117375 [code]
- Motor priming is associated with widespread recruitment into neural ensembles and more rapid ensemble transitions.Journal: iScienceIn common: Pingouin, Signal Processing Toolbox, statsmodels, 7 other tools, 1 reference
- [7] doi:10.7554/elife.108675 [code]
- SynaptoTagMe, a toolkit for in vivo mapping and modulating neurotransmission at single-cell resolution.Journal: eLifeIn common: C. elegans, cellular / molecular, 3 references, author Daniel A Colón-Ramos
- [8] doi:10.1038/s41593-026-02232-0 [code]
- Entorhinal cortex represents task-relevant remote locations independently of CA1.Journal: Nature neuroscienceIn common: Pingouin, Signal Processing Toolbox, statsmodels, 7 other tools, mouse
- [9] doi:10.1038/s41467-026-73818-2 [code]
- Prefrontal parvalbumin neurons mediate working memory in a task demand-dependent manner.Journal: Nature communicationsIn common: statsmodels, scikit-learn, pandas, 3 other tools, mouse, 3 references
- [10] doi:10.1016/j.nicl.2026.104012 [code]
- Structural-functional multilayer brain network properties and outcome of combined repetitive transcranial magnetic stimulation and psychotherapy for obsessive-compulsive disorder.Journal: NeuroImage. ClinicalIn common: Pingouin, Signal Processing Toolbox, statsmodels, 7 other tools
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 3 repositories of the authors' code, each at its verified commit and with its license, 70 scripts, and 13 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:625bbd5df08343c6…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
Request its removal
To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).
Discussion, reproductions, activity
Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.
Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
