Neural population dynamics of direct electrical stimulation of neocortex.
The 7 matches
- [1] § STAR★METHODS › QUANTIFICATION AND STATISTICAL ANALYSIS › Evoked potential analysis › Aligning and averaging the evoked potential ↔ figure2.ipynb, lines 805–922 · score 0.62 · CCF distance, peak aligned, normalized distance, evoked potential, boundaries, amplitude
- [2] § STAR★METHODS › METHOD DETAILS › Visual and electrical stimulation behavioral detection assay › Software and hardware implementation: ↔ dev.py, lines 242–287 · score 0.59 · spout positioning, Pyglet, solenoid, lick, monitors, delivery
- [3] § STAR★METHODS › METHOD DETAILS › Visual and electrical stimulation behavioral detection assay › Task protocol: ↔ plotter.py, lines 54–179 · score 0.59 · water delivery, daily, days, weight, curves, reward
- [4] § STAR★METHODS › QUANTIFICATION AND STATISTICAL ANALYSIS › Anisotropic volumetric analyses › Convex-hull comparison to an isotropic sphere hull ↔ figure3.ipynb, lines 1754–1827 · score 0.54 · convex hull, sphere hull, tessellated, Figure 3
- [5] § RESULTS › Evoked potential spatial extent increases sub-linearly with amplitude ↔ figure2.ipynb, lines 78–136 · score 0.54 · radial distance, point source model, meters, evoked potentials, Figure 2
- [6] § RESULTS › Anisotropic volumetric profile of the induced electrical field ↔ figure3.ipynb, lines 891–980 · score 0.50 · Euclidean distance, fitted spheres, mass, contact, radius, Figure 3
- [7] § STAR★METHODS › METHOD DETAILS › Visual and electrical stimulation behavioral detection assay › Trial structure: ↔ params.py, lines 26–77 · score 0.50 · variable, lapse, alarm, licking, Catch, 1.5 s
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
Jupyter notebook · 2,260 lines · 88 KB · GPL-3.0 · 2 matches
- # %%
- import pandas as pd
- import numpy as np
- import matplotlib.pyplot as plt
- import os,glob
- import seaborn as sns
- from open_ephys.analysis import Session
- from pynwb import NWBHDF5IO, NWBFile
- from pynwb.file import Subject
- from datetime import datetime
- from dateutil.tz import tzlocal
- from jlh_ephys.analysis import Analysis
- from jlh_ephys.utils import choose_stim_parameter, OE
- from jlh_ephys.psth_raster import psth_arr
- #from jlh_ephys import spatial_tools as st ## compatability issues maybe need to install from allensdk.core.mouse_connectivity_cache import MouseConnectivityCache in its own env
- from jlh_ephys.raw import find_artifact_start, align_data, raw_heatmap
- from ccf_3D.tools import herbs_processing as hp
- from ccf_3D.tools.metrics import distance
- import matplotlib
- matplotlib.rcParams['pdf.fonttype'] = 42
- matplotlib.rcParams['ps.fonttype'] = 42
- from scipy.ndimage import gaussian_filter1d
- # %%
- jlh31 = Analysis('jlh31', '2023-01-24') # all probes, many params and sites
- jlh32 = Analysis('jlh32', '2023-02-18') # all probes, many params and sites
- jlh33 = Analysis('jlh33', '2023-02-22') # probe B and C, many params and trains in diff recordings
- jlh34 = Analysis('jlh34', '2023-05-15') # all 3 many params
- jlh39 = Analysis('jlh39', '2023-08-16') # 3 probes for bi, no A for mono
- jlh40 = Analysis('jlh40', '2023-08-21') # probeB and probe C only: monopolar -- with diff pulse width
- jlh48 = Analysis('jlh48', '2024-11-05') # bb test, sorted only no bb portion (full bi and mono)
- jlh49 = Analysis('jlh49v1', '2024-07-11') # bb test, just no bb portion (full bi only)
- jlh53 = Analysis('jlh53concat', '2024-09-03') # regular, all 3 probes, probe A broke sometime.
- recordings = [jlh31, jlh32, jlh33, jlh34, jlh39, jlh40, jlh48, jlh49, jlh53]
- for r in recordings:
- r.get_brain_regs()
- r.get_electrode_coords()
- nn_contacts_labels = [6,11,3,14,1,16,2,15,5,12,4,13,7,10,8,9]
- EP_path = r"C:\Users\jordan\Documents\combined_figs\evoked_potentials"
- amp_alphas = {5: 0.3, 25: 0.5, 50: 0.7, 100: 1.0}
- bipolar_color = '#82A69D'
- cathodal_color = '#161943'
- anodal_color = '#D99551'
- # %%
- distances = {}
- distances_norm = {}
- for r in recordings:
- contact = r.trials.contact_negative[0]
- distances[r.mouse] = r.get_dists(contact)
- temp_norm_dist = {}
- for probe, dists in r.distances.items():
- # normalize min dist to 0
- min = np.min(dists)
- dists_norm = dists - min
- # make prior to crossing point negative and after positive
- cross_index = np.where(dists_norm == 0)[0][0] # this gives the first index where data is non-positive
- dists_norm[cross_index:] = -np.abs(dists_norm[cross_index:])
- temp_norm_dist[probe] = dists_norm
- dists[cross_index:] = -np.abs(dists[cross_index:])
- r.distances[probe] = dists
- distances_norm[r.mouse] = temp_norm_dist
- # %% [markdown]
- # # point source modeling (generating a null hypothesis) Figure 2a-b
- # %%
- rho = 5.56
- currents_uA = [5, 25, 50, 100]
- currents_A = [i * 1e-6 for i in currents_uA]
- # Radial distances (meters)
- r_min = 0.5e-4 # 0.05 mm or 50 microns
- r_max = 2e-3 # 1 mm
- N_points = 600
- r_values = np.linspace(r_min, r_max, N_points)
- fig = plt.figure(figsize=(3.54, 3.54))
- grays = ['0.8', '0.5', '0.3', '0.1']
- half_maxs_dist = [] # duh they're all the same LOL ignore this
- full_maxs_dists = []
- full_max_threshold = 0.1
- for i, I in enumerate(currents_A):
- # Compute potential (in Volts)
- V = (rho * I) / (4 * np.pi * r_values)
- # Distance in mm for plotting
- r_mm = r_values * 1e3
- hundred_microns = np.abs(r_mm - 0.100).argmin()
- max = V[hundred_microns]
- half_max = max / 2
- half_max_dist = np.abs(V - half_max).argmin()
- half_maxs_dist.append(r_mm[half_max_dist])
- full_max_dist = np.abs(V - full_max_threshold).argmin()
- full_maxs_dists.append(r_mm[full_max_dist])
- # Plot: x is potential (V), y is distance (mm)
- label_str = f"I = {I*1e6:.0f} µA"
- plt.plot(V, r_mm, linewidth=2, color=grays[i], label=label_str)
- plt.plot(V, -r_mm, linewidth=2, color=grays[i]) # mirror around 0 for ±r
- plt.xlabel('Potential V (V)', fontsize=10)
- plt.ylabel('Distance r (mm)', fontsize=10)
- plt.title('Point-Source Potential in a Homogeneous Medium', fontsize=10)
- # Match the original axis ranges:
- # Potential from 0 to 1, distance from -1 mm to 1 mm
- plt.xlim(0, 1)
- plt.ylim(-1, 1)
- plt.legend(fontsize=8)
- plt.tight_layout()
- save_str = r'C:\Users\jordan\Documents\combined_figs\evoked_potentials\point_source_modeling\potential_vs_distance.png'
- #plt.savefig(save_str)
- save_str = save_str = r'C:\Users\jordan\Documents\combined_figs\evoked_potentials\point_source_modeling\potential_vs_distance.pdf'
- #plt.savefig(save_str)
- full_maxs_dists
- # %%
- # point-source spatial extent vs current using analytic threshold crossing
- rho = 5.56 # resistivity (Ω·m)
- currents_uA = [5, 25, 50, 100] # µA
- currents_A = np.array(currents_uA) * 1e-6
- V_thresh = 0.05 # volts; "near zero" threshold (try 0.02–0.1)
- r_thresh_m = (rho * currents_A) / (4 * np.pi * V_thresh)
- r_thresh_mm = r_thresh_m * 1e3
- fig, ax = plt.subplots(figsize=(2, 2.5))
- ax.plot(currents_uA, r_thresh_mm, '-', lw=2, color = 'k')
- ax.scatter(currents_uA, r_thresh_mm, color = 'k', s=28)
- ax.set_xlabel('current (µA)')
- ax.set_ylabel(r'$r_{\mathrm{th}}$ (mm)')
- ax.set_title(f'spatial extent at V_th = {V_thresh:.3f} V')
- ax.spines['top'].set_visible(False)
- ax.spines['right'].set_visible(False)
- ax.grid(False)
- ax.set_yticks([0, 0.5, 1.0])
- ax.set_xticks([0, 25, 50, 100])
- plt.tight_layout()
- save_str = r'C:\Users\jordan\Documents\combined_figs\evoked_potentials\point_source_modeling\potential_vs_distance_spatialextent.pdf'
- #plt.savefig(save_str)
- # %%
- # %% [markdown]
- # # algorithm for calculating spatial boundaries of evoked potentials (load data from csv of pre-calculated)
- # %%
- ## note this is will only work if you have raw data which is available upon request
- # thus this is mainly demonstrative -- I provide a curated dataframe of the results where all figures were calculated
- from scipy.ndimage import gaussian_filter1d
- pre = -0.3
- post = 5
- snapshots = [1] # ms
- snapshot_idxs = [int(pre / 1000 * 30_000) + int(s / 1000 * 30_000) for s in snapshots]
- snap_alphas = [1]
- data_list = []
- for r in recordings:
- for probe in r.probes:
- if r.mouse in ['jlh33', 'jlh40']: # this fixes the issue with recorded vs geometric probes
- if probe == 'probeB':
- raw_probe = 'probeA'
- elif probe == 'probeC':
- raw_probe = 'probeB'
- else:
- raw_probe = probe
- channels = len(r.probe_coords[probe]) + 20
- for run in r.trials.run.unique():
- fig, axs = plt.subplots(1, 2, figsize=(11, 8))
- stim_times = r.trials[r.trials.run == run].start_time.values
- title = f'{probe}_Run{run} {r.parameters[run]}'
- data = r.raw.get_chunk(raw_probe, stim_times, pre=pre + 3, post=post + 3, chs=np.arange(channels))
- try:
- aligned_data = align_data(data, pre, post, channels, threshold=500, median_subtraction=False)
- except:
- print(f'Error in recording {r.mouse} probe {probe} amp {amp}')
- aligned_data = data
- dists = distances[r.mouse][probe]
- sub_chs = np.arange(len(dists), len(dists) + 15)
- aligned_sub_data = aligned_data - np.median(aligned_data[:,:, sub_chs], axis=2)[:,:, np.newaxis]
- # Use the axs object for the current amp
- raw_heatmap(aligned_sub_data, pre=pre, post=post, dists=dists, vmin=-500, vmax=500,
- save=False, title=f'{r.parameters[run]}', ax=axs[0])
- mean_aligned_sub_data = np.mean(aligned_sub_data, axis=0)
- for snap_idx, (snapshot_time, idx) in enumerate(zip(snapshots, snapshot_idxs)):
- data_snap = aligned_data[:, idx, :]
- data_snap = np.abs(data_snap - np.median(data_snap[:, sub_chs], axis=1)[:, np.newaxis])
- data_snap[:,191] = data_snap[:,192] # ch 191 seems to be the reference?
- # algo for slope detection
- smoothed_gauss = gaussian_filter1d(data_snap, sigma=2, axis=1)
- mean_data = np.mean(data_snap, axis=0)
- mean_gauss = np.mean(smoothed_gauss, axis=0)
- sd_gauss = np.std(smoothed_gauss, axis=0)
- baseline_mean = np.mean(mean_gauss[-15:])
- baseline_std = np.std(mean_gauss[-15:])
- max_idx = np.argmax(mean_gauss[30:])+30
- max_volt = mean_gauss[max_idx]
- max_threshold = 40
- min_voltage = 20
- half_max = max_volt / 2
- if max_volt > max_threshold:
- uppers = mean_gauss[max_idx:]
- lowers = mean_gauss[:max_idx] # doesn't allow bottom 30 channels to be considered
- upper_chs_outside_potential = np.where(uppers < min_voltage)[0]
- if len(upper_chs_outside_potential) > 0:
- upper_bound = (upper_chs_outside_potential[0] - 2) + max_idx #minus two to get channel safely within range
- half_max_upper = np.where(uppers > half_max)[0][-1] + max_idx
- else:
- upper_bound = -1
- half_max_upper = -1
- lower_chs_outside_potential = np.where(lowers < min_voltage)[0]
- if len(lower_chs_outside_potential) > 0:
- lower_bound = lower_chs_outside_potential[-1] + 2 #plus 2 to get channel safely within range
- half_max_lower = np.where(lowers < half_max)[0][-1]
- else:
- lower_bound = -1
- half_max_lower = -1
- else:
- lower_bound, upper_bound = -1,-1
- axs[1].plot(mean_data, np.arange(0,channels), color='k', label='Raw Mean')
- axs[1].plot(mean_gauss, np.arange(0,channels), color='blue', label='Gaussian Mean')
- axs[1].fill_betweenx(np.arange(0,channels), mean_gauss - sd_gauss, mean_gauss + sd_gauss, color='blue', alpha=0.3, label='SD')
- axs[1].set_ylabel('Channel')
- axs[1].set_xlabel('Voltage')
- axs[1].set_title('snapshots')
- axs[1].set_yticks(np.arange(0,channels,10))
- axs[1].legend()
- if max_volt > max_threshold:
- axs[1].axhline(lower_bound, color='cyan', linestyle='--', label='Slope Lower Bound')
- axs[1].axhline(upper_bound, color='cyan', linestyle='--', label='Slope Upper Bound')
- axs[1].axhline(half_max_lower, color = 'orange', linestyle = '--', label = 'Half Max Lower')
- axs[1].axhline(half_max_upper, color = 'orange', linestyle = '--', label = 'Half Max Upper')
- axs[1].axhline(max_idx, color='black', linestyle='--', label='Max')
- axs[0].axhline(lower_bound, color='cyan', linestyle='--')
- axs[0].axhline(upper_bound, color='cyan', linestyle='--')
- axs[0].axhline(half_max_lower, color = 'orange', linestyle = '--')
- axs[0].axhline(half_max_upper, color = 'orange', linestyle = '--',)
- axs[0].axhline(max_idx, color='black', linestyle='--')
- fig.suptitle(title, size=20)
- plt.tight_layout(rect=[0, 0.03, 1, 0.95])
- base_path = os.path.join(EP_path, "ap_potential_w_voltageline")
- sub_folder = os.path.join(base_path, "all_runs_med_sub_spatialdetection", 'abs_volt_threshold_from_max40_min20_halfmax_sigma3', '1ms', r.mouse)
- if not os.path.exists(sub_folder):
- os.makedirs(sub_folder)
- #plt.savefig(os.path.join(sub_folder, f'{r.mouse}_{probe}_run{run}.pdf'))
- #plt.savefig(os.path.join(sub_folder, f'{r.mouse}_{probe}_run{run}.png'))
- plt.close()
- # Append all relevant details to the list
- data_list.append({
- 'recording': r.mouse,
- 'probe': probe,
- 'run': run,
- 'stim_parameters': r.parameters[run],
- 'max_idx': max_idx,
- 'max_volt': max_volt,
- 'half_max': half_max,
- 'half_max_upper_idx': half_max_upper,
- 'half_max_lower_idx': half_max_lower,
- 'lower_bound_idx': lower_bound,
- 'upper_bound_idx': upper_bound,
- 'mean_gauss': mean_gauss.tolist(),
- 'std_gauss': sd_gauss.tolist(),
- 'mean_aligned_sub_data': mean_aligned_sub_data
- })
- # Convert the list to a dataframe
- df_results_w_array = pd.DataFrame(data_list)
- # %%
- # load the results from the EP boundaries algorithm with some intermediaries
- import pickle
- df_results = pd.read_pickle(os.path.join('intermediates', 'EP_boundaries_w_array_distances_curated.pkl'))
- recording_names = ['jlh31', 'jlh32', 'jlh33', 'jlh34', 'jlh39', 'jlh40', 'jlh48', 'jlh49v1', 'jlh53concat']
- recordings = [jlh31, jlh32, jlh33, jlh34, jlh39, jlh40, jlh48, jlh49, jlh53]
- # Create the recording map
- recording_map = {name: recording for name, recording in zip(recording_names, recordings)}
- with open(r'intermediates\mean_aligned_sub_data.pkl', 'rb') as f:
- array_list = pickle.load(f)
- df_results['mean_aligned_sub_data'] = array_list
- # %% [markdown]
- # # average EP heatmaps (Figure 2 e)
- # %%
- # note, requires raw data to run this code, which is available upon request
- from scipy.ndimage import gaussian_filter
- # average fields by polarity and amplitude
- polarities = ['bipolar', 'monopolar']
- fig, axs = plt.subplots(4, 4, figsize=(15, 12)) # 3 rows (probes), 4 columns (amplitudes)
- fig.suptitle(f'1ms aligned spatial response by polarities', fontsize=14)
- probes = ['probeA', 'probeB', 'probeC']
- amps = [-5, -25, -50, -100, 5, 25, 50, 100]
- pad = 400
- mean_array_bycondition = {}
- pre = -0.3
- post = 5
- df_results_w_array = df_results.copy()
- for polarity in polarities:
- for i, amp in enumerate(amps):
- if amp < 0 and polarity == 'bipolar':
- row = 0
- elif amp > 0 and polarity == 'bipolar':
- row = 1
- elif amp < 0 and polarity == 'monopolar':
- row = 2
- elif amp > 0 and polarity == 'monopolar':
- row = 3
- if np.abs(amp) == 5:
- col = 0
- elif np.abs(amp) == 25:
- col = 1
- elif np.abs(amp) == 50:
- col = 2
- elif np.abs(amp) == 100:
- col = 3
- ax = axs[row, col]
- # Filter data for the current probe and amplitude
- df = df_results_w_array[
- (df_results_w_array['max_volt'] > 40) &
- (df_results_w_array['amplitude'] == amp) &
- (df_results_w_array['polarity'] == polarity) &
- (df_results_w_array['half_max_lower_idx'] != -1) &
- (df_results_w_array['half_max_upper_idx'] != -1) &
- (df_results_w_array['lower_bound_idx'] != -1) &
- (df_results_w_array['upper_bound_idx'] != -1)
- ].reset_index()
- # Check if the dataframe is empty
- if df.empty:
- ax.set_title(f'{amp}uA {probe} (No Data)')
- ax.axis('off')
- continue
- combined_array = np.zeros((len(df), df.iloc[0]['mean_aligned_sub_data'].shape[0], 800)) #800 for padded
- for _, dfrow in df.iterrows():
- array = dfrow['mean_aligned_sub_data']
- gauss_array = gaussian_filter1d(array, sigma=1, axis=1)
- ## pad it
- max_idx = dfrow['max_idx']
- upper_gauss = gauss_array[:, max_idx:]
- lower_gauss = gauss_array[:, :max_idx]
- if upper_gauss.shape[1] < pad:
- upper_pad = [np.nan] * (pad - upper_gauss.shape[1])
- padded_upper_gauss = np.zeros((upper_gauss.shape[0], pad))
- for i in range(upper_gauss.shape[0]):
- padded_upper_gauss[i] = np.concatenate([upper_gauss[i], upper_pad])
- if lower_gauss.shape[1] < pad:
- lower_pad = [np.nan] * (pad - lower_gauss.shape[1])
- padded_lower_gauss = np.zeros((lower_gauss.shape[0], pad))
- for i in range(lower_gauss.shape[0]):
- padded_lower_gauss[i] = np.concatenate([lower_pad, lower_gauss[i]])
- padded_gauss = np.concatenate([padded_lower_gauss, padded_upper_gauss], axis=1)
- combined_array[_, :, :] = padded_gauss
- mean_array_bycondition[f'{polarity}_{amp}'] = combined_array #3D n (probe, run, etc) x samples x channels
- data_to_plot = np.nanmean(combined_array, axis=0).T
- data_to_plot = gaussian_filter(data_to_plot, sigma=2)
- #data_to_plot = gaussian_filter1d(data_to_plot, sigma=2, axis=1)
- #data_to_plot = gaussian_filter1d(data_to_plot, sigma = 2, axis=0)
- time_ms = np.linspace(-pre, post, data_to_plot.shape[0])
- ax.imshow(data_to_plot, aspect='auto', vmax=200, vmin=-200, origin='lower', cmap='vlag',
- extent=[time_ms[0], time_ms[-1], 0, data_to_plot.shape[0]])
- ax.set_ylim(300, 500)
- ax.set_title(f'{amp}uA {polarity}')
- ax.set_xlim(-pre, 3)
- if row == 3:
- ax.set_xlabel('Time (ms)')
- else:
- ax.set_xticks([])
- if col == 0:
- ax.set_ylabel('Channel')
- ax.set_yticklabels(np.arange(-100, 101, 50))
- else:
- ax.set_yticks([])
- #path = r'C:\Users\jordan\Documents\combined_figs\evoked_potentials'
- #plt.savefig(os.path.join(path, 'avg_heatmaps_by_polarity_amp_mean_smoothed_3ms_100ch_updated.pdf'))
- #plt.savefig(os.path.join(path, 'avg_heatmaps_by_polarity_amp_mean_smoothed_3ms_100ch_updated.png'))
- # %% [markdown]
- # # average EP at 1 ms across distances (Figure 2f and Supp)
- # %%
- polarities = ['bipolar', 'monopolar']
- fig, axs = plt.subplots(4, 4, figsize=(7, 7)) # 3 rows (probes), 4 columns (amplitudes)
- fig.suptitle(f'1ms aligned spatial response by polarities', fontsize=14)
- probes = ['probeA', 'probeB', 'probeC']
- amps = [-5, -25, -50, -100, 5, 25, 50, 100]
- mean_boundaries_bycondition = {}
- for polarity in polarities:
- for i, amp in enumerate(amps):
- if amp < 0 and polarity == 'bipolar':
- row = 0
- elif amp > 0 and polarity == 'bipolar':
- row = 1
- elif amp < 0 and polarity == 'monopolar':
- row = 2
- elif amp > 0 and polarity == 'monopolar':
- row = 3
- if np.abs(amp) == 5:
- col = 0
- elif np.abs(amp) == 25:
- col = 1
- elif np.abs(amp) == 50:
- col = 2
- elif np.abs(amp) == 100:
- col = 3
- ax = axs[row, col]
- # Filter data for the current probe and amplitude
- df = df_results[
- (df_results['max_volt'] > 40) &
- (df_results['amplitude'] == amp) &
- (df_results['polarity'] == polarity) &
- (df_results['half_max_lower_idx'] != -1) &
- (df_results['half_max_upper_idx'] != -1) &
- (df_results['lower_bound_idx'] != -1) &
- (df_results['upper_bound_idx'] != -1)
- ]
- # Check if the dataframe is empty
- if df.empty:
- ax.set_title(f'{amp}uA {probe} (No Data)')
- ax.axis('off')
- continue
- # Create a matrix of aligned responses
- aligned_matrix = np.array(df['aligned_mean_gauss'].tolist())
- mean_aligned_matrix = np.nanmean(aligned_matrix, axis=0)
- std_aligned_matrix = np.nanstd(aligned_matrix, axis=0)
- sem_aligned_matrix = std_aligned_matrix / np.sqrt(np.sum(~np.isnan(aligned_matrix), axis=0)) # SEM
- normalized_chs = np.arange(-400, 400, 1)
- normalized_distances = np.arange(-4000, 4000, 10)
- upper_bound_idx = int(np.mean(df['upper_bound_idx_aligned'].values))
- lower_bound_idx = int(np.mean(df['lower_bound_idx_aligned'].values))
- half_max_upper_idx = int(np.mean(df['half_max_upper_idx_aligned'].values))
- half_max_lower_idx = int(np.mean(df['half_max_lower_idx_aligned'].values))
- mean_boundaries_bycondition[f'{polarity}_{amp}'] = {
- 'upper_bound': normalized_distances[upper_bound_idx],
- 'lower_bound': normalized_distances[lower_bound_idx],
- 'half_max_upper': normalized_distances[half_max_upper_idx],
- 'half_max_lower': normalized_distances[half_max_lower_idx],
- 'mean_max': mean_aligned_matrix[400],
- 'mean_AUC_fullEP': np.trapz(mean_aligned_matrix[lower_bound_idx:upper_bound_idx]),
- 'mean_AUC_halfEP': np.trapz(mean_aligned_matrix[half_max_lower_idx:half_max_upper_idx])
- }
- ax.axhline(normalized_distances[lower_bound_idx], color='blue', linestyle='--', label='Slope Lower Bound')
- ax.axhline(normalized_distances[upper_bound_idx], color='blue', linestyle='--', label='Slope Upper Bound')
- ax.axhline(normalized_distances[half_max_lower_idx], color = 'orange', linestyle = '--', label = 'Half Max Lower')
- ax.axhline(normalized_distances[half_max_upper_idx], color = 'orange', linestyle = '--', label = 'Half Max Upper')
- # Plot the SEM error cloud
- ax.fill_betweenx(
- normalized_distances,
- mean_aligned_matrix - sem_aligned_matrix,
- mean_aligned_matrix + sem_aligned_matrix,
- color='gray', alpha=0.3, label='SEM'
- )
- # Plot the mean aligned matrix
- ax.plot(mean_aligned_matrix, normalized_distances, color='black', linewidth=2, label='Mean')
- # Set titles and labels
- if row == 0:
- ax.set_title(f'{amp}uA')
- if col == 0:
- ax.set_ylabel(f'{polarity}', fontsize=11)
- else:
- ax.set_yticks([]) # Hide y-ticks for all columns except the first
- if row == 3:
- ax.set_xlabel('uV', fontsize=8)
- else:
- ax.set_xticks([]) # Hide x-ticks for all rows except the last
- # Add legend only to the last subplot
- if row == 3 and col == 3:
- ax.legend(loc='upper right', fontsize=8)
- ax.set_xlim([0, 800])
- ax.set_ylim([-1500, 1500])
- plt.tight_layout()
- path = os.path.join(r'C:\Users\jordan\Documents\combined_figs\evoked_potentials')
- #plt.savefig(os.path.join(path, 'spatial_response_by_polarity.png'))
- #plt.savefig(os.path.join(path, 'spatial_response_by_polarity.pdf'))
- # %%
- mean_boundaries_bycondition = {}
- fig, axs = plt.subplots(1, 3, figsize=(8, 3)) # 2 rows for polarities, 2 columns for amplitude sign
- fig.suptitle('1ms aligned spatial response by polarities', fontsize=12)
- colors = [cathodal_color, anodal_color, bipolar_color]
- amps = [-5, -25, -50, -100]
- for i, pol in enumerate(['cathodal', 'anodal', 'bipolar']):
- ax = axs[i]
- if pol == 'bipolar':
- polarity = 'bipolar'
- else:
- polarity = 'monopolar'
- for k, amp in enumerate(amps):
- if pol == 'anodal':
- amp = np.abs(amp) # Make positive for anodal
- df = df_results[
- (df_results['max_volt'] > 40) &
- (df_results['amplitude'] == amp) &
- (df_results['polarity'] == polarity) &
- (df_results['half_max_lower_idx'] != -1) &
- (df_results['half_max_upper_idx'] != -1) &
- (df_results['lower_bound_idx'] != -1) &
- (df_results['upper_bound_idx'] != -1)
- ]
- if df.empty:
- print('skipping empty dataframe for', amp, pol)
- continue # Skip this iteration if the dataframe is empty
- aligned_matrix = np.array(df['aligned_mean_gauss'].tolist())
- mean_aligned_matrix = np.nanmean(aligned_matrix, axis=0)
- sem_aligned_matrix = np.nanstd(aligned_matrix, axis=0) / np.sqrt(np.sum(~np.isnan(aligned_matrix), axis=0))
- normalized_distances = np.arange(-4000, 4000, 10)
- # Plot SEM error cloud
- ax.fill_betweenx(
- normalized_distances,
- mean_aligned_matrix - sem_aligned_matrix,
- mean_aligned_matrix + sem_aligned_matrix,
- color='gray', alpha=0.2 # Use different colors for each amplitude
- )
- # Plot the mean response
- ax.plot(mean_aligned_matrix, normalized_distances, label=f'{amp}uA', color=colors[i], alpha = 0.3 + (k * 0.2))
- ax.set_xlim([0, 800])
- ax.set_ylim([-1000, 1000])
- ax.set_title(f'{pol}')
- ax.set_xlabel('uV')
- ax.set_ylabel('Distance (um)')
- ax.legend(loc='upper right', fontsize=8)
- # Adjust layout and save figures
- plt.tight_layout()
- #path = r'C:\Users\jordan\Documents\combined_figs\evoked_potentials'
- #plt.savefig(os.path.join(path, 'spatial_response_by_polarity_and_sign_100ch.png'))
- #plt.savefig(os.path.join(path, 'spatial_response_by_polarity_and_sign_100ch.pdf'))
- # %% [markdown]
- # # spatial extents superficial and deep (Figure 2g and Supp CCF vs peak-aligned)
- # %%
- # ccf distances and peak aligned distances plots
- from scipy.stats import sem
- from scipy.stats import linregress
- for dist_type in ['ccf', 'peak_aligned']:
- fig, axs = plt.subplots(1, 3, figsize=(8, 3)) # 2 rows for polarities, 2 columns for amplitude sign
- fig.suptitle(f'{dist_type} distances 1D spatial bounds (up vs down) w fits', fontsize=12)
- colors = [cathodal_color, anodal_color, bipolar_color]
- amps = [-5, -25, -50, -100]
- for i, pol in enumerate(['cathodal', 'anodal', 'bipolar']):
- ax = axs[i]
- ax.set_title(pol, color = colors[i])
- if pol == 'bipolar':
- polarity = 'bipolar'
- else:
- polarity = 'monopolar'
- all_upper_dists = []
- all_lower_dists = []
- all_amps = []
- for k, amp in enumerate(amps):
- if pol == 'anodal':
- amp = np.abs(amp) # Make positive for anodal
- ax.set_xlabel('Amplitude (uA)')
- ax.set_ylabel('Distance (units)')
- ax.invert_yaxis()
- df = df_results[
- (df_results['max_volt'] > 40) &
- (df_results['amplitude'] == amp) &
- (df_results['polarity'] == polarity) &
- (df_results['half_max_lower_idx'] != -1) &
- (df_results['half_max_upper_idx'] != -1) &
- (df_results['lower_bound_idx'] != -1) &
- (df_results['upper_bound_idx'] != -1)
- ]
- if dist_type == 'ccf':
- upper_dists = df['upper_bound_dist'].values
- lower_dists = df['lower_bound_dist'].values
- elif dist_type == 'peak_aligned':
- normalized_distances = np.arange(-4000, 4000, 10)[::-1]
- upper_bound_idxs = df['upper_bound_idx_aligned'].values
- lower_bound_idxs = df['lower_bound_idx_aligned'].values
- upper_dists = [normalized_distances[idx] for idx in upper_bound_idxs]
- lower_dists = [normalized_distances[idx] for idx in lower_bound_idxs]
- # Collect data for scatter plot
- all_upper_dists.extend(upper_dists)
- all_lower_dists.extend(lower_dists)
- all_amps.extend([amp] * len(upper_dists))
- # Scatter individual data points
- ax.scatter([amp] * len(upper_dists), upper_dists, color=colors[i], s = 3, alpha=0.8, zorder=2)
- ax.scatter([amp] * len(lower_dists), lower_dists, color=colors[i], s = 3, alpha=0.8, zorder=2)
- # print mean and std of upper and lower distances
- mean_upper = np.mean(all_upper_dists)
- mean_lower = np.mean(all_lower_dists)
- std_upper = np.std(all_upper_dists)
- std_lower = np.std(all_lower_dists)
- print(f'{pol} {dist_type} {amp}uA: Upper Mean: {mean_upper:.2f}, Std: {std_upper:.2f}; Lower Mean: {mean_lower:.2f}, Std: {std_lower:.2f}')
- # Fit polynomial regression to the data
- if len(all_amps) > 1: # Ensure we have enough data points
- upper_coeffs = np.polyfit(all_amps, all_upper_dists, deg=2)
- lower_coeffs = np.polyfit(all_amps, all_lower_dists, deg=2)
- upper_poly = np.poly1d(upper_coeffs)
- lower_poly = np.poly1d(lower_coeffs)
- # Generate smooth curves for plotting
- amp_range = np.linspace(np.min(all_amps), np.max(all_amps), 100)
- upper_fit = upper_poly(amp_range)
- lower_fit = lower_poly(amp_range)
- # Calculate standard error of the mean (SEM) for the fits
- upper_fit_err = sem(all_upper_dists)
- lower_fit_err = sem(all_lower_dists)
- # Plot polynomial regression lines
- ax.plot(amp_range, upper_fit, color='darkblue', label='Upper Bound Fit', zorder=4)
- ax.plot(amp_range, lower_fit, color='darkred', label='Lower Bound Fit', zorder=4)
- # Add error bands
- ax.fill_between(amp_range, upper_fit - upper_fit_err, upper_fit + upper_fit_err, color='blue', alpha=0.2, label='Upper Fit Error', zorder=1)
- ax.fill_between(amp_range, lower_fit - lower_fit_err, lower_fit + lower_fit_err, color='red', alpha=0.2, label='Lower Fit Error', zorder=1)
- x = all_amps
- y_upper = all_upper_dists
- y_lower = all_lower_dists
- # Linear fit (Upper)
- slope_u, intercept_u, r_u, pval_u, stderr_u = linregress(x, y_upper)
- r2_linear_upper = r_u ** 2
- print(f"Linear Fit (Upper): R² = {r2_linear_upper:.2f}, Slope = {slope_u:.2f}, Intercept = {intercept_u:.2f}, p = {pval_u:.2e}")
- # Quadratic fit (Upper)
- coeffs_quad_upper = np.polyfit(x, y_upper, 2)
- p_quad_upper = np.poly1d(coeffs_quad_upper)
- y_fit_quad_upper = p_quad_upper(x)
- r2_quad_upper = 1 - (np.sum((y_upper - y_fit_quad_upper)**2) / np.sum((y_upper - np.mean(y_upper))**2))
- print(f"Quadratic Fit (Upper): R² = {r2_quad_upper:.2f}, Coeffs = {coeffs_quad_upper}")
- # Linear fit (Lower)
- slope_l, intercept_l, r_l, pval_l, stderr_l = linregress(x, y_lower)
- r2_linear_lower = r_l ** 2
- print(f"Linear Fit (Lower): R² = {r2_linear_lower:.2f}, Slope = {slope_l:.2f}, Intercept = {intercept_l:.2f}, p = {pval_l:.2e}")
- # Quadratic fit (Lower)
- coeffs_quad_lower = np.polyfit(x, y_lower, 2)
- p_quad_lower = np.poly1d(coeffs_quad_lower)
- y_fit_quad_lower = p_quad_lower(x)
- r2_quad_lower = 1 - (np.sum((y_lower - y_fit_quad_lower)**2) / np.sum((y_lower - np.mean(y_lower))**2))
- print(f"Quadratic Fit (Lower): R² = {r2_quad_lower:.2f}, Coeffs = {coeffs_quad_lower}")
- # Add color-coded text annotations for R² values
- ax.text(
- 0.6, 0.90,
- f"Quad: R²: {r2_quad_upper:.2f}",
- color='blue', fontsize=5, verticalalignment='top', transform=ax.transAxes
- )
- ax.text(
- 0.6, 0.80,
- f"Lin: R²: {r2_linear_upper:.2f}",
- color='darkblue', fontsize=5, verticalalignment='top', transform=ax.transAxes
- )
- ax.text(
- 0.6, 0.10,
- f"Quad: R²: {r2_quad_lower:.2f}",
- color='red', fontsize=5, verticalalignment='top', transform=ax.transAxes)
- ax.text(
- 0.6, 0.20,
- f"Lin: R²: {r2_linear_lower:.2f}",
- color='darkred', fontsize=5, verticalalignment='top', transform=ax.transAxes)
- if pol == 'anodal':
- ax.set_xticks(np.abs(amps))
- else:
- ax.set_xticks(amps)
- ax.invert_xaxis() # Invert x-axis for negative amplitudes
- ax.invert_yaxis()
- # Adjust layout and save the figure
- plt.tight_layout(rect=[0, 0, 1, 0.96])
- path = r'C:\Users\jordan\Documents\combined_figs\evoked_potentials'
- #plt.savefig(os.path.join(path, f'distances_{dist_type}_with_fit_updated.png'))
- #plt.savefig(os.path.join(path, f'distances_{dist_type}_with_fit_updated.pdf'))
- # %%
- # ccf distances and peak aligned distances plots
- from scipy.stats import sem, linregress
- for dist_type in ['ccf', 'peak_aligned']:
- fig, axs = plt.subplots(1, 3, figsize=(8, 3))
- fig.suptitle(f'{dist_type} distances 1D spatial bounds (up vs down) — linear fits', fontsize=12)
- colors = [cathodal_color, anodal_color, bipolar_color]
- amps = [-5, -25, -50, -100]
- for i, pol in enumerate(['cathodal', 'anodal', 'bipolar']):
- ax = axs[i]
- ax.set_title(pol, color=colors[i])
- polarity = 'bipolar' if pol == 'bipolar' else 'monopolar'
- all_upper_dists = []
- all_lower_dists = []
- all_amps = []
- mean_upper_per_amp = []
- mean_lower_per_amp = []
- sem_upper_per_amp = []
- sem_lower_per_amp = []
- for k, amp in enumerate(amps):
- if pol == 'anodal':
- amp = np.abs(amp)
- df = df_results[
- (df_results['max_volt'] > 40) &
- (df_results['amplitude'] == amp) &
- (df_results['polarity'] == polarity) &
- (df_results['half_max_lower_idx'] != -1) &
- (df_results['half_max_upper_idx'] != -1) &
- (df_results['lower_bound_idx'] != -1) &
- (df_results['upper_bound_idx'] != -1)
- ]
- if dist_type == 'ccf':
- upper_dists = df['upper_bound_dist'].values
- lower_dists = df['lower_bound_dist'].values
- else:
- normalized_distances = np.arange(-4000, 4000, 10)[::-1]
- upper_dists = [normalized_distances[idx] for idx in df['upper_bound_idx_aligned'].values]
- lower_dists = [normalized_distances[idx] for idx in df['lower_bound_idx_aligned'].values]
- # scatter points
- ax.scatter([amp] * len(upper_dists), upper_dists, color=colors[i], s=3, alpha=0.6, zorder=2)
- ax.scatter([amp] * len(lower_dists), lower_dists, color=colors[i], s=3, alpha=0.6, zorder=2)
- # accumulate
- all_upper_dists.extend(upper_dists)
- all_lower_dists.extend(lower_dists)
- all_amps.extend([amp] * len(upper_dists))
- # store mean ± sem for plotting mean dots
- mean_upper_per_amp.append(np.mean(upper_dists))
- mean_lower_per_amp.append(np.mean(lower_dists))
- sem_upper_per_amp.append(sem(upper_dists))
- sem_lower_per_amp.append(sem(lower_dists))
- print(f'{pol} {dist_type} {amp}uA: upper mean={np.mean(upper_dists):.2f}, lower mean={np.mean(lower_dists):.2f}')
- # plot means with error bars
- ax.errorbar(amps if pol != 'anodal' else np.abs(amps),
- mean_upper_per_amp, yerr=sem_upper_per_amp,
- fmt='o', color='lightgray', markersize=4, label='upper mean', zorder=4)
- ax.errorbar(amps if pol != 'anodal' else np.abs(amps),
- mean_lower_per_amp, yerr=sem_lower_per_amp,
- fmt='o', color='black', markersize=4, label='lower mean', zorder=4)
- # linear fits
- if len(all_amps) > 1:
- x = np.asarray(all_amps)
- y_upper = np.asarray(all_upper_dists)
- y_lower = np.asarray(all_lower_dists)
- # linear regressions
- slope_u, intercept_u, r_u, p_u, _ = linregress(x, y_upper)
- slope_l, intercept_l, r_l, p_l, _ = linregress(x, y_lower)
- r2_u = r_u ** 2
- r2_l = r_l ** 2
- x_line = np.linspace(np.min(x), np.max(x), 100)
- ax.plot(x_line, slope_u * x_line + intercept_u, color='lightgray', linewidth=1.2)
- ax.plot(x_line, slope_l * x_line + intercept_l, color='black', linewidth=1.2)
- print(f"{pol} upper linear: R²={r2_u:.2f}, slope={slope_u:.2f}, p={p_u:.2e}")
- print(f"{pol} lower linear: R²={r2_l:.2f}, slope={slope_l:.2f}, p={p_l:.2e}")
- # annotate
- ax.text(0.60, 0.85, f"Upper R²={r2_u:.2f}", color='lightgray', fontsize=6, va='top', transform=ax.transAxes)
- ax.text(0.60, 0.20, f"Lower R²={r2_l:.2f}", color='black', fontsize=6, va='top', transform=ax.transAxes)
- # aesthetics
- if pol == 'anodal':
- ax.set_xticks(np.abs(amps))
- else:
- ax.set_xticks(amps)
- ax.invert_xaxis()
- ax.set_xlabel('amplitude (uA)')
- ax.set_ylabel('boundary distance')
- ax.spines['top'].set_visible(False)
- ax.spines['right'].set_visible(False)
- ax.invert_yaxis()
- ax.set_ylim([1500, -1500])
- ax.set_yticks([1500, 1000, 500, 0, -500, -1000, -1500], ['1500', '', '', '0', '', '', '-1500'])
- plt.tight_layout(rect=[0, 0, 1, 0.96])
- path = r'C:\Users\jordan\Documents\combined_figs\evoked_potentials'
- # plt.savefig(os.path.join(path, f'distances_{dist_type}_linear_means.png'))
- # plt.savefig(os.path.join(path, f'distances_{dist_type}_linear_means.pdf'))
- # %%
- # full version: signed means ± SEM (clouds) and absolute linear fits (offset scatters)
- from scipy.stats import sem, linregress
- for dist_type in ['ccf', 'peak_aligned']:
- colors = [cathodal_color, anodal_color, bipolar_color]
- amps_base = [-5, -25, -50, -100]
- # ========== PLOT 1: signed means ± SEM as error "clouds" with individual points ==========
- fig1, axs1 = plt.subplots(1, 3, figsize=(8, 3))
- fig1.suptitle(f'{dist_type} — signed boundary distances (mean ± SEM clouds)', fontsize=12)
- for i, pol in enumerate(['cathodal', 'anodal', 'bipolar']):
- ax = axs1[i]
- ax.set_title(pol, color=colors[i])
- polarity = 'bipolar' if pol == 'bipolar' else 'monopolar'
- amps = np.abs(amps_base) if pol == 'anodal' else amps_base
- mean_upper, mean_lower = [], []
- sem_upper, sem_lower = [], []
- for amp_in in amps_base:
- amp = abs(amp_in) if pol == 'anodal' else amp_in
- df = df_results[
- (df_results['max_volt'] > 40) &
- (df_results['amplitude'] == amp) &
- (df_results['polarity'] == polarity) &
- (df_results['half_max_lower_idx'] != -1) &
- (df_results['half_max_upper_idx'] != -1) &
- (df_results['lower_bound_idx'] != -1) &
- (df_results['upper_bound_idx'] != -1)
- ]
- if dist_type == 'ccf':
- upper_dists = df['upper_bound_dist'].values
- lower_dists = df['lower_bound_dist'].values
- else:
- normalized_distances = np.arange(-4000, 4000, 10)[::-1]
- upper_dists = [normalized_distances[idx] for idx in df['upper_bound_idx_aligned'].values]
- lower_dists = [normalized_distances[idx] for idx in df['lower_bound_idx_aligned'].values]
- ax.scatter([amp]*len(upper_dists), upper_dists, color=colors[i], s=4, alpha=0.4, zorder=1)
- ax.scatter([amp]*len(lower_dists), lower_dists, color=colors[i], s=4, alpha=0.4, zorder=1)
- mean_upper.append(np.mean(upper_dists))
- mean_lower.append(np.mean(lower_dists))
- sem_upper.append(sem(upper_dists))
- sem_lower.append(sem(lower_dists))
- # shaded SEM "clouds"
- ax.plot(amps, mean_upper, color='gray', lw=1.2)
- ax.fill_between(amps,
- np.array(mean_upper)-np.array(sem_upper),
- np.array(mean_upper)+np.array(sem_upper),
- color='gray', alpha=0.25, label='upper ± sem')
- ax.plot(amps, mean_lower, color='black', lw=1.2)
- ax.fill_between(amps,
- np.array(mean_lower)-np.array(sem_lower),
- np.array(mean_lower)+np.array(sem_lower),
- color='black', alpha=0.25, label='lower ± sem')
- # formatting
- if pol == 'anodal':
- ax.set_xticks(np.abs(amps_base))
- else:
- ax.set_xticks(amps_base)
- ax.invert_xaxis()
- ax.set_xlabel('amplitude (µA)')
- if i == 0:
- ax.set_ylabel('boundary distance (µm)')
- ax.spines['top'].set_visible(False)
- ax.spines['right'].set_visible(False)
- ax.invert_yaxis()
- ax.set_ylim([1500, -1500])
- ax.set_yticks([1500, 1000, 500, 0, -500, -1000, -1500],
- ['1500', '', '', '0', '', '', '-1500'])
- if i == 0:
- ax.legend(frameon=False, fontsize=7, loc='lower left')
- plt.tight_layout(rect=[0, 0, 1, 0.95])
- #plt.savefig(os.path.join(path, f'{dist_type}_means_sem_cloud.png'))
- #plt.savefig(os.path.join(path, f'{dist_type}_means_sem_cloud.pdf'))
- # ========== PLOT 2: absolute distances with linear fits + offset scatters ==========
- fig2, axs2 = plt.subplots(1, 3, figsize=(8, 3))
- fig2.suptitle(f'{dist_type} — absolute boundary distances (linear fits + offset scatters)', fontsize=12)
- offset = 3.0 # µA offset between upper and lower scatters
- for i, pol in enumerate(['cathodal', 'anodal', 'bipolar']):
- ax = axs2[i]
- ax.set_title(pol, color=colors[i])
- polarity = 'bipolar' if pol == 'bipolar' else 'monopolar'
- amps = np.abs(amps_base) if pol == 'anodal' else amps_base
- x_upper, y_upper = [], []
- x_lower, y_lower = [], []
- for amp_in in amps_base:
- amp = abs(amp_in) if pol == 'anodal' else amp_in
- df = df_results[
- (df_results['max_volt'] > 40) &
- (df_results['amplitude'] == amp) &
- (df_results['polarity'] == polarity) &
- (df_results['half_max_lower_idx'] != -1) &
- (df_results['half_max_upper_idx'] != -1) &
- (df_results['lower_bound_idx'] != -1) &
- (df_results['upper_bound_idx'] != -1)
- ]
- if dist_type == 'ccf':
- upper_dists = df['upper_bound_dist'].values
- lower_dists = df['lower_bound_dist'].values
- else:
- normalized_distances = np.arange(-4000, 4000, 10)[::-1]
- upper_dists = [normalized_distances[idx] for idx in df['upper_bound_idx_aligned'].values]
- lower_dists = [normalized_distances[idx] for idx in df['lower_bound_idx_aligned'].values]
- au = np.abs(upper_dists)
- al = np.abs(lower_dists)
- x_upper.extend([amp]*len(au))
- y_upper.extend(au)
- x_lower.extend([amp]*len(al))
- y_lower.extend(al)
- # offset scatter positions
- ax.scatter(np.array([amp]*len(au)) + offset, au,
- marker='o', s=24, color='gray', alpha=0.6, edgecolor='none', zorder=2, label=None)
- ax.scatter(np.array([amp]*len(al)) - offset, al,
- marker='s', s=24, color='black', alpha=0.6, edgecolor='none', zorder=2, label=None)
- # regression fits
- if len(x_upper) > 1 and len(x_lower) > 1:
- xu, yu = np.array(x_upper, float), np.array(y_upper, float)
- xl, yl = np.array(x_lower, float), np.array(y_lower, float)
- su, bu, ru, pu, _ = linregress(xu, yu)
- sl, bl, rl, pl, _ = linregress(xl, yl)
- x_line = np.linspace(np.min(amps), np.max(amps), 100)
- ax.plot(x_line, sl * x_line + bl, color='black', lw=1.5, ls='-', label='|lower| fit')
- ax.plot(x_line, su * x_line + bu, color='gray', lw=1.5, ls='--', label='|upper| fit')
- print(f"{pol} {dist_type}: upper R²={ru**2:.2f}, lower R²={rl**2:.2f}")
- if pol == 'anodal':
- ax.set_xticks(np.abs(amps_base))
- else:
- ax.set_xticks(amps_base)
- ax.invert_xaxis()
- ax.set_xlabel('amplitude (µA)')
- if i == 0:
- ax.set_ylabel('absolute boundary distance (µm)')
- ax.spines['top'].set_visible(False)
- ax.spines['right'].set_visible(False)
- ax.set_ylim(0, 1500)
- ax.set_yticks([0, 500, 1000, 1500])
- if i == 0:
- ax.legend(frameon=False, fontsize=7, loc='upper left')
- plt.tight_layout(rect=[0, 0, 1, 0.95])
- path = r'C:\Users\jordan\Documents\combined_figs\evoked_potentials'
- #plt.savefig(os.path.join(path, f'{dist_type}_abs_linear_fit_offset.png'))
- #plt.savefig(os.path.join(path, f'{dist_type}_abs_linear_fit_offset.pdf'))
- # %%
- # config
- dist_type = 'ccf' # 'ccf' or 'peak_aligned'
- title = f'50 µA radius across pols'
- # helper: pull combined absolute distances (upper + lower) at 50 µA for one polarity
- def _pull_combined_50(df_results, dist_type, pol_name):
- # map to df polarity/amp
- if pol_name == 'bipolar':
- polarity = 'bipolar'; amp = -50
- elif pol_name == 'anodal':
- polarity = 'monopolar'; amp = 50
- else:
- polarity = 'monopolar'; amp = -50 # cathodal
- df = df_results[
- (df_results['max_volt'] > 40) &
- (df_results['amplitude'] == amp) &
- (df_results['polarity'] == polarity) &
- (df_results['half_max_lower_idx'] != -1) &
- (df_results['half_max_upper_idx'] != -1) &
- (df_results['lower_bound_idx'] != -1) &
- (df_results['upper_bound_idx'] != -1)
- ].copy()
- # get distances per side
- if dist_type == 'ccf':
- upper = df['upper_bound_dist'].to_numpy(dtype=float)
- lower = df['lower_bound_dist'].to_numpy(dtype=float)
- else: # 'peak_aligned'
- # aligned distances are in 10 µm steps from -4000..+3990 reversed
- normalized_distances = np.arange(-4000, 4000, 10)[::-1]
- upper = np.array([normalized_distances[idx] for idx in df['upper_bound_idx_aligned'].to_numpy()], dtype=float)
- lower = np.array([normalized_distances[idx] for idx in df['lower_bound_idx_aligned'].to_numpy()], dtype=float)
- # combine as magnitudes; n doubles (upper+lower treated as separate observations)
- pooled = np.concatenate([np.abs(upper), np.abs(lower)])
- return pooled
- # build tidy df
- rows = []
- for pol in ['cathodal', 'anodal', 'bipolar']:
- vals = _pull_combined_50(df_results, dist_type, pol)
- rows += [{'polarity': pol, 'dist_abs': v} for v in vals]
- plot_df = pd.DataFrame(rows)
- # plot: strip + mean±sem in your stim colors
- fig, ax = plt.subplots(figsize=(2, 3.0))
- ax.set_title(title, fontsize=9)
- ax.set_xlabel('polarity (50 µA overall)')
- ax.set_ylabel('distance (µm)')
- for pol, col in [('cathodal', cathodal_color), ('anodal', anodal_color), ('bipolar', bipolar_color)]:
- sub = plot_df[plot_df['polarity'] == pol]
- sns.stripplot(
- data=sub, x='polarity', y='dist_abs',
- jitter=0.25, alpha=0.65, size=3,
- color=col, ax=ax
- )
- # overlay mean ± sem
- g = plot_df.groupby('polarity')['dist_abs']
- xcats = ['cathodal', 'anodal', 'bipolar']
- xlocs = np.arange(len(xcats))
- means = [g.get_group(cat).mean() for cat in xcats]
- sems = [g.get_group(cat).sem() for cat in xcats]
- for xi, cat, m, s in zip(xlocs, xcats, means, sems):
- ax.errorbar(
- xi, m, yerr=s, fmt='D-', lw=1.2, ms=5,
- color={'cathodal': cathodal_color, 'anodal': anodal_color, 'bipolar': bipolar_color}[cat],
- capsize=3, zorder=3
- )
- ax.set_yticks([0, 1000, 2000])
- # styling
- ax.spines['top'].set_visible(False)
- ax.spines['right'].set_visible(False)
- # optional fixed ylim for comparability:
- # ax.set_ylim(0, 1500)
- plt.tight_layout()
- path = r'C:\Users\jordan\Documents\combined_figs\evoked_potentials'
- #plt.savefig(os.path.join(path, f'ccf_dist_50ua_avgdist.png'))
- #plt.savefig(os.path.join(path, f'ccf_dist_50ua_avgdist.pdf'))
- # %%
- import pandas as pd
- import numpy as np
- from statsmodels.formula.api import ols
- import statsmodels.api as sm
- # Filter for valid ccf entries
- df_ccf = df_results[
- (df_results['max_volt'] > 40) &
- (df_results['half_max_lower_idx'] != -1) &
- (df_results['half_max_upper_idx'] != -1) &
- (df_results['lower_bound_idx'] != -1) &
- (df_results['upper_bound_idx'] != -1) &
- (df_results['amplitude'] != 10) &
- (df_results['amplitude'] != -10) # Exclude 10 uA
- ]
- # Build long-form dataframe
- records = []
- for _, row in df_ccf.iterrows():
- for bound_type, dist_col in zip(['upper', 'lower'], ['upper_bound_dist', 'lower_bound_dist']):
- records.append({
- 'distance': row[dist_col],
- 'amplitude': np.abs(row['amplitude']),
- 'polarity': (
- 'bipolar' if row['polarity'] == 'bipolar'
- else 'anodal' if row['amplitude'] > 0
- else 'cathodal'
- ),
- 'bound_type': bound_type
- })
- df_long = pd.DataFrame(records)
- df_long['amplitude'] = df_long['amplitude'].astype('category')
- df_long['polarity'] = df_long['polarity'].astype('category')
- df_long['bound_type'] = df_long['bound_type'].astype('category')
- for bound in ['upper', 'lower']:
- print(f"\n---- {bound.upper()} BOUND ----")
- df_bound = df_long[df_long['bound_type'] == bound]
- model = ols('distance ~ C(polarity) * amplitude', data=df_bound).fit()
- anova_table = sm.stats.anova_lm(model, typ=2)
- print(anova_table)
- model = ols('distance ~ C(polarity) * amplitude * C(bound_type)', data=df_long).fit()
- anova_table = sm.stats.anova_lm(model, typ=2)
- print(anova_table)
- # %%
- # Treat amplitude as continuous — make sure it's numeric
- df_cathodal['amplitude'] = df_cathodal['amplitude'].astype(float)
- # Run model with continuous amplitude and interaction with bound_type
- model = ols('distance ~ amplitude * C(bound_type)', data=df_cathodal).fit()
- anova_table = sm.stats.anova_lm(model, typ=2)
- print(anova_table)
- # %% [markdown]
- # # half spatial extents superficial and deep (Figure 2g and Supp CCF vs peak-aligned)
- # %%
- # ccf distances and peak aligned distances plots
- from scipy.stats import sem
- for dist_type in ['ccf', 'peak_aligned']:
- fig, axs = plt.subplots(1, 3, figsize=(8, 3)) # 2 rows for polarities, 2 columns for amplitude sign
- fig.suptitle(f'{dist_type} distances 1D half spatial bounds (up vs down) w fits', fontsize=12)
- colors = [cathodal_color, anodal_color, bipolar_color]
- for i, pol in enumerate(['cathodal', 'anodal', 'bipolar']):
- ax = axs[i]
- ax.set_title(pol, color = colors[i])
- if pol == 'bipolar':
- polarity = 'bipolar'
- else:
- polarity = 'monopolar'
- all_upper_dists = []
- all_lower_dists = []
- all_amps = []
- all_mean_uppers = []
- all_mean_lowers = []
- all_std_uppers = []
- all_std_lowers = []
- all_sem_uppers = []
- all_sem_lowers = []
- amps = [-5, -25, -50, -100]
- for k, amp in enumerate(amps):
- if pol == 'anodal':
- amp = np.abs(amp) # Make positive for anodal
- ax.set_xlabel('Amplitude (uA)')
- ax.set_ylabel('Distance (units)')
- #ax.invert_yaxis()
- df = df_results[
- (df_results['max_volt'] > 40) &
- (df_results['amplitude'] == amp) &
- (df_results['polarity'] == polarity) &
- (df_results['half_max_lower_idx'] != -1) &
- (df_results['half_max_upper_idx'] != -1) &
- (df_results['lower_bound_idx'] != -1) &
- (df_results['upper_bound_idx'] != -1)
- ]
- if dist_type == 'ccf':
- upper_dists = df['half_max_upper_dist'].values
- lower_dists = df['half_max_lower_dist'].values
- elif dist_type == 'peak_aligned':
- normalized_distances = np.arange(-4000, 4000, 10)[::-1]
- upper_bound_idxs = df['half_max_upper_idx_aligned'].values
- lower_bound_idxs = df['half_max_lower_idx_aligned'].values
- upper_dists = [normalized_distances[idx] for idx in upper_bound_idxs]
- lower_dists = [normalized_distances[idx] for idx in lower_bound_idxs]
- # Collect data for scatter plot
- all_upper_dists.extend(upper_dists)
- all_lower_dists.extend(lower_dists)
- all_amps.extend([amp] * len(upper_dists))
- # Scatter individual data points
- ax.scatter([amp] * len(upper_dists), upper_dists, color=colors[i], s = 3, alpha=0.8, zorder=2)
- ax.scatter([amp] * len(lower_dists), lower_dists, color=colors[i], s = 3, alpha=0.8, zorder=2)
- # print mean and std of upper and lower distances
- mean_upper = np.mean(all_upper_dists)
- mean_lower = np.mean(all_lower_dists)
- std_upper = np.std(all_upper_dists)
- std_lower = np.std(all_lower_dists)
- sem_upper = sem(all_upper_dists)
- sem_lower = sem(all_lower_dists)
- all_mean_uppers.append(mean_upper)
- all_mean_lowers.append(mean_lower)
- all_std_uppers.append(std_upper)
- all_std_lowers.append(std_lower)
- all_sem_uppers.append(sem_upper)
- all_sem_lowers.append(sem_lower)
- print(f'{pol} {dist_type} {amp}uA: Upper Mean: {mean_upper:.2f}, Std: {std_upper:.2f}; Lower Mean: {mean_lower:.2f}, Std: {std_lower:.2f}')
- if pol == 'anodal':
- amps = [5, 25, 50, 100]
- else:
- amps = [-5, -25, -50, -100]
- ax.plot(amps, all_mean_uppers, color = colors[i], marker = 'o', markersize = 4, linewidth = 1)
- ax.plot(amps, all_mean_lowers, color = colors[i], marker = 'o', markersize = 4, linewidth = 1)
- ax.fill_between(amps,
- np.array(all_mean_uppers) - np.array(all_sem_uppers),
- np.array(all_mean_uppers) + np.array(all_sem_uppers),
- color=colors[i], alpha=0.2, label='Upper Bound SEM', zorder=1)
- ax.fill_between(amps,
- np.array(all_mean_lowers) - np.array(all_sem_lowers),
- np.array(all_mean_lowers) + np.array(all_sem_lowers),
- color=colors[i], alpha=0.2, label='Lower Bound SEM', zorder=1)
- # Fit polynomial regression to the data
- if len(all_amps) > 1: # Ensure we have enough data points
- upper_coeffs = np.polyfit(all_amps, all_upper_dists, deg=2)
- lower_coeffs = np.polyfit(all_amps, all_lower_dists, deg=2)
- upper_poly = np.poly1d(upper_coeffs)
- lower_poly = np.poly1d(lower_coeffs)
- # Generate smooth curves for plotting
- amp_range = np.linspace(np.min(all_amps), np.max(all_amps), 100)
- upper_fit = upper_poly(amp_range)
- lower_fit = lower_poly(amp_range)
- # Calculate standard error of the mean (SEM) for the fits
- upper_fit_err = sem(all_upper_dists)
- lower_fit_err = sem(all_lower_dists)
- # Plot polynomial regression lines
- #ax.plot(amp_range, upper_fit, color='darkblue', label='Upper Bound Fit', zorder=4)
- #ax.plot(amp_range, lower_fit, color='darkred', label='Lower Bound Fit', zorder=4)
- # Add error bands
- #ax.fill_between(amp_range, upper_fit - upper_fit_err, upper_fit + upper_fit_err, color='blue', alpha=0.2, label='Upper Fit Error', zorder=1)
- #ax.fill_between(amp_range, lower_fit - lower_fit_err, lower_fit + lower_fit_err, color='red', alpha=0.2, label='Lower Fit Error', zorder=1)
- x = all_amps
- y_upper = all_upper_dists
- y_lower = all_lower_dists
- # Fit linear regression for upper bounds
- coeffs_linear_upper = np.polyfit(x, y_upper, 1)
- p_linear_upper = np.poly1d(coeffs_linear_upper)
- y_fit_linear_upper = p_linear_upper(x)
- r2_linear_upper = 1 - (np.sum((y_upper - y_fit_linear_upper)**2) / np.sum((y_upper - np.mean(y_upper))**2))
- # Fit quadratic regression for upper bounds
- coeffs_quad_upper = np.polyfit(x, y_upper, 2)
- p_quad_upper = np.poly1d(coeffs_quad_upper)
- y_fit_quad_upper = p_quad_upper(x)
- r2_quad_upper = 1 - (np.sum((y_upper - y_fit_quad_upper)**2) / np.sum((y_upper - np.mean(y_upper))**2))
- # Fit linear regression for lower bounds
- coeffs_linear_lower = np.polyfit(x, y_lower, 1)
- p_linear_lower = np.poly1d(coeffs_linear_lower)
- y_fit_linear_lower = p_linear_lower(x)
- r2_linear_lower = 1 - (np.sum((y_lower - y_fit_linear_lower)**2) / np.sum((y_lower - np.mean(y_lower))**2))
- # Fit quadratic regression for lower bounds
- coeffs_quad_lower = np.polyfit(x, y_lower, 2)
- p_quad_lower = np.poly1d(coeffs_quad_lower)
- y_fit_quad_lower = p_quad_lower(x)
- r2_quad_lower = 1 - (np.sum((y_lower - y_fit_quad_lower)**2) / np.sum((y_lower - np.mean(y_lower))**2))
- # Add color-coded text annotations for R² values
- # Upper bounds
- #ax.text(
- # 0.6, 0.90,
- # f"Quad: R²: {r2_quad_upper:.2f}",
- # color='blue', fontsize=5, verticalalignment='top', transform=ax.transAxes
- #)
- #ax.text(
- # 0.6, 0.80,
- # f"Lin: R²: {r2_linear_upper:.2f}",
- # color='darkblue', fontsize=5, verticalalignment='top', transform=ax.transAxes
- #)
- #ax.text(
- # 0.6, 0.10,
- # f"Quad: R²: {r2_quad_lower:.2f}",
- # color='red', fontsize=5, verticalalignment='top', transform=ax.transAxes)
- #ax.text(
- # 0.6, 0.20,
- # f"Lin: R²: {r2_linear_lower:.2f}",
- # color='darkred', fontsize=5, verticalalignment='top', transform=ax.transAxes)
- if pol == 'anodal':
- ax.set_xticks(np.abs(amps))
- else:
- ax.set_xticks(amps)
- ax.invert_xaxis() # Invert x-axis for negative amplitudes
- #ax.invert_yaxis()
- ax.set_ylim([-1000, 1000])
- ax.set_yticks([1000, 500, 0, -500, -1000], ['1', '', '0', '', '-1'])
- ax.invert_yaxis()
- # Adjust layout and save the figure
- plt.tight_layout(rect=[0, 0, 1, 0.96])
- path = r'C:\Users\jordan\Documents\combined_figs\evoked_potentials'
- #plt.savefig(os.path.join(path, f'half_distances_{dist_type}_updated.png'))
- #plt.savefig(os.path.join(path, f'half_distances_{dist_type}_updated.pdf'))
- # %%
- import pandas as pd
- import numpy as np
- from statsmodels.formula.api import ols
- import statsmodels.api as sm
- # Filter for valid ccf entries
- df_ccf = df_results[
- (df_results['max_volt'] > 40) &
- (df_results['half_max_lower_idx'] != -1) &
- (df_results['half_max_upper_idx'] != -1) &
- (df_results['lower_bound_idx'] != -1) &
- (df_results['upper_bound_idx'] != -1) &
- (df_results['amplitude'] != 10) &
- (df_results['amplitude'] != -10) # Exclude 10 uA
- ]
- # Build long-form dataframe
- records = []
- for _, row in df_ccf.iterrows():
- for bound_type, dist_col in zip(['upper', 'lower'], ['half_max_upper_idx_aligned', 'half_max_lower_idx_aligned']):
- records.append({
- 'distance': normalized_distances[row[dist_col]],
- 'amplitude': np.abs(row['amplitude']),
- 'polarity': (
- 'bipolar' if row['polarity'] == 'bipolar'
- else 'anodal' if row['amplitude'] > 0
- else 'cathodal'
- ),
- 'bound_type': bound_type
- })
- df_long = pd.DataFrame(records)
- df_long['amplitude'] = df_long['amplitude'].astype('category')
- df_long['polarity'] = df_long['polarity'].astype('category')
- df_long['bound_type'] = df_long['bound_type'].astype('category')
- for bound in ['upper', 'lower']:
- print(f"\n---- {bound.upper()} BOUND ----")
- df_bound = df_long[df_long['bound_type'] == bound]
- model = ols('distance ~ C(polarity) * amplitude', data=df_bound).fit()
- anova_table = sm.stats.anova_lm(model, typ=2)
- print(anova_table)
- model = ols('distance ~ C(polarity) * amplitude * C(bound_type)', data=df_long).fit()
- anova_table = sm.stats.anova_lm(model, typ=2)
- print(anova_table)
- # %%
- 6.040685e-03
- # %%
- df.columns
- # %%
- # ccf distances and peak aligned distances plots
- from scipy.stats import sem
- for dist_type in ['ccf', 'peak_aligned']:
- fig, axs = plt.subplots(1, 3, figsize=(8, 3)) # 2 rows for polarities, 2 columns for amplitude sign
- fig.suptitle(f'{dist_type} distances 1D half spatial bounds (up vs down) w fits', fontsize=12)
- colors = [cathodal_color, anodal_color, bipolar_color]
- for i, pol in enumerate(['cathodal', 'anodal', 'bipolar']):
- ax = axs[i]
- ax.set_title(pol, color = colors[i])
- if pol == 'bipolar':
- polarity = 'bipolar'
- else:
- polarity = 'monopolar'
- all_upper_dists = []
- all_lower_dists = []
- all_amps = []
- all_mean_uppers = []
- all_mean_lowers = []
- all_std_uppers = []
- all_std_lowers = []
- all_sem_uppers = []
- all_sem_lowers = []
- amps = [-5, -25, -50, -100]
- for k, amp in enumerate(amps):
- if pol == 'anodal':
- amp = np.abs(amp) # Make positive for anodal
- ax.set_xlabel('Amplitude (uA)')
- ax.set_ylabel('Distance (units)')
- #ax.invert_yaxis()
- df = df_results[
- (df_results['max_volt'] > 40) &
- (df_results['amplitude'] == amp) &
- (df_results['polarity'] == polarity) &
- (df_results['half_max_lower_idx'] != -1) &
- (df_results['half_max_upper_idx'] != -1) &
- (df_results['lower_bound_idx'] != -1) &
- (df_results['upper_bound_idx'] != -1)
- ]
- if dist_type == 'ccf':
- upper_dists = df['upper_bound_dist'].values
- lower_dists = df['lower_bound_dist'].values
- elif dist_type == 'peak_aligned':
- normalized_distances = np.arange(-4000, 4000, 10)[::-1]
- upper_bound_idxs = df['upper_bound_idx_aligned'].values
- lower_bound_idxs = df['lower_bound_idx_aligned'].values
- upper_dists = [normalized_distances[idx] for idx in upper_bound_idxs]
- lower_dists = [normalized_distances[idx] for idx in lower_bound_idxs]
- # Collect data for scatter plot
- all_upper_dists.extend(upper_dists)
- all_lower_dists.extend(lower_dists)
- all_amps.extend([amp] * len(upper_dists))
- # Scatter individual data points
- ax.scatter([amp] * len(upper_dists), upper_dists, color=colors[i], s = 3, alpha=0.8, zorder=2)
- ax.scatter([amp] * len(lower_dists), lower_dists, color=colors[i], s = 3, alpha=0.8, zorder=2)
- # print mean and std of upper and lower distances
- mean_upper = np.mean(all_upper_dists)
- mean_lower = np.mean(all_lower_dists)
- std_upper = np.std(all_upper_dists)
- std_lower = np.std(all_lower_dists)
- sem_upper = sem(all_upper_dists)
- sem_lower = sem(all_lower_dists)
- all_mean_uppers.append(mean_upper)
- all_mean_lowers.append(mean_lower)
- all_std_uppers.append(std_upper)
- all_std_lowers.append(std_lower)
- all_sem_uppers.append(sem_upper)
- all_sem_lowers.append(sem_lower)
- print(f'{pol} {dist_type} {amp}uA: Upper Mean: {mean_upper:.2f}, Std: {std_upper:.2f}; Lower Mean: {mean_lower:.2f}, Std: {std_lower:.2f}')
- if pol == 'anodal':
- amps = [5, 25, 50, 100]
- else:
- amps = [-5, -25, -50, -100]
- ax.plot(amps, all_mean_uppers, color = colors[i], marker = 'o', markersize = 4, linewidth = 1)
- ax.plot(amps, all_mean_lowers, color = colors[i], marker = 'o', markersize = 4, linewidth = 1)
- ax.fill_between(amps,
- np.array(all_mean_uppers) - np.array(all_sem_uppers),
- np.array(all_mean_uppers) + np.array(all_sem_uppers),
- color=colors[i], alpha=0.2, label='Upper Bound SEM', zorder=1)
- ax.fill_between(amps,
- np.array(all_mean_lowers) - np.array(all_sem_lowers),
- np.array(all_mean_lowers) + np.array(all_sem_lowers),
- color=colors[i], alpha=0.2, label='Lower Bound SEM', zorder=1)
- # Fit polynomial regression to the data
- if len(all_amps) > 1: # Ensure we have enough data points
- upper_coeffs = np.polyfit(all_amps, all_upper_dists, deg=2)
- lower_coeffs = np.polyfit(all_amps, all_lower_dists, deg=2)
- upper_poly = np.poly1d(upper_coeffs)
- lower_poly = np.poly1d(lower_coeffs)
- # Generate smooth curves for plotting
- amp_range = np.linspace(np.min(all_amps), np.max(all_amps), 100)
- upper_fit = upper_poly(amp_range)
- lower_fit = lower_poly(amp_range)
- # Calculate standard error of the mean (SEM) for the fits
- upper_fit_err = sem(all_upper_dists)
- lower_fit_err = sem(all_lower_dists)
- # Plot polynomial regression lines
- #ax.plot(amp_range, upper_fit, color='darkblue', label='Upper Bound Fit', zorder=4)
- #ax.plot(amp_range, lower_fit, color='darkred', label='Lower Bound Fit', zorder=4)
- # Add error bands
- #ax.fill_between(amp_range, upper_fit - upper_fit_err, upper_fit + upper_fit_err, color='blue', alpha=0.2, label='Upper Fit Error', zorder=1)
- #ax.fill_between(amp_range, lower_fit - lower_fit_err, lower_fit + lower_fit_err, color='red', alpha=0.2, label='Lower Fit Error', zorder=1)
- x = all_amps
- y_upper = all_upper_dists
- y_lower = all_lower_dists
- # Fit linear regression for upper bounds
- coeffs_linear_upper = np.polyfit(x, y_upper, 1)
- p_linear_upper = np.poly1d(coeffs_linear_upper)
- y_fit_linear_upper = p_linear_upper(x)
- r2_linear_upper = 1 - (np.sum((y_upper - y_fit_linear_upper)**2) / np.sum((y_upper - np.mean(y_upper))**2))
- # Fit quadratic regression for upper bounds
- coeffs_quad_upper = np.polyfit(x, y_upper, 2)
- p_quad_upper = np.poly1d(coeffs_quad_upper)
- y_fit_quad_upper = p_quad_upper(x)
- r2_quad_upper = 1 - (np.sum((y_upper - y_fit_quad_upper)**2) / np.sum((y_upper - np.mean(y_upper))**2))
- # Fit linear regression for lower bounds
- coeffs_linear_lower = np.polyfit(x, y_lower, 1)
- p_linear_lower = np.poly1d(coeffs_linear_lower)
- y_fit_linear_lower = p_linear_lower(x)
- r2_linear_lower = 1 - (np.sum((y_lower - y_fit_linear_lower)**2) / np.sum((y_lower - np.mean(y_lower))**2))
- # Fit quadratic regression for lower bounds
- coeffs_quad_lower = np.polyfit(x, y_lower, 2)
- p_quad_lower = np.poly1d(coeffs_quad_lower)
- y_fit_quad_lower = p_quad_lower(x)
- r2_quad_lower = 1 - (np.sum((y_lower - y_fit_quad_lower)**2) / np.sum((y_lower - np.mean(y_lower))**2))
- # Add color-coded text annotations for R² values
- # Upper bounds
- #ax.text(
- # 0.6, 0.90,
- # f"Quad: R²: {r2_quad_upper:.2f}",
- # color='blue', fontsize=5, verticalalignment='top', transform=ax.transAxes
- #)
- #ax.text(
- # 0.6, 0.80,
- # f"Lin: R²: {r2_linear_upper:.2f}",
- # color='darkblue', fontsize=5, verticalalignment='top', transform=ax.transAxes
- #)
- #ax.text(
- # 0.6, 0.10,
- # f"Quad: R²: {r2_quad_lower:.2f}",
- # color='red', fontsize=5, verticalalignment='top', transform=ax.transAxes)
- #ax.text(
- # 0.6, 0.20,
- # f"Lin: R²: {r2_linear_lower:.2f}",
- # color='darkred', fontsize=5, verticalalignment='top', transform=ax.transAxes)
- if pol == 'anodal':
- ax.set_xticks(np.abs(amps))
- else:
- ax.set_xticks(amps)
- ax.invert_xaxis() # Invert x-axis for negative amplitudes
- #ax.invert_yaxis()
- ax.set_ylim([-1500, 1500])
- ax.set_yticks([1500, 1000, 500, 0, -500, -1000,-1500], ['1.5', '', '', '0', '', '', '-1.5'])
- ax.invert_yaxis()
- # Adjust layout and save the figure
- plt.tight_layout(rect=[0, 0, 1, 0.96])
- path = r'C:\Users\jordan\Documents\combined_figs\evoked_potentials'
- #plt.savefig(os.path.join(path, f'distances_{dist_type}_updated.png'))
- #plt.savefig(os.path.join(path, f'distances_{dist_type}_updated.pdf'))
- # %%
- min_ch_list = []
- for _, row in df_results.iterrows():
- recording = row['recording']
- probe = row['probe']
- r = recording_map[recording]
- dists = distances[recording][probe]
- arg_min = np.argmin(np.abs(dists))
- min_ch_list.append(arg_min)
- df_results['closest_ch_ccf'] = min_ch_list
- # %%
- df = df_results.copy()
- df = df[
- (df['max_volt'] > 40) &
- (df['half_max_lower_idx'] != -1) &
- (df['half_max_upper_idx'] != -1) &
- (df['lower_bound_idx'] != -1) &
- (df['upper_bound_idx'] != -1)
- ].reset_index()
- df['normalized_closest_ch'] = df['closest_ch_ccf'] - df['max_idx']
- fig = plt.figure(figsize=(3, 3))
- swarm_plot = sns.swarmplot(data=df, x='recording', y='normalized_closest_ch', hue = 'recording', palette='viridis', size = 2)
- # Set labels with specified font size
- plt.xlabel('Recording', fontsize=10)
- plt.xticks(rotation=45)
- plt.ylabel('closest ccf channel - max_response ch', fontsize=10)
- plt.title('Normalized Closest Channel by Recording', fontsize=12)
- #plt.savefig(os.path.join(path, 'normalized_closest_ch_by_recording.png'))
- #plt.savefig(os.path.join(path, 'normalized_closest_ch_by_recording.pdf'))
- from scipy.stats import f_oneway
- # Make sure amplitude is treated as a group
- grouped = df.groupby('recording')['normalized_closest_ch']
- # Extract list of arrays (one per amplitude)
- groups = [group.values for _, group in grouped]
- # Run ANOVA
- fval, pval = f_oneway(*groups)
- print(f"One-way ANOVA: F = {fval:.4f}, p = {pval:.4e}")
- from statsmodels.stats.multicomp import pairwise_tukeyhsd
- tukey = pairwise_tukeyhsd(endog=df['normalized_closest_ch'],
- groups=df['recording'],
- alpha=0.05)
- print(tukey.summary())
- # %%
- df = df_results.copy()
- df = df[
- (df['max_volt'] > 40) &
- (df['half_max_lower_idx'] != -1) &
- (df['half_max_upper_idx'] != -1) &
- (df['lower_bound_idx'] != -1) &
- (df['upper_bound_idx'] != -1) &
- (df['amplitude'] != 10) &
- (df['amplitude'] != -10)
- ].reset_index()
- df['normalized_closest_ch'] = df['closest_ch_ccf'] - df['max_idx']
- fig = plt.figure(figsize = (3, 3))
- swarm_plot = sns.swarmplot(data=df, x='amplitude', y='normalized_closest_ch', palette='viridis', s = 2)
- # Set labels with specified font size
- plt.xlabel('amplitude', fontsize=10)
- plt.xticks(rotation=45)
- plt.ylabel('closest ccf channel - max_response ch', fontsize=10)
- plt.title('Normalized Closest Channel by amplitude', fontsize=12)
- from scipy.stats import f_oneway
- # Make sure amplitude is treated as a group
- grouped = df.groupby('amplitude')['normalized_closest_ch']
- # Extract list of arrays (one per amplitude)
- groups = [group.values for _, group in grouped]
- # Run ANOVA
- fval, pval = f_oneway(*groups)
- print(f"One-way ANOVA: F = {fval:.4f}, p = {pval:.4e}")
- # %%
- df = df_results.copy()
- df = df[
- (df['max_volt'] > 40) &
- (df['half_max_lower_idx'] != -1) &
- (df['half_max_upper_idx'] != -1) &
- (df['lower_bound_idx'] != -1) &
- (df['upper_bound_idx'] != -1)
- ].reset_index()
- df['normalized_closest_ch'] = df['closest_ch_ccf'] - df['max_idx']
- fig = plt.figure(figsize = (2, 3))
- df['group'] = 'all'
- swarm_plot = sns.swarmplot(data=df, x='group', y='normalized_closest_ch', hue = 'group', palette='viridis', s = 2)
- # Set labels with specified font size
- plt.xlabel('amplitude', fontsize=10)
- plt.xticks(rotation=45)
- plt.ylabel('closest ccf channel - max_response ch', fontsize=10)
- plt.title('Normalized Closest Channel by amplitude', fontsize=12)
- # print the mean and std and sem of the normalized closest channel
- mean_closest_ch = df['normalized_closest_ch'].mean()
- std_closest_ch = df['normalized_closest_ch'].std()
- sem_closest_ch = df['normalized_closest_ch'].sem()
- print(f'Mean normalized closest channel: {mean_closest_ch:.2f}, Std: {std_closest_ch:.2f} SEM: {sem_closest_ch:.2f}')
- plt.axhline(0, color='black', linestyle='--', linewidth=0.5)
- path = r'C:\Users\jordan\Documents\combined_figs\evoked_potentials'
- #plt.savefig(os.path.join(path, 'normalized_closest_ch_all.png'))
- #plt.savefig(os.path.join(path, 'normalized_closest_ch_all.pdf'))
- # %% [markdown]
- # # peak voltage, half and full AUC regressions for amplitudes
- # %%
- df.head()
- # %%
- #scatters
- fig, axs = plt.subplots(3, 3, figsize=(4, 4))
- amplitudes = [-5, -25, -50, -100]
- for col, pol in enumerate(['cathodal', 'anodal', 'bipolar']):
- if pol == 'bipolar':
- polarity = 'bipolar'
- else:
- polarity = 'monopolar'
- if pol == 'anodal':
- amps = np.abs(amplitudes)
- else:
- amps = amplitudes
- df = df_results[
- (df_results['max_volt'] > 40) &
- (df_results['amplitude'].isin(amps)) &
- (df_results['polarity'] == polarity) &
- (df_results['half_max_lower_idx'] != -1) &
- (df_results['half_max_upper_idx'] != -1) &
- (df_results['lower_bound_idx'] != -1) &
- (df_results['upper_bound_idx'] != -1)
- ]
- variables = ['max_volt', 'half_max_AUC', 'full_AUC']
- titles = ['Max Voltage by Amplitude', 'Half Max AUC by Amplitude', 'Full AUC by Amplitude']
- for i, (var, title) in enumerate(zip(variables, titles)):
- row = i
- ax = axs[row, col]
- # Scatter plot of individual points
- sns.scatterplot(
- data=df, x='amplitude', y=var, color = colors[col], s=6, ax=ax, legend=False, alpha=1
- )
- # fit first and second order regression lines style
- sns.regplot(
- data=df, x='amplitude', y=var, scatter=False, ci=68, order = 2, line_kws={'color': 'black', 'linewidth': 0.5}, ax=ax
- )
- sns.regplot(
- data=df, x='amplitude', y=var, scatter=False, ci=68, order = 1, line_kws={'color': 'blue', 'linewidth': 0.5}, ax=ax
- )
- # print mean and std of the variables
- for amp in [-5, -25, -50, -100]:
- if pol == 'anodal':
- amp = np.abs(amp)
- amp_df = df[df['amplitude'] == amp]
- mean_value = amp_df[var].mean()
- std_value = amp_df[var].std()
- print(f'{pol}_{amp}: {var} mean ± std: {mean_value:.2f} ± {std_value:.2f}')
- #if row == 0:
- #ax.set_title(title, fontsize=6)
- if row == 2:
- ax.set_xlabel('Amplitude (uA)', fontsize=6)
- ax.set_xticks(amps, amps, fontsize = 6)
- else:
- ax.set_xlabel('')
- ax.set_xticks(amps, ['', '', '', ''], fontsize = 6)
- filtered_df = df[['amplitude', var]].dropna()
- x = filtered_df['amplitude']
- y = filtered_df[var]
- # Fit linear regression (order=1)
- coeffs_linear = np.polyfit(x, y, 1)
- p_linear = np.poly1d(coeffs_linear)
- y_fit_linear = p_linear(x)
- r2_linear = 1 - (np.sum((y - y_fit_linear)**2) / np.sum((y - np.mean(y))**2))
- # Fit quadratic regression (order=2)
- coeffs_quad = np.polyfit(x, y, 2)
- p_quad = np.poly1d(coeffs_quad)
- y_fit_quad = p_quad(x)
- r2_quad = 1 - (np.sum((y - y_fit_quad)**2) / np.sum((y - np.mean(y))**2))
- ax.text(0.05, 0.95, f"Linear R² = {r2_linear:.2f}\nQuadratic R²= {r2_quad:.2f}",
- transform=ax.transAxes, fontsize=4, verticalalignment='top')
- if pol != 'anodal':
- ax.invert_xaxis()
- if var == 'max_volt':
- ax.set_ylim([0, 2000])
- if col == 0:
- ax.set_ylabel('Max Voltage (uV)', fontsize=6)
- ax.set_yticks([0, 1000, 2000], [0, 1, 2], fontsize = 6)
- else:
- ax.set_ylabel('')
- ax.set_yticks([0, 1000, 2000], ['', '', ''], fontsize = 6)
- elif var == 'half_max_AUC':
- ax.set_ylim([0, 80000])
- if col == 0:
- ax.set_ylabel('Half Max AUC (mV*ms)', fontsize=6)
- ax.set_yticks([0, 40000, 80000], [0, 4, 8], fontsize = 6)
- else:
- ax.set_ylabel('')
- ax.set_yticks([0, 40000, 80000], ['', '', ''], fontsize = 6)
- elif var == 'full_AUC':
- ax.set_ylim([0, 120000])
- if col == 0:
- ax.set_yticks([0, 60000, 120000], [0, 6, 12], fontsize = 6)
- ax.set_ylabel('Full AUC (mV*ms)', fontsize=6)
- else:
- ax.set_yticks([0, 60000, 120000], ['', '', ''], fontsize = 6)
- ax.set_ylabel('')
- ax.spines['top'].set_visible(False)
- ax.spines['right'].set_visible(False)
- # Adjust layout and save the figure
- #plt.suptitle()
- plt.tight_layout()
- #plt.savefig(os.path.join(path, f'max_auc_scatter_with_means__poly_updated.png'))
- #plt.savefig(os.path.join(path, f'max_auc_scatter_with_means__poly_updated.pdf'))
- # %% [markdown]
- # # comparing LFP and AP
- # %%
- r = jlh31
- probe = 'probeB'
- amp = -100
- channels = 300
- stim_times = choose_stim_parameter(r.trials, amp=amp, pulse_number=1, pulse_duration=100, polarity='monopolar')[0:75]
- fig, axes = plt.subplots(1,2)
- ## AP plot
- pre = -0.3
- post = 5
- data = r.raw.get_chunk(probe, stim_times, pre=pre + 3, post=post + 3, chs=np.arange(channels))
- aligned_data = align_data(data, pre, post, channels, threshold=500, median_subtraction=False)
- dists = distances[r.mouse][probe]
- # Use the axs object for the current amp
- sub_chs = np.arange(len(dists), len(dists) + 20)
- aligned_sub_data = aligned_data - np.median(aligned_data[:,:, sub_chs], axis=2)[:,:, np.newaxis]
- ax = axes[0]
- raw_heatmap(aligned_sub_data, pre=pre, post=post, dists=dists, vmin=-300, vmax=300,
- save=False, title=f'{amp} uA', ax=ax)
- # LFP
- pre = 0
- post = 100
- data = r.raw.get_chunk(probe, stim_times, pre=pre, post=post, band = 'lfp', chs=np.arange(channels))
- aligned_data = data
- dists = distances[r.mouse][probe]
- # Use the axs object for the current amp
- sub_chs = np.arange(len(dists), len(dists) + 40)
- aligned_sub_data = aligned_data - np.median(aligned_data[:,:, sub_chs], axis=2)[:,:, np.newaxis]
- ax = axes[1]
- raw_heatmap(aligned_sub_data, pre=pre, post=post, dists=dists, vmin=-800, vmax=800,
- save=False, title=f'{amp} uA', ax=ax)
- plt.tight_layout()
- # %% [markdown]
- # # volumes
- # %%
- from scipy.spatial import ConvexHull
- import numpy as np
- def calculate_volume(points):
- """
- Calculate the volume of a 3D shape defined by 6 points using a convex hull.
- Parameters:
- points (ndarray): A (6, 3) numpy array where each row represents a 3D point (x, y, z).
- Returns:
- float: The volume of the convex hull enclosing the points.
- """
- # Check if the input is valid
- if points.shape != (6, 3):
- raise ValueError("Input must be a (6, 3) array of 3D points.")
- # Compute the convex hull
- hull = ConvexHull(points) # points should be (6,3) array
- volume = hull.volume
- # Return the volume of the convex hull
- return hull.volume
- # %%
- #3D stuff... this requires measurements for all 3 probes...
- polarities = ['bipolar', 'monopolar']
- probes = ['probeA', 'probeB', 'probeC']
- amps = [-5, -25, -50, -100, 5, 25, 50, 100]
- volume_dict = {}
- points_dict = {}
- points_dict_rec = {}
- rec_dict = {}
- for polarity in polarities:
- for i, amp in enumerate(amps):
- # Filter data for the current probe and amplitude
- df = df_results[
- (df_results['max_volt'] > 40) &
- (df_results['amplitude'] == amp) &
- (df_results['polarity'] == polarity) &
- (df_results['lower_bound_idx'] != -1) &
- (df_results['upper_bound_idx'] != -1)
- ]
- #print(df['recording'].unique())
- for record in df['recording'].unique():
- probes = df[df['recording'] == record]['probe'].unique()
- if len(probes) == 3:
- key = f'{polarity}_{amp}'
- r = recording_map[record]
- a_df = df[(df['recording'] == record) & (df['probe'] == 'probeA')]
- a_upper_ch = a_df['upper_bound_idx'].values[0]
- a_lower_ch = a_df['lower_bound_idx'].values[0]
- try:
- a_up_ccf = r.probe_coords['probeA'][a_upper_ch]
- except:
- a_up_cff = r.probe_coords['probeA'][-1]
- a_low_ccf = r.probe_coords['probeA'][a_lower_ch]
- b_df = df[(df['recording'] == record) & (df['probe'] == 'probeB')]
- b_upper_ch = b_df['upper_bound_idx'].values[0]
- b_lower_ch = b_df['lower_bound_idx'].values[0]
- try:
- b_up_ccf = r.probe_coords['probeB'][b_upper_ch]
- except:
- b_up_ccf = r.probe_coords['probeB'][-1]
- b_low_ccf = r.probe_coords['probeB'][b_lower_ch]
- c_df = df[(df['recording'] == record) & (df['probe'] == 'probeC')]
- c_upper_ch = c_df['upper_bound_idx'].values[0]
- c_lower_ch = c_df['lower_bound_idx'].values[0]
- try:
- c_up_ccf = r.probe_coords['probeC'][c_upper_ch]
- except:
- c_up_ccf = r.probe_coords['probeC'][-1]
- c_low_ccf = r.probe_coords['probeC'][c_lower_ch]
- points = np.array([a_up_ccf, a_low_ccf, b_up_ccf, b_low_ccf, c_up_ccf, c_low_ccf])
- hull = ConvexHull(points)
- volume = hull.volume
- if key not in volume_dict:
- volume_dict[key] = []
- volume_dict[key].append(volume)
- if key not in points_dict:
- points_dict[key] = []
- points_dict[key].append(points)
- key_rec = f'{record}_{polarity}_{amp}'
- if key_rec not in points_dict_rec:
- points_dict_rec[key_rec] = []
- points_dict_rec[key_rec].append(points)
- if key not in rec_dict:
- rec_dict[key] = []
- rec_dict[key].append(record)
- # %%
- volume_dict_mm3 = {key: np.array(values) / 1e9 for key, values in volume_dict.items()}
- mc_25 = volume_dict_mm3['monopolar_-25']
- mc_50 = volume_dict_mm3['monopolar_-50']
- mc_100 = volume_dict_mm3['monopolar_-100']
- mc_x = [-25, -50, -100]
- mc_y = [np.mean(mc_25), np.mean(mc_50), np.mean(mc_100)]
- mc_err = [sem(mc_25), sem(mc_50), sem(mc_100)]
- # print mean +/- std of monopolar volumes
- print(f'Cathodal Mean Volumes: -25uA: {np.mean(mc_25):.2f}+/-{np.std(mc_25):.2f} mm³, -50uA: {np.mean(mc_50):.2f}+/-{np.std(mc_50):.2f}mm³, -100uA: {np.mean(mc_100):.2f}+/-{np.std(mc_100):.2f} mm³')
- fig, ax = plt.subplots(1, 1, figsize=(2, 3.5))
- # Line and error fill for monopolar (cathodal)
- ax.plot(mc_x, mc_y, label='Cathodal', color=cathodal_color, marker='o')
- ax.fill_between(mc_x, np.array(mc_y) - np.array(mc_err), np.array(mc_y) + np.array(mc_err),
- color=cathodal_color, alpha=0.2)
- # Plot individual values with jitter
- jitter_strength = 1.1
- for x, values in zip(mc_x, [mc_25, mc_50, mc_100]):
- jittered_x = np.random.normal(x, scale=jitter_strength, size=len(values))
- ax.scatter(jittered_x, values, color=cathodal_color, s=5, alpha=0.7, zorder=3)
- # Labels and formatting
- ax.set_title('Mean Volume')
- ax.set_xlabel('Amplitude (uA)')
- ax.set_ylabel('Volume (mm³)')
- ax.invert_xaxis()
- ax.set_xticks([-25, -50, -100])
- ax.set_ylim(0, 1)
- ax.set_yticks([0, 0.5, 1])
- ax.spines['top'].set_visible(False)
- ax.spines['right'].set_visible(False)
- plt.tight_layout()
- path = r'C:\Users\jordan\Documents\combined_figs\evoked_potentials'
- #plt.savefig(os.path.join(path, 'monopolar_EPvolume.png'))
- #plt.savefig(os.path.join(path, 'monopolar_EPvolume.pdf'))
- # %%
- from scipy.stats import linregress
- import numpy as np
- # Stack all individual volumes
- all_volumes = np.concatenate([mc_25, mc_50, mc_100])
- all_amplitudes = np.concatenate([
- np.full_like(mc_25, -25),
- np.full_like(mc_50, -50),
- np.full_like(mc_100, -100)
- ])
- # Run linear regression
- slope, intercept, r_value, p_value, std_err = linregress(np.abs(all_amplitudes), all_volumes)
- # Print regression results
- print(f"Linear Regression: Volume = {slope:.4f} * Amplitude + {intercept:.4f}")
- print(f"R² = {r_value**2:.4f}, p = {p_value:.4e}")
- # %%
- 9.1036e-02
- # %%
- # Quadratic fit
- quad_coeffs = np.polyfit(np.abs(all_amplitudes), all_volumes, deg=2)
- quad_model = np.poly1d(quad_coeffs)
- # Predicted values
- y_quad_fit = quad_model(np.abs(all_amplitudes))
- # R² for quadratic
- ss_res_quad = np.sum((all_volumes - y_quad_fit) ** 2)
- ss_tot = np.sum((all_volumes - np.mean(all_volumes)) ** 2)
- r2_quad = 1 - (ss_res_quad / ss_tot)
- print(f"Quadratic Fit: Volume = {quad_coeffs[0]:.4e} * A² + {quad_coeffs[1]:.4e} * A + {quad_coeffs[2]:.4e}")
- print(f"R² (Quadratic) = {r2_quad:.4f}")
- # %%
- from scipy.stats import sem
- # Convert µm³ to mm³ (1 mm³ = 10^9 µm³)
- volume_dict_mm3 = {key: np.array(values) / 1e9 for key, values in volume_dict.items()}
- bc_25 = volume_dict_mm3['bipolar_-25']
- bc_50 = volume_dict_mm3['bipolar_-50']
- bc_100 = volume_dict_mm3['bipolar_-100']
- mc_25 = volume_dict_mm3['monopolar_-25']
- mc_50 = volume_dict_mm3['monopolar_-50']
- mc_100 = volume_dict_mm3['monopolar_-100']
- ma_25 = volume_dict_mm3['monopolar_25']
- ma_50 = volume_dict_mm3['monopolar_50']
- ma_100 = volume_dict_mm3['monopolar_100']
- bc_x = [-25, -50, -100]
- bc_y = [np.mean(bc_25), np.mean(bc_50), np.mean(bc_100)]
- bc_err = [sem(bc_25), sem(bc_50), sem(bc_100)]
- mc_x = [-25, -50, -100]
- mc_y = [np.mean(mc_25), np.mean(mc_50), np.mean(mc_100)]
- mc_err = [sem(mc_25), sem(mc_50), sem(mc_100)]
- ma_x = [25, 50, 100]
- ma_y = [np.mean(ma_25), np.mean(ma_50), np.mean(ma_100)]
- ma_err = [sem(ma_25), sem(ma_50), sem(ma_100)]
- print(f'Cathodal Mean Volumes: -25uA: {np.mean(mc_25):.2f}+/-{np.std(mc_25):.2f} mm³, -50uA: {np.mean(mc_50):.2f}+/-{np.std(mc_50):.2f}mm³, -100uA: {np.mean(mc_100):.2f}+/-{np.std(mc_100):.2f} mm³')
- print(f'Anodal Mean Volumes: 25uA: {np.mean(ma_25):.2f}+/-{np.std(ma_25):.2f} mm³, 50uA: {np.mean(ma_50):.2f}+/-{np.std(ma_50):.2f}mm³, 100uA: {np.mean(ma_100):.2f}+/-{np.std(ma_100):.2f} mm³')
- print(f'Bipolar Mean Volumes: -25uA: {np.mean(bc_25):.2f}+/-{np.std(bc_25):.2f} mm³, -50uA: {np.mean(bc_50):.2f}+/-{np.std(bc_50):.2f}mm³, -100uA: {np.mean(bc_100):.2f}+/-{np.std(bc_100):.2f} mm³')
- fig, ax = plt.subplots(1, 1, figsize=(3.5, 3.5))
- # Bipolar
- ax.plot(bc_x, bc_y, label='Bipolar', color=bipolar_color, marker='o')
- ax.fill_between(bc_x, np.array(bc_y) - np.array(bc_err), np.array(bc_y) + np.array(bc_err), color=bipolar_color, alpha=0.2)
- # Cathodal
- ax.plot(mc_x, mc_y, label='Cathodal', color=cathodal_color, marker='o')
- ax.fill_between(mc_x, np.array(mc_y) - np.array(mc_err), np.array(mc_y) + np.array(mc_err), color=cathodal_color, alpha=0.2)
- # Anodal
- ax.plot(ma_x, ma_y, label='Anodal', color=anodal_color, marker='o')
- ax.fill_between(ma_x, np.array(ma_y) - np.array(ma_err), np.array(ma_y) + np.array(ma_err), color=anodal_color, alpha=0.2)
- # Labels and legend
- ax.set_title('Mean Volume')
- ax.set_xlabel('Amplitude (uA)')
- ax.set_ylabel('Volume (mm³)')
- #ax.legend(loc='upper right')
- ax.set_xticks([-100, -50, -25, 25, 50, 100])
- ax.set_ylim(0, 1)
- ax.set_yticks([0, 0.5, 1])
- ax.spines['top'].set_visible(False)
- ax.spines['right'].set_visible(False)
- plt.tight_layout()
- #plt.savefig(os.path.join(path, 'volume_by_amplitude.png'))
- #plt.savefig(os.path.join(path, 'volume_by_amplitude.pdf'))
- # %%
- import statsmodels.api as sm
- from statsmodels.formula.api import ols
- model = ols("volume ~ C(polarity) * amplitude", data=df_vol).fit()
- anova_table = sm.stats.anova_lm(model, typ=2)
- print(anova_table)
- # %%
figure2.ipynb at commit c4aaa60, under GPL-3.0 · at the source
Overview
- Department of Biophysics and Physiology, University of Colorado Anschutz Medical Campus, Aurora, CO, USA
- Medical Scientist Training Program, University of Colorado Anschutz Medical Campus, Aurora, CO, USA
- Neuroscience Graduate Program, University of Colorado Anschutz Medical Campus, Aurora, CO, USA
- Department of Ophthalmology, University of Colorado Anschutz Medical Campus, Aurora, CO, USA
- Lead contact
Abstract
The abstract is not reproduced here: the paper's license (CC BY-NC-ND) does not allow it. Read it in the paper, at the publisher or on Europe PMC.
Repositories
Its files are read in the Code ↔ Paper reader above, with 7 matches between paragraphs and lines of code.
denmanlab/am4100_code
b1587c33836b7c8626e5b3816ff4d4753b978043, 22 May 2024Availability: 1 check, the latest on 28 September 2026: the link answers
- 28 September 2026: the link answers
34 files
- am4100.py, Python, 153 lines
- matlab_from_amsystems/
ComConstants.m , MATLAB, 116 lines - matlab_from_amsystems/
DrawBiphasic.m , MATLAB, 392 lines - matlab_from_amsystems/
DrawMonophasic.m , MATLAB, 215 lines - matlab_from_amsystems/
DrawRamp.m , MATLAB, 313 lines - matlab_from_amsystems/
EXAMPLE_1_USB.m , MATLAB, 115 lines - matlab_from_amsystems/
EXAMPLE_2_Ethernet.m , MATLAB, 106 lines - matlab_from_amsystems/
IDnum.m , MATLAB, 158 lines - matlab_from_amsystems/
LoadLibrary.m , MATLAB, 45 lines - matlab_from_amsystems/
LoadWindow.m , MATLAB, 218 lines - matlab_from_amsystems/
Plotit.m , MATLAB, 396 lines - matlab_from_amsystems/
RetEamp1.m , MATLAB, 38 lines - matlab_from_amsystems/
RetEamp3.m , MATLAB, 38 lines - matlab_from_amsystems/
SetCom.m , MATLAB, 66 lines - matlab_from_amsystems/
UpdateEvents.m , MATLAB, 35 lines - matlab_from_amsystems/
VorIvalue.m , MATLAB, 59 lines - matlab_from_amsystems/
ams4100_GUI.m , MATLAB, 534 lines - matlab_from_amsystems/
ams4100_hClass.m , MATLAB, 1,553 lines - matlab_from_amsystems/
checkTimes.m , MATLAB, 46 lines - matlab_from_amsystems/
controlsGUI.m , MATLAB, 51 lines - matlab_from_amsystems/
defaultData.m , MATLAB, 70 lines - matlab_from_amsystems/
getOffsetAmps.m , MATLAB, 81 lines - matlab_from_amsystems/
getTimeValues.m , MATLAB, 85 lines - matlab_from_amsystems/
getValueNames.m , MATLAB, 71 lines - matlab_from_amsystems/
nidaq_test.m , MATLAB, 13 lines - matlab_from_amsystems/
processUserInput.m , MATLAB, 421 lines - matlab_from_amsystems/
setTimeAMS.m , MATLAB, 45 lines - matlab_from_amsystems/
timeNum.m , MATLAB, 52 lines - matlab_from_amsystems/
trainNum.m , MATLAB, 38 lines - matlab_from_amsystems/
untitled2.m , MATLAB, 2 lines - matlab_from_amsystems/
untitled3.m , MATLAB, 1 line - read_serial.py, Python, 26 lines
- workshopping_am4100class
.ipynb , Jupyter, 190 lines - LICENSE, License, 21 lines
denmanlab/mouse_behavior
59fb7046918c4f9cb361b8df584a525af5a0cc2e, 1 August 2025Availability: 1 check, the latest on 28 September 2026: the link answers
- 28 September 2026: the link answers
19 files
- archive/
arduino_controller.py , Python, 96 lines - archive/
camera.py , Python, 304 lines - archive/
camera2.py , Python, 103 lines - archive/
mouse_detection.py , Python, 559 lines - archive/
mouse_detection2.py , Python, 673 lines - archive/
mouse_detection_contrast , Python, 937 lines.py - archive/
mouse_detection_shaping. , Python, 924 linespy - archive/
mouse_detection_timeout. , Python, 744 linespy - archive/
solenoid_controller.py , Python, 39 lines - archive/
sync_to_backups.py , Python, 187 lines - arduino_controller_jlh.p
y , Python, 55 lines - custom_timer.py, Python, 11 lines
- dev.py, Python, 533 lines, 1 match
- params.py, Python, 177 lines, 1 match
- plotter.py, Python, 710 lines, 1 match
- stimuli.py, Python, 29 lines
- testing.py, Python, 28 lines
- testing_backup_notebook.
ipynb , Jupyter, 108 lines - README.md, Text, 1 line
denmanlab/estim_populations
c4aaa608314c8e8e91aee422baa69cd1b443a682, 29 October 2025Availability: 1 check, the latest on 28 September 2026: the link answers
- 28 September 2026: the link answers
5 files
- figure1.ipynb, Jupyter, 825 lines
- figure2.ipynb, Jupyter, 2,260 lines, 2 matches
- figure3.ipynb, Jupyter, 2,826 lines, 2 matches
- repository limit reached (2,000 files or 30 MB): the rest is at the source (16 files)
- LICENSE, License, 674 lines
- README.md, Text, 77 lines
marcomusy/vedo
88f8b2f9c5c6267b5dceda0e4ab0e15b3130910a, 4 August 2026Availability: 1 check, the latest on 28 September 2026: the link answers
- 28 September 2026: the link answers
584 files
- cli_entry.py, Python, 17 lines
- docs/
assets/ , JavaScript, 10 linessearch-expand.js - docs/
examples_db.js , JavaScript, 2,821 lines - docs/
logos/ , Python, 44 linesembl_logo.py - docs/
logos/ , Python, 21 lineslab_logo_maker.py - docs/
logos/ , Python, 8 lineslogo_vedo_simple.py - examples/
advanced/ , Python, 56 linescapping_mesh.py - examples/
advanced/ , Python, 22 linescontours2mesh.py - examples/
advanced/ , Python, 13 linesconvex_hull.py - examples/
advanced/ , Python, 16 linescut_and_cap.py - examples/
advanced/ , Python, 19 linescut_with_mesh1.py - examples/
advanced/ , Python, 24 linescut_with_points1.py - examples/
advanced/ , Python, 26 linescut_with_points2.py - examples/
advanced/ , Python, 67 linesdiffuse_data.py - examples/
advanced/ , Python, 28 linesfitline.py - examples/
advanced/ , Python, 25 linesfitplanes.py - examples/
advanced/ , Python, 30 linesfitspheres1.py - examples/
advanced/ , Python, 31 linesfitspheres2.py - examples/
advanced/ , Python, 17 linesgeodesic_curve.py - examples/
advanced/ , Python, 156 linesgeological_model.py - examples/
advanced/ , Python, 22 linesgyroid.py - examples/
advanced/ , Python, 66 linesinterpolate_field.py - examples/
advanced/ , Python, 21 linesinterpolate_scalar1.py - examples/
advanced/ , Python, 33 linesinterpolate_scalar2.py - examples/
advanced/ , Python, 17 linesinterpolate_scalar3.py - examples/
advanced/ , Python, 34 linesinterpolate_scalar4.py - examples/
advanced/ , Python, 49 linesinterpolate_scalar5.py - examples/
advanced/ , Python, 31 linesline2mesh_quads.py - examples/
advanced/ , Python, 26 linesline2mesh_tri.py - examples/
advanced/ , Python, 45 linesmeasure_curvature1.py - examples/
advanced/ , Python, 80 linesmeasure_curvature2.py - examples/
advanced/ , Python, 21 linesmesh_smoother1.py - examples/
advanced/ , Python, 21 linesmesh_smoother2.py - examples/
advanced/ , Python, 33 linesmeshquality.py - examples/
advanced/ , Python, 38 linesmoving_least_squares1D.p y - examples/
advanced/ , Python, 52 linesmoving_least_squares2D.p y - examples/
advanced/ , Python, 64 linesmulti_viewer2.py - examples/
advanced/ , Python, 43 linesrecosurface.py - examples/
advanced/ , Shell, 16 linesrun_all.sh - examples/
advanced/ , Python, 17 linesskeletonize.py - examples/
advanced/ , Python, 16 linesspline_draw1.py - examples/
advanced/ , Python, 18 linesspline_draw2.py - examples/
advanced/ , Python, 11 linessplitmesh.py - examples/
advanced/ , Python, 22 linestimer_callback0.py - examples/
advanced/ , Python, 51 linestimer_callback1.py - examples/
advanced/ , Python, 63 linestimer_callback2.py - examples/
advanced/ , Python, 55 linestimer_callback3.py - examples/
advanced/ , Python, 17 linesvoronoi2.py - examples/
advanced/ , Python, 31 lineswarp1.py - examples/
advanced/ , Python, 30 lineswarp2.py - examples/
advanced/ , Python, 103 lineswarp3.py - examples/
advanced/ , Python, 160 lineswarp4a.py - examples/
advanced/ , Python, 120 lineswarp4b.py - examples/
advanced/ , Python, 141 lineswarp5.py - examples/
advanced/ , Python, 33 lineswarp6.py - examples/
animation/ , Python, 21 linesairplane1.py - examples/
animation/ , Python, 27 linesairplane2.py - examples/
animation/ , Python, 67 linesaizawa_attractor.py - examples/
animation/ , Python, 40 linesaspring1.py - examples/
animation/ , Python, 49 linesaspring2_player.py - examples/
animation/ , Python, 130 linesbrownian2d.py - examples/
animation/ , Python, 59 linesdoubleslit.py - examples/
animation/ , Python, 32 linesdrag_chain.py - examples/
animation/ , Python, 77 linesfourier_epicycles.py - examples/
animation/ , Python, 134 linesgas.py - examples/
animation/ , Python, 83 linesgrayscott.py - examples/
animation/ , Python, 62 linesgyroscope1.py - examples/
animation/ , Python, 34 lineskoch_fractal.py - examples/
animation/ , Python, 45 lineslorenz.py - examples/
animation/ , Python, 69 linesmag_field1.py - examples/
animation/ , Python, 126 linesmultiple_pendulum.py - examples/
animation/ , Python, 324 linesoptics_base.py - examples/
animation/ , Python, 108 linesoptics_main1.py - examples/
animation/ , Python, 59 linesoptics_main2.py - examples/
animation/ , Python, 34 linesoptics_main3.py - examples/
animation/ , Python, 123 linesparticle_simulator.py - examples/
animation/ , Python, 49 linespendulum_3d.py - examples/
animation/ , Python, 66 linespendulum_ode.py - examples/
animation/ , Shell, 14 linesrun_all.sh - examples/
animation/ , Python, 78 linesself_org_maps2d.py - examples/
animation/ , Python, 27 linesspline_ease.py - examples/
animation/ , Python, 81 linessprings_fem.py - examples/
animation/ , Python, 23 linestrail.py - examples/
animation/ , Python, 52 linestunnelling1.py - examples/
animation/ , Python, 79 linestunnelling2.py - examples/
animation/ , Python, 123 linesvalue_iteration.py - examples/
animation/ , Python, 72 linesvolterra.py - examples/
animation/ , Python, 117 lineswave_equation1d.py - examples/
animation/ , Python, 65 lineswave_equation2d.py - examples/
basic/ , Python, 21 linesalign1.py - examples/
basic/ , Python, 29 linesalign2.py - examples/
basic/ , Python, 26 linesalign3.py - examples/
basic/ , Python, 26 linesalign4.py - examples/
basic/ , Python, 31 linesalign5.py - examples/
basic/ , Python, 21 linesalign6.py - examples/
basic/ , Python, 24 linesbackground_image.py - examples/
basic/ , Python, 29 linesboolean.py - examples/
basic/ , Python, 18 linesboundaries.py - examples/
basic/ , Python, 19 linesbuildmesh.py - examples/
basic/ , Python, 31 linesbuttons1.py - examples/
basic/ , Python, 52 linesbuttons2.py - examples/
basic/ , Python, 44 linesbuttons3.py - examples/
basic/ , Python, 37 linescartoony.py - examples/
basic/ , Python, 30 linescells_within_bounds.py - examples/
basic/ , Python, 24 linesclustering.py - examples/
basic/ , Python, 25 linescolor_mesh_cells1.py - examples/
basic/ , Python, 27 linescolor_mesh_cells2.py - examples/
basic/ , Python, 69 linescolorcubes.py - examples/
basic/ , Python, 39 linescolorlines.py - examples/
basic/ , Python, 43 linescolormap_list.py - examples/
basic/ , Python, 35 linescolormaps.py - examples/
basic/ , Python, 25 linesconnected_vtx.py - examples/
basic/ , Python, 62 linescut_freehand.py - examples/
basic/ , Python, 61 linescut_interactive.py - examples/
basic/ , Python, 20 linesdelaunay2d.py - examples/
basic/ , Python, 30 linesdelete_mesh_pts.py - examples/
basic/ , Python, 19 linesdistance2mesh.py - examples/
basic/ , Python, 17 linesextrude1.py - examples/
basic/ , Python, 25 linesextrude2.py - examples/
basic/ , Python, 14 linesfillholes.py - examples/
basic/ , Python, 15 linesflatarrow.py - examples/
basic/ , Python, 45 linesglyphs1.py - examples/
basic/ , Python, 35 linesglyphs2.py - examples/
basic/ , Python, 21 linesglyphs3.py - examples/
basic/ , Python, 29 lineshover_legend.py - examples/
basic/ , Python, 39 linesinput_box.py - examples/
basic/ , Python, 24 linesinteraction_modes1.py - examples/
basic/ , Python, 31 linesinteraction_modes2.py - examples/
basic/ , Python, 24 linesinteraction_modes3.py - examples/
basic/ , Python, 40 linesinteraction_modes4.py - examples/
basic/ , Python, 27 lineskeypress.py - examples/
basic/ , Python, 17 lineslargestregion.py - examples/
basic/ , Python, 29 lineslegendbox.py - examples/
basic/ , Python, 33 lineslight_sources.py - examples/
basic/ , Python, 15 lineslightings.py - examples/
basic/ , Python, 20 lineslin_interpolate.py - examples/
basic/ , Python, 21 linesmanyspheres.py - examples/
basic/ , Python, 20 linesmesh_alphas.py - examples/
basic/ , Python, 36 linesmesh_coloring.py - examples/
basic/ , Python, 27 linesmesh_custom.py - examples/
basic/ , Python, 48 linesmesh_lut.py - examples/
basic/ , Python, 22 linesmesh_map2cell.py - examples/
basic/ , Python, 15 linesmesh_modify.py - examples/
basic/ , Python, 16 linesmesh_sharemap.py - examples/
basic/ , Python, 17 linesmesh_threshold.py - examples/
basic/ , Python, 14 linesmirror.py - examples/
basic/ , Python, 28 linesmouseclick1.py - examples/
basic/ , Python, 33 linesmouseclick2.py - examples/
basic/ , Python, 30 linesmousehighlight.py - examples/
basic/ , Python, 26 linesmousehover0.py - examples/
basic/ , Python, 43 linesmousehover1.py - examples/
basic/ , Python, 28 linesmousehover2.py - examples/
basic/ , Python, 38 linesmousehover3.py - examples/
basic/ , Python, 26 linesmultirenderers.py - examples/
basic/ , Python, 52 linesmultiwindows1.py - examples/
basic/ , Python, 34 linesmultiwindows2.py - examples/
basic/ , Python, 21 linespca_ellipse.py - examples/
basic/ , Python, 49 linespca_ellipsoid.py - examples/
basic/ , Python, 13 linesrecord_play.py - examples/
basic/ , Python, 19 linesribbon.py - examples/
basic/ , Python, 20 linesrotate_image.py - examples/
basic/ , Shell, 10 linesrun_all.sh - examples/
basic/ , Python, 35 linesscalarbars.py - examples/
basic/ , Python, 13 linesshadow1.py - examples/
basic/ , Python, 20 linesshadow2.py - examples/
basic/ , Python, 17 linesshadow3.py - examples/
basic/ , Python, 11 linesshrink.py - examples/
basic/ , Python, 25 linessilhouette1.py - examples/
basic/ , Python, 50 linessilhouette2.py - examples/
basic/ , Python, 12 linessilhouette3.py - examples/
basic/ , Python, 15 linesskybox.py - examples/
basic/ , Python, 35 linesslider_browser.py - examples/
basic/ , Python, 39 linessliders1.py - examples/
basic/ , Python, 66 linessliders2.py - examples/
basic/ , Python, 30 linessliders3d.py - examples/
basic/ , Python, 95 linessliders_hsv.py - examples/
basic/ , Python, 50 linessliders_range.py - examples/
basic/ , Python, 33 linesspecular.py - examples/
basic/ , Python, 35 linesspline_tool.py - examples/
basic/ , Python, 23 linesssao.py - examples/
basic/ , Python, 17 linessurf_intersect.py - examples/
basic/ , Python, 35 linestexture_coords.py - examples/
basic/ , Python, 21 linestexturecubes.py - examples/
basic/ , Python, 28 linestube_radii.py - examples/
basic/ , Python, 19 linesvoronoi1.py - examples/
extras/ , Python, 49 lineschemistry1.py - examples/
extras/ , Python, 35 lineschemistry2.py - examples/
extras/ , Python, 23 linesclone2d.py - examples/
extras/ , Python, 28 linesellipt_fourier_desc.py - examples/
extras/ , Python, 22 linesexport_numpy.py - examples/
extras/ , Python, 39 linesexport_threejs.py - examples/
extras/ , Python, 22 linesexport_x3d.py - examples/
extras/ , Python, 40 linesfast_simpl.py - examples/
extras/ , Python, 30 linesflag_labels1.py - examples/
extras/ , Python, 17 linesflag_labels2.py - examples/
extras/ , Python, 17 linesicon.py - examples/
extras/ , Python, 19 linesimage_editor.py - examples/
extras/ , Python, 27 linesimage_false_colors.py - examples/
extras/ , Python, 24 linesimage_fft.py - examples/
extras/ , Python, 37 linesimage_probe.py - examples/
extras/ , Python, 31 linesimage_rgba.py - examples/
extras/ , Python, 35 linesimage_to_mesh.py - examples/
extras/ , Python, 34 linesiminuit1.py - examples/
extras/ , Python, 48 linesiminuit2.py - examples/
extras/ , Python, 49 linesinset.py - examples/
extras/ , Python, 79 linesmadcad1.py - examples/
extras/ , Python, 34 linesmadcad2.py - examples/
extras/ , Python, 46 linesmagic-class1.py - examples/
extras/ , Python, 54 linesmake_video.py - examples/
extras/ , Python, 15 linesmeshio_read.py - examples/
extras/ , Python, 143 linesmeshlib1.py - examples/
extras/ , Python, 51 linesmorphomatics_riemann.py - examples/
extras/ , Python, 45 linesmorphomatics_tube.py - examples/
extras/ , Python, 28 linesnapari1.py - examples/
extras/ , Python, 29 linesnelder-mead.py - examples/
extras/ , Python, 43 linesnevergrad_opt.py - examples/
extras/ , Python, 36 linesprintc.py - examples/
extras/ , Python, 18 linespygeodesic1.py - examples/
extras/ , Python, 36 linespygmsh_cut.py - examples/
extras/ , Python, 45 linespymeshlab1.py - examples/
extras/ , Python, 40 linespymeshlab2.py - examples/
extras/ , Python, 79 linespysr_regression.py - examples/
extras/ , Python, 54 linesqt_cutter.py - examples/
extras/ , Python, 119 linesqt_tabs.py - examples/
extras/ , Python, 57 linesqt_window1.py - examples/
extras/ , Python, 57 linesqt_window2.py - examples/
extras/ , Python, 57 linesqt_window3.py - examples/
extras/ , Python, 105 linesquaternions.py - examples/
extras/ , Python, 46 linesremesh_ACVD.py - examples/
extras/ , Python, 48 linesremesh_meshfix.py - examples/
extras/ , Shell, 14 linesrun_all.sh - examples/
extras/ , Python, 91 linesspherical_harmonics1.py - examples/
extras/ , Python, 13 linestensor_grid1.py - examples/
extras/ , Python, 60 linestetgen1.py - examples/
extras/ , Python, 51 linestrame_ex1.py - examples/
extras/ , Python, 54 linestrame_ex2.py - examples/
extras/ , Python, 97 linestrame_ex3.py - examples/
extras/ , Python, 35 linestrimesh_nearest.py - examples/
extras/ , Python, 41 linestrimesh_ray.py - examples/
extras/ , Python, 45 linestrimesh_section.py - examples/
extras/ , Python, 43 linestrimesh_shortest.py - examples/
extras/ , Python, 49 lineswx_window.py - examples/
notebooks/ , Jupyter, 29 linesalign1.ipynb - examples/
notebooks/ , Jupyter, 24 linesdistance2mesh.ipynb - examples/
notebooks/ , Jupyter, 36 linesinterpolate_volume.ipynb - examples/
notebooks/ , Jupyter, 23 lineslegosurface.ipynb - examples/
notebooks/ , Jupyter, 31 linesmanipulate_camera.ipynb - examples/
notebooks/ , Jupyter, 28 linesnumpy2volume.ipynb - examples/
notebooks/ , Jupyter, 29 linespca.ipynb - examples/
notebooks/ , Jupyter, 15 linesshrink.ipynb - examples/
notebooks/ , Jupyter, 62 linesslider2d.ipynb - examples/
notebooks/ , Jupyter, 19 linessphere.ipynb - examples/
notebooks/ , Jupyter, 67 linestest_types.ipynb - examples/
pyplot/ , Python, 44 linesandrews_cluster.py - examples/
pyplot/ , Python, 67 linesanim_lines.py - examples/
pyplot/ , Python, 25 linescaption.py - examples/
pyplot/ , Python, 54 linescustom_axes1.py - examples/
pyplot/ , Python, 39 linescustom_axes2.py - examples/
pyplot/ , Python, 30 linescustom_axes3.py - examples/
pyplot/ , Python, 43 linescustom_axes4.py - examples/
pyplot/ , Python, 49 linesearthquake_browser.py - examples/
pyplot/ , Python, 19 linesembed_matplotlib1.py - examples/
pyplot/ , Python, 72 linesembed_matplotlib2.py - examples/
pyplot/ , Python, 60 linesexplore5d.py - examples/
pyplot/ , Python, 36 linesfill_gap.py - examples/
pyplot/ , Python, 33 linesfit_circle.py - examples/
pyplot/ , Python, 32 linesfit_curve1.py - examples/
pyplot/ , Python, 45 linesfit_curve2.py - examples/
pyplot/ , Python, 51 linesfit_erf.py - examples/
pyplot/ , Python, 44 linesfit_polynomial1.py - examples/
pyplot/ , Python, 69 linesfit_polynomial2.py - examples/
pyplot/ , Python, 114 linesfonts3d.py - examples/
pyplot/ , Python, 29 linesgoniometer.py - examples/
pyplot/ , Python, 38 linesgraph_lineage.py - examples/
pyplot/ , Python, 51 linesgraph_network.py - examples/
pyplot/ , Python, 8 lineshisto_1d_a.py - examples/
pyplot/ , Python, 38 lineshisto_1d_b.py - examples/
pyplot/ , Python, 33 lineshisto_1d_c.py - examples/
pyplot/ , Python, 44 lineshisto_1d_d.py - examples/
pyplot/ , Python, 25 lineshisto_1d_e.py - examples/
pyplot/ , Python, 27 lineshisto_2d_a.py - examples/
pyplot/ , Python, 29 lineshisto_2d_b.py - examples/
pyplot/ , Python, 19 lineshisto_3d.py - examples/
pyplot/ , Python, 32 lineshisto_gauss.py - examples/
pyplot/ , Python, 21 lineshisto_hexagonal.py - examples/
pyplot/ , Python, 88 lineshisto_manual.py - examples/
pyplot/ , Python, 37 lineshisto_pca.py - examples/
pyplot/ , Python, 46 lineshisto_polar.py - examples/
pyplot/ , Python, 15 lineshisto_spheric.py - examples/
pyplot/ , Python, 27 lineshisto_violin.py - examples/
pyplot/ , Python, 34 linesintersect2d.py - examples/
pyplot/ , Python, 35 linesisolines.py - examples/
pyplot/ , Python, 16 lineslatex.py - examples/
pyplot/ , Python, 27 lineslines_intersect.py - examples/
pyplot/ , Python, 32 linesmarkers.py - examples/
pyplot/ , Python, 17 linesmarkpoint.py - examples/
pyplot/ , Python, 24 linesnp_matrix.py - examples/
pyplot/ , Python, 17 linespie_chart.py - examples/
pyplot/ , Python, 33 linesplot_bars.py - examples/
pyplot/ , Python, 26 linesplot_density2d.py - examples/
pyplot/ , Python, 23 linesplot_density3d.py - examples/
pyplot/ , Python, 42 linesplot_density4d.py - examples/
pyplot/ , Python, 37 linesplot_empty.py - examples/
pyplot/ , Python, 47 linesplot_errband.py - examples/
pyplot/ , Python, 46 linesplot_errbars.py - examples/
pyplot/ , Python, 63 linesplot_extra_yaxis.py - examples/
pyplot/ , Python, 61 linesplot_fxy0.py - examples/
pyplot/ , Python, 58 linesplot_fxy1.py - examples/
pyplot/ , Python, 115 linesplot_fxy2.py - examples/
pyplot/ , Python, 25 linesplot_hexcells.py - examples/
pyplot/ , Python, 24 linesplot_multi.py - examples/
pyplot/ , Python, 45 linesplot_pip.py - examples/
pyplot/ , Python, 38 linesplot_polar.py - examples/
pyplot/ , Python, 27 linesplot_spheric.py - examples/
pyplot/ , Python, 36 linesplot_stream.py - examples/
pyplot/ , Python, 13 linesquiver.py - examples/
pyplot/ , Shell, 14 linesrun_all.sh - examples/
pyplot/ , Python, 21 linesscatter1.py - examples/
pyplot/ , Python, 57 linesscatter2.py - examples/
pyplot/ , Python, 52 linesscatter3.py - examples/
pyplot/ , Python, 22 linesscatter_large.py - examples/
pyplot/ , Python, 14 linestriangulate2d.py - examples/
pyplot/ , Python, 46 lineswhiskers.py - examples/
run_all.sh , Shell, 19 lines - examples/
volumetric/ , Python, 11 linesapp_isobrowser.py - examples/
volumetric/ , Python, 13 linesapp_raycaster.py - examples/
volumetric/ , Python, 34 linescolorize_volume.py - examples/
volumetric/ , Python, 28 linesdelaunay3d.py - examples/
volumetric/ , Python, 19 linesdensifycloud.py - examples/
volumetric/ , Python, 84 linesearth_model.py - examples/
volumetric/ , Python, 13 lineserode_dilate.py - examples/
volumetric/ , Python, 12 lineseuclidian_dist.py - examples/
volumetric/ , Python, 36 linesinterpolate_volume.py - examples/
volumetric/ , Python, 19 linesisosurfaces1.py - examples/
volumetric/ , Python, 53 linesisosurfaces2.py - examples/
volumetric/ , Python, 16 lineslegosurface.py - examples/
volumetric/ , Python, 18 lineslowpassfilter.py - examples/
volumetric/ , Python, 17 linesmesh2volume.py - examples/
volumetric/ , Python, 39 linesmultiscalars.py - examples/
volumetric/ , Python, 13 linesnumpy2volume0.py - examples/
volumetric/ , Python, 21 linesnumpy2volume1.py - examples/
volumetric/ , Python, 16 linesnumpy2volume2.py - examples/
volumetric/ , Python, 25 linesnumpy_imread.py - examples/
volumetric/ , Python, 245 linesoff_furniture.py - examples/
volumetric/ , Python, 23 linesoffice.py - examples/
volumetric/ , Python, 22 linesprobe_line1.py - examples/
volumetric/ , Python, 34 linesprobe_line2.py - examples/
volumetric/ , Python, 19 linesprobe_points.py - examples/
volumetric/ , Python, 28 linesread_volume1.py - examples/
volumetric/ , Python, 32 linesread_volume2.py - examples/
volumetric/ , Python, 14 linesread_volume3.py - examples/
volumetric/ , Python, 21 linesrectl_grid1.py - examples/
volumetric/ , Shell, 14 linesrun_all.sh - examples/
volumetric/ , Python, 22 linesslab_vol.py - examples/
volumetric/ , Python, 18 linesslice_mesh.py - examples/
volumetric/ , Python, 33 linesslice_plane1.py - examples/
volumetric/ , Python, 24 linesslice_plane2.py - examples/
volumetric/ , Python, 39 linesslice_plane3.py - examples/
volumetric/ , Python, 22 linesslicer1.py - examples/
volumetric/ , Python, 45 linesslicer2.py - examples/
volumetric/ , Python, 65 linesslicer_set_volume.py - examples/
volumetric/ , Python, 37 linesstreamlines1.py - examples/
volumetric/ , Python, 15 linesstreamlines2.py - examples/
volumetric/ , Python, 37 linesstreamlines2_cylinderwid get.py - examples/
volumetric/ , Python, 39 linesstreamlines2_linewidget. py - examples/
volumetric/ , Python, 35 linesstreamlines2_pointwidget .py - examples/
volumetric/ , Python, 35 linesstreamlines2_spherewidge t.py - examples/
volumetric/ , Python, 23 linesstreamlines3.py - examples/
volumetric/ , Python, 25 linesstreamlines4.py - examples/
volumetric/ , Python, 27 linesstruc_grid1.py - examples/
volumetric/ , Python, 31 linestensors.py - examples/
volumetric/ , Python, 18 linestet_astyle.py - examples/
volumetric/ , Python, 44 linestet_build.py - examples/
volumetric/ , Python, 19 linestet_cut1.py - examples/
volumetric/ , Python, 38 linestet_cut2.py - examples/
volumetric/ , Python, 40 linestet_explode.py - examples/
volumetric/ , Python, 25 linestet_isos_slice.py - examples/
volumetric/ , Python, 17 linestet_threshold.py - examples/
volumetric/ , Python, 31 linestetralize_surface.py - examples/
volumetric/ , Python, 13 linesugrid1.py - examples/
volumetric/ , Python, 12 linesugrid2.py - examples/
volumetric/ , Python, 13 linesvol2points.py - examples/
volumetric/ , Python, 16 linesvolume_from_mesh.py - examples/
volumetric/ , Python, 59 linesvolume_operations.py - examples/
volumetric/ , Python, 27 linesvolume_sharemap.py - examples/
volumetric/ , Python, 15 lineswarp_scalars.py - tests/
common/ , Shell, 14 linesrun_all.sh - tests/
common/ , Python, 10 linestest_0_imports.py - tests/
common/ , Python, 28 linestest_bins.py - tests/
common/ , Python, 59 linestest_chemistry_protein.p y - tests/
common/ , Python, 156 linestest_cli.py - tests/
common/ , Python, 78 linestest_cylinder_widget.py - tests/
common/ , Python, 66 linestest_data_format.py - tests/
common/ , Python, 182 linestest_dataset_algorithms. py - tests/
common/ , Python, 71 linestest_explicit_grid.py - tests/
common/ , Python, 28 linestest_file_io_cube.py - tests/
common/ , Python, 73 linestest_file_io_opendx.py - tests/
common/ , Python, 30 linestest_file_io_writers.py - tests/
common/ , Python, 26 linestest_hist.py - tests/
common/ , Python, 100 linestest_keymap.py - tests/
common/ , Python, 63 linestest_lazy_exports.py - tests/
common/ , Python, 90 linestest_line_widget.py - tests/
common/ , Python, 50 linestest_logging_rich.py - tests/
common/ , Python, 177 linestest_mesh_core.py - tests/
common/ , Python, 18 linestest_numpy_hist.py - tests/
common/ , Python, 18 linestest_numpy_hist2.py - tests/
common/ , Python, 15 linestest_numpy_hist3.py - tests/
common/ , Python, 59 linestest_plotter_dry_run.py - tests/
common/ , Python, 126 linestest_plotter_modes.py - tests/
common/ , Python, 31 linestest_plotter_scene.py - tests/
common/ , Python, 44 linestest_point_widget.py - tests/
common/ , Python, 152 linestest_pointcloud_analyze. py - tests/
common/ , Python, 139 linestest_pointcloud_fits.py - tests/
common/ , Python, 122 linestest_pyplot.py - tests/
common/ , Python, 390 linestest_pyplot_functions.py - tests/
common/ , Python, 26 linestest_pyplot_stats.py - tests/
common/ , Python, 27 linestest_scalarbar.py - tests/
common/ , Python, 233 linestest_shapes.py - tests/
common/ , Python, 80 linestest_slicer3d_plotter.py - tests/
common/ , Python, 20 linestest_smoke_imports.py - tests/
common/ , Python, 66 linestest_sphere_widget.py - tests/
common/ , Python, 30 linestest_summary_rich.py - tests/
common/ , Python, 77 linestest_summary_rich_rollou t.py - tests/
common/ , Python, 140 linestest_text2d_and_colors.p y - tests/
common/ , Python, 40 linestest_trame_backend.py - tests/
common/ , Python, 113 linestest_transformations.py - tests/
common/ , Python, 60 linestest_transformations_qua ternion.py - tests/
common/ , Python, 34 linestest_utils.py - tests/
common/ , Python, 95 linestest_volume_api.py - tests/
common/ , Python, 72 linestest_vtkclasses_slow_not ice.py - tests/
conftest.py , Python, 31 lines - tests/
issues/ , Python, 37 linesdiscussion_1190.py - tests/
issues/ , Python, 37 linesdiscussion_527.py - tests/
issues/ , Python, 16 linesdiscussion_716.py - tests/
issues/ , Python, 26 linesdiscussion_751.py - tests/
issues/ , Python, 13 linesdiscussion_784.py - tests/
issues/ , Python, 36 linesdiscussion_800.py - tests/
issues/ , Python, 68 linesdiscussion_942.py - tests/
issues/ , Python, 56 linesdiscussion_978.py - tests/
issues/ , Python, 43 linesissue_1025.py - tests/
issues/ , Python, 16 linesissue_1077.py - tests/
issues/ , Python, 46 linesissue_1109.py - tests/
issues/ , Python, 127 linesissue_1118.py - tests/
issues/ , Python, 59 linesissue_1146.py - tests/
issues/ , Python, 94 linesissue_1218.py - tests/
issues/ , Python, 169 linesissue_1221.py - tests/
issues/ , Python, 36 linesissue_1230.py - tests/
issues/ , Python, 9 linesissue_656.py - tests/
issues/ , Python, 15 linesissue_805.py - tests/
issues/ , Python, 13 linesissue_851.py - tests/
issues/ , Python, 18 linesissue_854.py - tests/
issues/ , Python, 18 linesissue_856.py - tests/
issues/ , Python, 123 linesissue_871a.py - tests/
issues/ , Python, 31 linesissue_871b.py - tests/
issues/ , Python, 33 linesissue_893.py - tests/
issues/ , Python, 59 linesissue_905.py - tests/
issues/ , Python, 35 linesissue_908.py - tests/
issues/ , Python, 5 linesissue_939.py - tests/
issues/ , Python, 58 linesissue_946.py - tests/
issues/ , Python, 35 linesissue_948.py - tests/
issues/ , Python, 17 linesissue_950.py - tests/
issues/ , Python, 58 linesissue_953.py - tests/
issues/ , Python, 10 linesissue_968.py - tests/
issues/ , Shell, 11 linesrun_all.sh - tests/
issues/ , Python, 76 linestest_force_anim.py - tests/
issues/ , Python, 41 linestest_fxy_bessel1.py - tests/
issues/ , Python, 37 linestest_remove_objects.py - tests/
issues/ , Python, 97 linestest_sph_harm2.py - tests/
snippets/ , Shell, 11 linesrun_all.sh - tests/
snippets/ , Python, 177 linestest_cell_types.py - tests/
snippets/ , Python, 50 linestest_closewindow.py - tests/
snippets/ , Python, 81 linestest_compare_fit1.py - tests/
snippets/ , Python, 9 linestest_discourse_1956.py - tests/
snippets/ , Python, 350 linestest_docs_sniplets.py - tests/
snippets/ , Python, 70 linestest_elastic_pendulum.py - tests/
snippets/ , Python, 41 linestest_ellipsoid_main_axes .py - tests/
snippets/ , Python, 67 linestest_fit_shapiro.py - tests/
snippets/ , Python, 26 linestest_interactive_plotxy1 .py - tests/
snippets/ , Python, 66 linestest_interactive_plotxy2 .py - tests/
snippets/ , Python, 18 linestest_project_point_on_va riety.py - vedo/
__init__.py , Python, 525 lines - vedo/
addons/ , Python, 47 lines__init__.py - vedo/
addons/ , Python, 1,833 linesaxes.py - vedo/
addons/ , Python, 690 linescore.py - vedo/
addons/ , Python, 730 linescutters.py - vedo/
addons/ , Python, 1,129 linesinteraction.py - vedo/
addons/ , Python, 581 linesmeasure.py - vedo/
addons/ , Python, 463 linessliders.py - vedo/
addons/ , Python, 423 linesui.py - vedo/
addons/ , Python, 318 lineswidgets.py - vedo/
applications/ , Python, 40 lines__init__.py - vedo/
applications/ , Python, 757 linesanimation.py - vedo/
applications/ , Python, 345 linesbrowsers.py - vedo/
applications/ , Python, 752 lineschemistry.py - vedo/
applications/ , Python, 607 linesediting.py - vedo/
applications/ , Python, 827 linesmorphing.py - vedo/
applications/ , Python, 1,089 linesslicing.py - vedo/
assembly.py , Python, 707 lines - vedo/
backends.py , Python, 595 lines - vedo/
cli.py , Python, 1,635 lines - vedo/
colors.py , Python, 1,378 lines - vedo/
core/ , Python, 22 lines__init__.py - vedo/
core/ , Python, 1,415 linescommon.py - vedo/
core/ , Python, 377 linesdata.py - vedo/
core/ , Python, 70 linesinput.py - vedo/
core/ , Python, 295 linespoints.py - vedo/
core/ , Python, 122 linessummary.py - vedo/
core/ , Python, 1,556 linestransformations.py - vedo/
core/ , Python, 289 linesvolume.py - vedo/
external/ , Python, 29 lines__init__.py - vedo/
external/ , Python, 357 linesconversions.py - vedo/
file_io/ , Python, 50 lines__init__.py - vedo/
file_io/ , Python, 26 linesexport_npy.py - vedo/
file_io/ , Python, 1,005 linesexport_threejs.py - vedo/
file_io/ , Python, 155 linesexport_x3d.py - vedo/
file_io/ , Python, 1,090 linesloaders.py - vedo/
file_io/ , Python, 107 linesnetwork.py - vedo/
file_io/ , Python, 588 linesscene.py - vedo/
file_io/ , Python, 58 linesterminal.py - vedo/
file_io/ , Python, 245 linesvideo.py - vedo/
file_io/ , Python, 200 lineswriters.py - vedo/
grids/ , Python, 23 lines__init__.py - vedo/
grids/ , Python, 486 linesexplicit.py - vedo/
grids/ , Python, 1,584 linesimage.py - vedo/
grids/ , Python, 526 linesrectilinear.py - vedo/
grids/ , Python, 487 linesstructured.py - vedo/
grids/ , Python, 527 linestetmesh.py - vedo/
grids/ , Python, 969 linesunstructured.py - vedo/
lazy_imports.py , Python, 47 lines - vedo/
mesh/ , Python, 17 lines__init__.py - vedo/
mesh/ , Python, 2,884 linescore.py - vedo/
mesh/ , Python, 399 linesmetrics.py - vedo/
plotter/ , Python, 16 lines__init__.py - vedo/
plotter/ , Python, 270 linescamera.py - vedo/
plotter/ , Python, 91 linesevents.py - vedo/
plotter/ , Python, 563 linesinteraction.py - vedo/
plotter/ , Python, 78 linesio.py - vedo/
plotter/ , Python, 1,092 lineskeymap.py - vedo/
plotter/ , Python, 271 lineslifecycle.py - vedo/
plotter/ , Python, 1,571 linesmodes.py - vedo/
plotter/ , Python, 2,583 linesruntime.py - vedo/
plotter/ , Python, 289 linesscene.py - vedo/
plotter/ , Python, 56 linessession.py - vedo/
pointcloud/ , Python, 33 lines__init__.py - vedo/
pointcloud/ , Python, 1,257 linesanalyze.py - vedo/
pointcloud/ , Python, 500 linescore.py - vedo/
pointcloud/ , Python, 658 linescut.py - vedo/
pointcloud/ , Python, 589 linesfits.py - vedo/
pointcloud/ , Python, 675 linesreconstruct.py - vedo/
pointcloud/ , Python, 487 linestransform.py - vedo/
pyplot/ , Python, 24 lines__init__.py - vedo/
pyplot/ , Python, 1,263 linescharts.py - vedo/
pyplot/ , Python, 655 linesfigure.py - vedo/
pyplot/ , Python, 1,695 linesfunctions.py - vedo/
pyplot/ , Python, 375 linesgraph.py - vedo/
pyplot/ , Python, 585 linesstats.py - vedo/
settings.py , Python, 816 lines - vedo/
shapes/ , Python, 88 lines__init__.py - vedo/
shapes/ , Python, 95 linesanalysis.py - vedo/
shapes/ , Python, 47 linesbranding.py - vedo/
shapes/ , Python, 54 linescurves.py - vedo/
shapes/ , Python, 1,538 linescurves_core.py - vedo/
shapes/ , Python, 602 linescurves_extras.py - vedo/
shapes/ , Python, 253 linesglyphs.py - vedo/
shapes/ , Python, 103 lineslatex.py - vedo/
shapes/ , Python, 425 linesmarkers.py - vedo/
shapes/ , Python, 64 linesprimitives.py - vedo/
shapes/ , Python, 693 linesprimitives_planar.py - vedo/
shapes/ , Python, 756 linesprimitives_solids.py - vedo/
shapes/ , Python, 15 linestext.py - vedo/
shapes/ , Python, 345 linestext2d.py - vedo/
shapes/ , Python, 370 linestext3d.py - vedo/
shapes/ , Python, 164 linestext_utils.py - vedo/
transformations.py , Python, 7 lines - vedo/
utils.py , Python, 2,883 lines - vedo/
visual/ , Python, 29 lines__init__.py - vedo/
visual/ , Python, 1,061 linesmixins.py - vedo/
visual/ , Python, 2,099 linesruntime.py - vedo/
volume/ , Python, 17 lines__init__.py - vedo/
volume/ , Python, 1,513 linescore.py - vedo/
volume/ , Python, 273 linesslicing.py - vedo/
vtkclasses.py , Python, 1,065 lines - LICENSE, License, 21 lines
- README.md, Text, 306 lines
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:
- 4 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 636 scripts, each with its path and the digest of its content;
- 7 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
Datasets cited
- doi:10.48324/
dandi.000774/ , at DANDI; found in the resources table0.260520.1753
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 → Cell Press
Version 1, 28 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 5 authors, 9 keywords, 10 MeSH terms, 5 funders, 120 references, 1 RRID.
Cite
This paper
Hickman, J. L., Hughes, G., Sahai, E., Miles, M., & Denman, D. J. (2026). Neural population dynamics of direct electrical stimulation of neocortex. Cell reports, 45(6), 117420. https://
BibTeX
@article{hickman2026neur
author = {Hickman, Jordan L and Hughes, Grant and Sahai, Eashan and Miles, Moriah and Denman, Daniel J},
title = {{Neural population dynamics of direct electrical stimulation of neocortex}},
journal = {Cell reports},
year = {2026},
month = may,
volume = {45},
number = {6},
pages = {117420},
publisher = {Cell Press},
issn = {2211-1247},
doi = {10.1016/
url = {https://
pmid = {42207642},
pmcid = {PMC13404931}
}
RIS
TY - JOUR
AU - Hickman, Jordan L
AU - Hughes, Grant
AU - Sahai, Eashan
AU - Miles, Moriah
AU - Denman, Daniel J
TI - Neural population dynamics of direct electrical stimulation of neocortex
T2 - Cell reports
J2 - Cell Rep
PY - 2026
DA - 2026/
VL - 45
IS - 6
SP - 117420
SN - 2211-1247
PB - Cell Press
DO - 10.1016/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1016/
"type": "article-journal",
"title": "Neural population dynamics of direct electrical stimulation of neocortex",
"container-title": "Cell reports",
"author": [
{
"family": "Hickman",
"given": "Jordan L"
},
{
"family": "Hughes",
"given": "Grant"
},
{
"family": "Sahai",
"given": "Eashan"
},
{
"family": "Miles",
"given": "Moriah"
},
{
"family": "Denman",
"given": "Daniel J"
}
],
"container-title-short":
"volume": "45",
"issue": "6",
"page": "117420",
"DOI": "10.1016/
"PMID": "42207642",
"PMCID": "PMC13404931",
"ISSN": "2211-1247",
"publisher": "Cell Press",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
5,
28
]
]
}
}
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.1126/sciadv.aef0343 [code]
- Learning induces activation-mechanism-dep
endent neural plasticity in an intracortical microstimulation task. Journal: Science advancesIn common: Pillow, statsmodels, seaborn, 5 other tools, 14 references - [2] doi:10.7554/elife.109717 [code]
- Retrosplenial cortex enables context-dependent goal-directed sensorimotor transformation.Journal: eLifeIn common: Neurodata Without Borders (PyNWB, MatNWB), imageio, OpenCV, 9 other tools, systems, mouse, 2 references
- [3] doi:10.1038/s41593-026-02262-8 [code]
- Cheese3D enables sensitive detection and analysis of whole-face movement in mice.Journal: Nature neuroscienceIn common: Open Ephys analysis tools, napari, JAX, 9 other tools, systems, mouse
- [4] doi: [code]
- Real-time closed-loop feedback system for mouse mesoscale cortical signal and movement controlJournal: eLifeIn common: napari, imageio, OpenCV, 9 other tools, mouse, 2 references
- [5] doi:10.1038/s41593-026-02232-0 [code]
- Entorhinal cortex represents task-relevant remote locations independently of CA1.Journal: Nature neuroscienceIn common: Neurodata Without Borders (PyNWB, MatNWB), NetworkX, OpenCV, 9 other tools, systems, mouse, 2 references
- [6] doi: [code]
- Naturalistic behavior and self-generated neural activity predictive of self-correctionJournal: bioRxiv : the preprint server for biologyIn common: Neurodata Without Borders (PyNWB, MatNWB), JAX, NetworkX, 10 other tools
- [7] doi:10.1371/journal.pcbi.1014571 [code]
- SynAPSeg: A novel dataset and image analysis framework for deep learning-based synapse detection and quantification.Journal: PLoS computational biologyIn common: napari, imageio, NetworkX, 10 other tools
- [8] doi:10.1016/j.crmeth.2026.101421 [code]
- EthoPy provides an accessible platform for reproducible behavioral neuroscience.Journal: Cell reports methodsIn common: Neurodata Without Borders (PyNWB, MatNWB), imageio, OpenCV, 6 other tools, mouse, 3 references
- [9] doi:10.3389/fncom.2026.1876230 [code]
- Linking spatially distributed neuronal activation overlap to the limits of perceptual discrimination in rodent primary somatosensory cortex.Journal: Frontiers in computational neuroscienceIn common: Matplotlib, NumPy, 8 references
- [10] doi:10.1038/s42003-026-10957-8 [code]
- Brain defence by the extracellular matrix protein Cochlin.Journal: Communications biologyIn common: JAX, NetworkX, OpenCV, 9 other tools, mouse
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: 4 repositories of the authors' code, each at its verified commit and with its license, 636 scripts, and 7 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:fe3852569d11218b…
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.
