OSCR

Osmotic stress triggers fast and reversible PMF collapse in Escherichia coli.

Code ↔ Paper

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

The 10 matches
  1. [1] § Materials and methods › Curve fitting ↔ code/bead-assay/sucrose_shock_analysis.ipynb, lines 685–758 · score 0.85 · Green triangles, Red circles, linear fit, viscosity driven, Normalized speed reduction, sucrose shocks
  2. [2] § Materials and methods › Curve fitting ↔ code/bead-assay/sucrose_shock_analysis.ipynb, lines 403–490 · score 0.79 · inflection, 155 s, 215 s, 235 s, 240 s, sigmoidal
  3. [3] § Materials and methods › Curve fitting ↔ code/bead-assay/adaptation_curve_fitting.ipynb, lines 105–155 · score 0.72 · 175–240 s, 175 s, fit, motor speed, Curve
  4. [4] § Results › Rotation speed decrease does not require potassium ions ↔ code/bead-assay/sucrose_shock_analysis.ipynb, lines 685–758 · score 0.71 · Red circles, linear fit, Normalized speed reduction, normalized speed decrease, sucrose shocks, SD
  5. [5] § Materials and methods › Curve fitting ↔ code/bead-assay/bead_shock_curve_fits.ipynb, lines 36–107 · score 0.63 · exponential function, speed increase, normalized speed, fit, Curve, motor speed
  6. [6] § Materials and methods › Curve fitting ↔ code/bead-assay/adaptation_curve_fitting.ipynb, lines 264–334 · score 0.59 · speeds normalized, 600 s, exponential, plateau, fit, 250 s
  7. [7] § Results › Motor speed response is independent of rotor conformation ↔ code/bead-assay/sucrose_shock_analysis.ipynb, lines 761–811 · score 0.58 · Blue squares, viscosity corrected speed, sucrose shocks, speed decrease, SD, osmolarity
  8. [8] § Materials and methods › TMRM quantification ↔ code/TMRM-anaylsis/analyze_tmrm_population_dff.py, lines 66–88 · score 0.51 · analyze TMRM fluorescence, pre shock, background, ratio, signal, baseline
  9. [9] § Materials and methods › TMRM quantification ↔ code/TMRM-anaylsis/analyze_tmrm_single_condition_dff.py, lines 40–62 · score 0.51 · analyze TMRM fluorescence, pre shock, background, ratio, signal, baseline
  10. [10] § Materials and methods › Single-motor measurements ↔ code/bead-assay/sucrose_shock_analysis.ipynb, lines 403–490 · score 0.50 · single motors, Raw, median, rotation speed, mask, filter

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 · 924 lines · 34 KB · no license · 5 matches

  1. # %% [markdown]
  2. # # Sucrose Shock Analysis
  3. #
  4. # Status: cleaned for the publication supplement. Supported cells start from the curated time-series data committed under `data/time-series/` and write generated files under `outputs/` directories.
  5. #
  6. # Upstream image/video processing, raw acquisition data, segmentation outputs, bead-center detection outputs, and other large intermediates are intentionally not included in this repository. Cells from the original notebook that depended on those local upstream files have been replaced by archived notes.
  7. # %% [markdown]
  8. # ### 📦 Importing Required Libraries
  9. #
  10. # This cell imports all the necessary Python libraries for data analysis and plotting:
  11. #
  12. # - `numpy`, `pandas` for data handling
  13. # - `matplotlib.pyplot`, `seaborn` for plotting and visualization
  14. # - `os`, `pathlib.Path` for file handling
  15. # - `scipy` libraries for signal processing and curve fitting
  16. # - `sklearn.metrics.r2_score` for computing the R² value
  17. # - `%matplotlib inline` ensures that plots display directly in the notebook
  18. # %%
  19. # Import necessary libraries
  20. import numpy as np
  21. import pandas as pd
  22. import matplotlib.pyplot as plt
  23. import seaborn as sns
  24. import os
  25. from pathlib import Path
  26. import scipy
  27. from scipy.optimize import curve_fit
  28. import scipy.signal
  29. import re
  30. from scipy.interpolate import interp1d
  31. import scipy.signal as signal
  32. from sklearn.metrics import r2_score
  33. import sys
  34. NOTEBOOK_DIR = Path.cwd()
  35. if (NOTEBOOK_DIR / "bead_time_series.py").exists():
  36. sys.path.insert(0, str(NOTEBOOK_DIR))
  37. else:
  38. sys.path.insert(0, str((NOTEBOOK_DIR / "code" / "bead-assay").resolve()))
  39. from bead_time_series import ROOT, legacy_speed_tree, legacy_condition_folder, read_legacy_speed_csv, iter_condition_traces, median_kernel_size
  40. # Ensure plots are rendered in the notebook
  41. %matplotlib inline
  42. # %% [markdown]
  43. # ## Defining terms for the exponential function
  44. # %%
  45. dict_for_plotting = {}
  46. for name in ['0', '100', '200', '300', '400', '500']:
  47. dict_for_plotting[name] = []
  48. # %%
  49. # Define exponential functions
  50. def old_exp_decrease(t, A, tau, C, t0):
  51. return A / (1 + np.exp((t-175-t0) / tau)) + C
  52. def exp_decrease(t, tau, t0):
  53. global speed_initial
  54. global speed_final
  55. return (speed_initial-speed_final) / (1 + np.exp((t-175-t0) / tau)) + speed_final
  56. def exp_increase(t, tau, t0):
  57. global speed_increase_max
  58. global speed_increase_min
  59. return speed_increase_min + (speed_increase_max - speed_increase_min)/ (1 + np.exp(-(t-270-t0) / tau))
  60. def old_exp_increase(t, B, tau, D, t0):
  61. return B * (1 - np.exp(-(t - t0) / tau)) + D
  62. def exponential_decay(t, tau, t0):
  63. global speed_initial
  64. global speed_final
  65. return np.where(t < t0, speed_initial, (speed_initial-speed_final) * np.exp(-(t - t0) / tau) + speed_final)
  66. def calculate_time_constants(time, speed, filename, sucrose_choice):
  67. results = []
  68. #normalize speed for ease of fitting
  69. speed = speed / np.average(speed[time<=180]) #normally 300
  70. # Filter data for decrease and increase
  71. mask_decrease = (time >= 175) & (time <= 240)
  72. time_decrease = time[mask_decrease]
  73. speed_decrease = speed[mask_decrease]
  74. mask_increase = (time > 250) & (time <= 360)
  75. time_increase = time[mask_increase]
  76. speed_increase = speed[mask_increase]
  77. global speed_initial
  78. global speed_final
  79. speed_initial = np.average(speed[(time >= 155) & (time <= 175)])
  80. speed_final = np.average(speed[(time >= 215) & (time <= 235)])
  81. global speed_increase_max
  82. global speed_increase_min
  83. speed_increase_min = np.average(speed[(time >= 240) & (time <= 260)])
  84. speed_increase_max = np.average(speed[(time >= 330) & (time <= 350)])
  85. global dict_for_plotting
  86. dict_for_plotting[filename].append(speed[(time >= 155) & (time <= 175)])
  87. dict_for_plotting['0'].append(speed[(time >= 215) & (time <= 235)])
  88. #Fit the decrease segment
  89. try:
  90. popt_decrease, _ = curve_fit(
  91. exp_decrease,
  92. time_decrease,
  93. speed_decrease,
  94. p0=[10, 10],
  95. bounds=(0, np.inf)
  96. )
  97. A_dec, C_dec = speed_initial-speed_final, speed_final
  98. tau_dec, t0_dec = popt_decrease
  99. except Exception as e:
  100. print(f"Error fitting decrease for {filename}: {e}")
  101. tau_dec = np.nan
  102. A_dec, tau_dec, C_dec, t0_dec = [np.nan, np.nan, np.nan, np.nan]
  103. popt_decrease = [np.nan, np.nan, np.nan, np.nan]
  104. if True: #plotting to double check
  105. if filename == "500": #filename[-13:-10]
  106. #print('here')
  107. plt.plot(time, speed)
  108. plt.plot(time_decrease, exp_decrease(time_decrease, *popt_decrease))
  109. # Fit the increase segment
  110. try:
  111. popt_increase, _ = curve_fit(
  112. exp_increase, time_increase, speed_increase, p0=[3, -10]
  113. )
  114. tau_inc, t0_inc = popt_increase
  115. except Exception as e:
  116. print(f"Error fitting increase for {filename}: {e}")
  117. tau_inc = np.nan
  118. popt_increase = [np.nan, np.nan]
  119. if True:
  120. if filename == "500":
  121. plt.plot(time_increase, exp_increase(time_increase, *popt_increase))
  122. # Save results for this concentration
  123. condition = filename.split(".csv")[
  124. 0
  125. ] # Assuming the filename contains the concentration info
  126. results.append(
  127. {
  128. "Condition": condition,
  129. "Tau (Decrease)": tau_dec,
  130. "Tau (Increase)": tau_inc,
  131. "Time Range (Decrease)": f"180-270",
  132. "Time Range (Increase)": f"270-{time[-1]}",
  133. }
  134. )
  135. return tau_dec, t0_dec, A_dec, tau_inc
  136. # %% [markdown]
  137. # ### Data Processing Functions
  138. #
  139. # This section defines three functions used to process time-series data by interpolating to a fixed frame rate and binning into defined intervals:
  140. # %%
  141. def convert_to_300fps(time_stamps, data, target_fps=300):# Calculate the total duration
  142. total_time = time_stamps[-1]
  143. # Generate new time stamps at the target FPS
  144. new_time_stamps = np.linspace(0, total_time, int(total_time * target_fps))
  145. # Create a linear interpolator
  146. interpolator = interp1d(time_stamps, data, kind='linear', fill_value='extrapolate')
  147. # Interpolate data to the new time stamps
  148. interpolated_data = interpolator(new_time_stamps)
  149. return new_time_stamps[:380*target_fps], interpolated_data[:380*target_fps]
  150. def bin_data(time_stamps, data, bin_size=0.1):
  151. data = data[time_stamps < 380]
  152. time_stamps = time_stamps[time_stamps < 380]
  153. # Determine the range of time stamps
  154. start_time = 0
  155. end_time = 380
  156. # Create bin edges
  157. bin_edges = np.arange(start_time, end_time + bin_size, bin_size)
  158. # Calculate bin indices for each time stamp
  159. bin_indices = np.digitize(time_stamps, bin_edges, right=False) - 1 # Bin indices (0-based)
  160. # Initialize lists to store results
  161. binned_times = np.arange(0,end_time,0.1)
  162. averaged_data = np.zeros_like(binned_times)
  163. # Iterate over each bin
  164. for i in range(len(bin_edges) - 1):
  165. # Get indices of data points in the current bin
  166. indices_in_bin = np.where(bin_indices == i)[0]
  167. if len(indices_in_bin) > 0:
  168. # Calculate the midpoint of the current bin
  169. bin_midpoint = (bin_edges[i] + bin_edges[i + 1]) / 2
  170. binned_times[i] = (bin_midpoint)
  171. # Calculate the average of data points in the current bin
  172. average_value = np.mean(data[indices_in_bin])
  173. averaged_data[i] = (average_value)
  174. return np.array(binned_times), np.array(averaged_data)
  175. def bin_data_2(time_stamps, data, bin_size=0.1):
  176. T = time_stamps[time_stamps<=380]
  177. F = data[time_stamps<=380]
  178. # Resample into 4000 bins
  179. data_F = np.zeros(3800)
  180. data_T = np.zeros(3800)
  181. bin = 0
  182. counter = 0
  183. for kk in range(len(T)):
  184. counter += 1
  185. pos = int(T[kk] // 0.1)
  186. if pos < 3800:
  187. data_F[pos] += F[kk]
  188. if T[kk] // 0.1 > bin + 1:
  189. data_F[pos - 1] /= counter
  190. data_T[pos - 1] = (bin + 0.5) * 0.1
  191. counter = 0
  192. bin += 1
  193. else:
  194. data_F[pos - 1] /= counter
  195. data_T[pos - 1] = (bin + 0.5) * 0.1
  196. break
  197. return data_T, data_F
  198. # %% [markdown]
  199. # ## Sucrose
  200. # Extracing motor speeds from the sucrose results folder
  201. # %%
  202. parentDir = legacy_speed_tree(assay="Sucrose")
  203. outputDir = ROOT / "outputs" / "bead" / "sucrose-shock"
  204. os.makedirs(outputDir, exist_ok=True)
  205. sucrose_choice = True
  206. time_mult = 10
  207. # Initialize the list to store time, data, and sucrose concentrations
  208. all_time = []
  209. all_data = []
  210. concentrations = []
  211. tau_values = {}
  212. t0_values = {}
  213. A_values = {}
  214. tau_inc_values = {}
  215. for concentration in [200, 300, 400, 500]:
  216. condition_dir = Path(parentDir) / f"{concentration}mM"
  217. csv_files = sorted(condition_dir.glob("*.csv"))
  218. if not csv_files:
  219. print(f"No traces found for {concentration} mM in {condition_dir}")
  220. continue
  221. tau_values[str(concentration)] = []
  222. t0_values[str(concentration)] = []
  223. A_values[str(concentration)] = []
  224. tau_inc_values[str(concentration)] = []
  225. condition_cells = []
  226. for file_path in csv_files:
  227. if concentration == 200 and "cell4" in file_path.stem:
  228. continue
  229. trace = read_legacy_speed_csv(file_path)
  230. time = trace["frame"].to_numpy() / 300
  231. data = trace["frequency_hz"].to_numpy()
  232. if len(data) == 0:
  233. continue
  234. if data[0] < 0:
  235. data = data * -1
  236. data = signal.medfilt(data, kernel_size=median_kernel_size(time, window_s=1.0))
  237. new_time, interp_data = bin_data(time, data)
  238. tau_dec, t0_dec, A_dec, tau_inc = calculate_time_constants(new_time, interp_data, str(concentration), sucrose_choice)
  239. if ((tau_dec > 0) & (tau_dec < 40)):
  240. tau_values[str(concentration)] += [float(tau_dec)]
  241. t0_values[str(concentration)] += [float(t0_dec)]
  242. A_values[str(concentration)] += [float(A_dec)]
  243. tau_inc_values[str(concentration)] += [float(tau_inc)]
  244. condition_cells.append(interp_data)
  245. if not condition_cells:
  246. continue
  247. allcells = np.asarray(condition_cells)
  248. data_mean = np.average(allcells, axis=0)
  249. data_mean_norm = data_mean / np.average(data_mean[:180 * time_mult])
  250. data_std = np.std(allcells, axis=0) / np.average(data_mean[:180 * time_mult])
  251. upper = data_mean_norm + 0.5 * data_std
  252. lower = data_mean_norm - 0.5 * data_std
  253. time = new_time
  254. all_time.append(time)
  255. all_data.append(data_mean_norm)
  256. concentrations.append(float(concentration))
  257. df_output = pd.DataFrame({
  258. 'Time': time,
  259. 'Normalized Data': data_mean_norm
  260. })
  261. output_file = outputDir / f"normalized_data_{concentration}mM.csv"
  262. # df_output.to_csv(output_file, index=False)
  263. # Sort the concentrations and associated data
  264. sorted_indices = np.argsort(concentrations)
  265. sorted_concentrations = [concentrations[i] for i in sorted_indices]
  266. sorted_data = [all_data[i] for i in sorted_indices]
  267. sorted_times = [all_time[i] for i in sorted_indices]
  268. # %%
  269. sorted_indices = np.argsort(concentrations)
  270. sorted_concentrations = [concentrations[i] for i in sorted_indices]
  271. sorted_data = [all_data[i] for i in sorted_indices]
  272. sorted_times = [all_time[i] for i in sorted_indices]
  273. print("Sorted concentrations:", sorted_concentrations)
  274. # Save a code-generated panel corresponding to the sucrose motor-speed panel.
  275. figure_dir = ROOT / "outputs" / "figure-panels"
  276. figure_dir.mkdir(parents=True, exist_ok=True)
  277. plt.rcParams.update({'font.size': 25, 'font.family': 'Arial'})
  278. fig, ax = plt.subplots(figsize=(10, 5.5))
  279. for axis in ['top', 'bottom', 'left', 'right']:
  280. ax.spines[axis].set_linewidth(2)
  281. ax.tick_params(axis='x', direction='in', width=2, length=7.5, pad=8)
  282. ax.tick_params(axis='y', direction='in', width=2, length=7.5, pad=8)
  283. for time_arr, mean_arr, conc in zip(sorted_times, sorted_data, sorted_concentrations):
  284. ax.plot(time_arr, mean_arr, label=f'{int(conc)} mM')
  285. ax.axvspan(180, 270, color='lightgray', alpha=0.6)
  286. ax.set_xlabel('Time (s)', fontsize=25)
  287. ax.set_ylabel('Rotation Speed (Normalized)', fontsize=25)
  288. ax.set_xlim(0, 380)
  289. ax.set_ylim(0, 1.25)
  290. ax.set_xticks(np.arange(0, 381, 50))
  291. ticks = np.arange(0, 1.26, 0.25)
  292. ax.set_yticks(ticks[:-1])
  293. ax.legend(loc='lower left', fontsize=25, frameon=False)
  294. fig.tight_layout()
  295. fig.savefig(figure_dir / "figure-1d-sucrose-motor-speed.pdf", dpi=350, bbox_inches='tight')
  296. plt.close(fig)
  297. # %%
  298. def equalize_lists(data):
  299. if not data:
  300. return {}
  301. lengths = [len(v) for v in data.values()]
  302. if max(lengths) == 0:
  303. return {k: [] for k in data}
  304. max_length = max(lengths)
  305. return {
  306. k: v + [np.nan] * (max_length - len(v))
  307. for k, v in data.items()
  308. }
  309. # %%
  310. # Save per-cell fit parameters to intermediate CSVs in the output directory.
  311. # Downstream cells (tau/magnitude vs osmolarity) read from these files.
  312. new_tau_values = equalize_lists(tau_values)
  313. new_tau_inc_values = equalize_lists(tau_inc_values)
  314. new_t0_values = equalize_lists(t0_values)
  315. new_A_values = equalize_lists(A_values)
  316. csv_dir = outputDir
  317. pd.DataFrame(new_tau_values).to_csv( csv_dir / "sucrose_tau_values.csv")
  318. pd.DataFrame(new_tau_inc_values).to_csv(csv_dir / "sucrose_tau_inc_values.csv")
  319. pd.DataFrame(new_t0_values).to_csv( csv_dir / "sucrose_t0_values.csv")
  320. pd.DataFrame(new_A_values).to_csv( csv_dir / "sucrose_A_values.csv")
  321. print("tau_values :", tau_values)
  322. print("tau_inc_values:", tau_inc_values)
  323. print("t0_values :", t0_values)
  324. print("A_values :", A_values)
  325. # %%
  326. # Figure 1C – example single-motor trace (cell3, 500 mM sucrose) with inset
  327. # showing the normalised speed and the sigmoidal fit.
  328. # Corresponds to manuscript figure panel sucrose.pdf / Fig. 1C.
  329. # --- load the example trace ---------------------------------------------------
  330. example_row = None
  331. example_trace = None
  332. for row, trace in iter_condition_traces(assay="Sucrose", condition_mM=500):
  333. if row["cell_id"] == "cell3":
  334. example_row = row
  335. example_trace = trace
  336. break
  337. time_raw = example_trace["time_s"].to_numpy()
  338. freq_raw = example_trace["frequency_hz"].to_numpy()
  339. # --- plot settings ------------------------------------------------------------
  340. plt.rcParams.update({"font.size": 25, "font.family": "Arial"})
  341. fig, ax = plt.subplots(figsize=(10, 5.5))
  342. for axis in ["top", "bottom", "left", "right"]:
  343. ax.spines[axis].set_linewidth(2)
  344. ax.tick_params(axis="x", direction="in", labelsize=25, width=2, length=7.5, pad=8)
  345. ax.tick_params(axis="y", direction="in", labelsize=25, width=2, length=7.5, pad=8)
  346. # main trace (already mean-binned at 0.1 s in the parquet)
  347. ax.plot(time_raw, freq_raw)
  348. ax.set_xlabel("Time (s)", fontsize=25)
  349. ax.set_ylabel("Rotation Speed (Hz)", fontsize=25)
  350. ax.set_xticks(np.arange(0, 381, 50))
  351. ax.set_xlim(0, 370)
  352. ax.set_yticks(np.arange(0, 70, 10))
  353. ax.set_ylim(0, 70)
  354. ax.axvspan(180, 270, color="lightgray", alpha=0.6)
  355. # --- inset: normalised + filtered + fitted -----------------------------------
  356. inset_ax = plt.axes([0.19, 0.20, 0.29, 0.45])
  357. data_filt = signal.medfilt(freq_raw, kernel_size=median_kernel_size(time_raw, window_s=1.0))
  358. time_b, data_b = bin_data(time_raw, data_filt)
  359. data_norm = data_b / np.average(data_b[time_b <= 180])
  360. inset_ax.plot(time_b, data_norm, alpha=0.6)
  361. mask_dec = (time_b >= 175) & (time_b <= 240)
  362. time_dec = time_b[mask_dec]
  363. speed_dec = data_norm[mask_dec]
  364. global speed_initial, speed_final
  365. speed_initial = np.average(data_norm[(time_b >= 155) & (time_b <= 175)])
  366. speed_final = np.average(data_norm[(time_b >= 215) & (time_b <= 235)])
  367. popt_dec, _ = curve_fit(exp_decrease, time_dec, speed_dec, p0=[10, 10], bounds=(0, np.inf))
  368. tau_ex, t0_ex = popt_dec
  369. inset_ax.plot(time_dec, exp_decrease(time_dec, *popt_dec),
  370. "--", color="black", linewidth=2.25, label="Fit")
  371. # magnitude annotation (red vertical error bar)
  372. x_mag = 170
  373. y_mag_top = 1.05
  374. y_mag_bot = float(np.min(data_norm[(time_b >= 215) & (time_b <= 235)]))
  375. y_mag_mid = (y_mag_top + y_mag_bot) / 2
  376. inset_ax.errorbar([x_mag], [y_mag_mid],
  377. yerr=[(y_mag_top - y_mag_bot) / 2],
  378. marker="None", linestyle="None",
  379. ecolor="red", capsize=4, elinewidth=1.75, capthick=1.75)
  380. inset_ax.text(x_mag - 3, y_mag_mid, "Magnitude",
  381. ha="right", va="center", rotation=90, fontsize=25)
  382. # timescale annotation (purple horizontal error bar)
  383. x_inflect = 175 + t0_ex
  384. inset_ax.errorbar([x_inflect], [0.05], xerr=[6],
  385. marker="None", linestyle="None",
  386. ecolor="purple", capsize=4, elinewidth=1.75, capthick=1.75)
  387. inset_ax.text(x_inflect, 0.07, "Time", ha="center", va="bottom", fontsize=25)
  388. inset_ax.set_xlim(155, 245)
  389. inset_ax.set_ylim(-0.05, 1.15)
  390. inset_ax.axvspan(180, 270, color="lightgray", alpha=0.6)
  391. inset_ax.tick_params(which="both", direction="in")
  392. inset_ax.set_xticks([160, 200, 240])
  393. inset_ax.set_yticks([0, 0.5, 1.0])
  394. figure_dir = ROOT / "outputs" / "figure-panels"
  395. fig.savefig(figure_dir / "figure-1c-sucrose-example-trace.pdf", dpi=350, bbox_inches="tight")
  396. plt.close(fig)
  397. print("Saved figure-1c-sucrose-example-trace.pdf")
  398. # %%
  399. # Supplemental example trace – cell2 at 200 mM sucrose.
  400. # Single-cell quality check; matches the style used in the supplemental
  401. # individual-cells figure (sucros_individual-cells_all.pdf).
  402. for row, trace in iter_condition_traces(assay="Sucrose", condition_mM=200):
  403. if row["cell_id"] == "cell2":
  404. ex200_trace = trace
  405. break
  406. plt.rcParams.update({"font.size": 50, "font.family": "Arial"})
  407. fig, ax = plt.subplots(figsize=(20, 10))
  408. for axis in ["top", "bottom", "left", "right"]:
  409. ax.spines[axis].set_linewidth(10)
  410. ax.tick_params(axis="x", direction="in", labelsize=150, width=10, length=50, pad=8)
  411. ax.tick_params(axis="y", direction="in", labelsize=150, width=10, length=50, pad=8)
  412. ax.plot(ex200_trace["time_s"], ex200_trace["frequency_hz"], linewidth=6, color="blue")
  413. ax.set_xticks(np.arange(0, 381, 150))
  414. ax.set_xlim(0, 380)
  415. ax.set_yticks(np.arange(0, 90, 40))
  416. ax.set_ylim(0, 80)
  417. ax.axvspan(180, 270, color="lightgray", alpha=0.6)
  418. figure_dir = ROOT / "outputs" / "figure-panels"
  419. fig.savefig(figure_dir / "supplement-sucrose-example-cell-200mM.pdf", dpi=350, bbox_inches="tight")
  420. plt.close(fig)
  421. print("Saved supplement-sucrose-example-cell-200mM.pdf")
  422. # %%
  423. # Supplemental Figure – Viscosity-sucrose.pdf
  424. # 4 panels (A–D): per-concentration population-averaged normalised speed
  425. # with a dashed black line marking the estimated viscosity-driven speed reduction.
  426. viscosity_reduction = {200: 0.18, 300: 0.26, 400: 0.36, 500: 0.43}
  427. plt.rcParams.update({"font.size": 25, "font.family": "Arial"})
  428. fig, axes = plt.subplots(1, 4, figsize=(20, 5.5), sharey=True)
  429. for ax, (time_arr, mean_arr, conc) in zip(axes, zip(sorted_times, sorted_data, sorted_concentrations)):
  430. conc_i = int(conc)
  431. ax.plot(time_arr, mean_arr, linewidth=2)
  432. # dashed viscosity line: flat at (1 - reduction) during shock; 1.0 elsewhere
  433. visc_red = viscosity_reduction[conc_i]
  434. visc_level = 1.0 - visc_red
  435. t = np.asarray(time_arr)
  436. visc_line = np.where((t >= 180) & (t <= 270), visc_level, 1.0)
  437. ax.plot(t, visc_line, "--", color="black", linewidth=2)
  438. ax.axvspan(180, 270, color="lightgray", alpha=0.6)
  439. ax.set_xlim(0, 380)
  440. ax.set_ylim(0, 1.4)
  441. ax.set_xticks(np.arange(0, 381, 150))
  442. ax.set_xlabel("Time (s)", fontsize=25)
  443. ax.set_title(f"{conc_i} mM", fontsize=25)
  444. for spine in ["top", "bottom", "left", "right"]:
  445. ax.spines[spine].set_linewidth(2)
  446. ax.tick_params(direction="in", width=2, length=7.5)
  447. axes[0].set_ylabel("Rotation Speed (Normalised)", fontsize=25)
  448. fig.tight_layout()
  449. figure_dir = ROOT / "outputs" / "figure-panels"
  450. fig.savefig(figure_dir / "supplement-sucrose-viscosity.pdf", dpi=350, bbox_inches="tight")
  451. plt.close(fig)
  452. print("Saved supplement-sucrose-viscosity.pdf")
  453. # %%
  454. import os
  455. import numpy as np
  456. import matplotlib.pyplot as plt
  457. # -------------------------------
  458. # Global plot settings
  459. # -------------------------------
  460. plt.rcParams['font.family'] = 'Arial'
  461. plt.rcParams.update({'font.size': 25})
  462. # -------------------------------
  463. # Paths
  464. # -------------------------------
  465. save_root = ROOT / "outputs" / "bead" / "sucrose-individual-cells"
  466. os.makedirs(save_root, exist_ok=True)
  467. # -------------------------------
  468. # Loop through compact canonical traces
  469. # -------------------------------
  470. for row, trace in iter_condition_traces(assay="Sucrose"):
  471. fig, ax = plt.subplots(figsize=(20, 10))
  472. for axis in ['top','bottom','left','right']:
  473. ax.spines[axis].set_linewidth(10)
  474. ax.tick_params(axis='x', direction='in', labelsize=150, width=10, length=50, pad=8)
  475. ax.tick_params(axis='y', direction='in', labelsize=150, width=10, length=50, pad=8)
  476. ax.plot(trace["time_s"], trace["frequency_hz"], linewidth=6, color="blue")
  477. ax.set_xticks(np.arange(0, 381, 150))
  478. ax.set_xlim(0, 380)
  479. ax.set_yticks(np.arange(0, 90, 40))
  480. ax.set_ylim(0, 80)
  481. ax.axvspan(180, 270, color="lightgray", alpha=0.6)
  482. save_path = save_root / f"{row['condition_mM']}mM_{row['cell_id']}.pdf"
  483. plt.savefig(save_path, dpi=350)
  484. plt.close(fig)
  485. print(f"Saved figure: {save_path}")
  486. # %%
  487. # Supplemental Figure – tau decrease vs. change in osmolarity
  488. # (Panel A of sucros-time-charc.pdf / sfig:tau-sucro-increa-decrease).
  489. import openpyxl # noqa: F401 (ensure available; pandas uses it)
  490. osmo_excel = ROOT / "data" / "Osmolarity-readings" / "sucrose-old.xlsx"
  491. df_osmo = pd.read_excel(osmo_excel)
  492. df_osmo.columns = df_osmo.columns.str.strip().str.lower()
  493. osmotic_shock_values = df_osmo["change_in_osmolarity"].iloc[1:].values # skip baseline row
  494. concentrations_ordered = [200, 300, 400, 500]
  495. tau_means, tau_stds = [], []
  496. for c in concentrations_ordered:
  497. vals = [v for v in tau_values[str(c)] if not np.isnan(v)]
  498. tau_means.append(np.mean(vals))
  499. tau_stds.append(np.std(vals))
  500. plt.rcParams.update({"font.size": 30, "font.family": "Arial"})
  501. fig, ax = plt.subplots(figsize=(10, 5.5))
  502. for spine in ["top", "bottom", "left", "right"]:
  503. ax.spines[spine].set_linewidth(2)
  504. ax.tick_params(axis="x", direction="in", labelsize=30, width=2, length=7.5, pad=8)
  505. ax.tick_params(axis="y", direction="in", labelsize=30, width=2, length=7.5, pad=8)
  506. ax.errorbar(osmotic_shock_values, tau_means, yerr=tau_stds,
  507. marker="o", markeredgecolor="k", markersize=12,
  508. linestyle="None", color="black", ecolor="black", capsize=4)
  509. ax.set_xticks(osmotic_shock_values)
  510. ax.set_xlabel("Change in osmolarity (mOsmol/Kg)")
  511. ax.set_ylabel("Characteristic Time (s)")
  512. ax.set_xlim(80, 796.5)
  513. ax.set_ylim(-0.5, 6.5)
  514. ax.set_yticks([0, 1, 2, 3, 4, 5, 6])
  515. figure_dir = ROOT / "outputs" / "figure-panels"
  516. fig.savefig(figure_dir / "supplement-sucrose-tau-decrease.pdf", dpi=350, bbox_inches="tight")
  517. plt.close(fig)
  518. print("Saved supplement-sucrose-tau-decrease.pdf")
  519. # %%
  520. # Supplemental Figure – tau increase (recovery) vs. change in osmolarity
  521. # (Panel B of sucros-time-charc.pdf / sfig:tau-sucro-increa-decrease).
  522. osmo_excel = ROOT / "data" / "Osmolarity-readings" / "sucrose-old.xlsx"
  523. df_osmo = pd.read_excel(osmo_excel)
  524. df_osmo.columns = df_osmo.columns.str.strip().str.lower()
  525. osmotic_shock_values = df_osmo["change_in_osmolarity"].iloc[1:].values
  526. concentrations_ordered = [200, 300, 400, 500]
  527. tinc_means, tinc_stds = [], []
  528. for c in concentrations_ordered:
  529. vals = [v for v in tau_inc_values[str(c)] if not np.isnan(v)]
  530. tinc_means.append(np.mean(vals))
  531. tinc_stds.append(np.std(vals))
  532. plt.rcParams.update({"font.size": 30, "font.family": "Arial"})
  533. fig, ax = plt.subplots(figsize=(10, 5.5))
  534. for spine in ["top", "bottom", "left", "right"]:
  535. ax.spines[spine].set_linewidth(2)
  536. ax.tick_params(axis="x", direction="in", labelsize=30, width=2, length=7.5, pad=8)
  537. ax.tick_params(axis="y", direction="in", labelsize=30, width=2, length=7.5, pad=8)
  538. ax.errorbar(osmotic_shock_values, tinc_means, yerr=tinc_stds,
  539. marker="o", markeredgecolor="k", markersize=12,
  540. linestyle="None", color="black", ecolor="black", capsize=4)
  541. ax.set_xticks(osmotic_shock_values)
  542. ax.set_xlabel("Change in osmolarity (mOsmol/Kg)")
  543. ax.set_ylabel("Characteristic Time (s)")
  544. ax.set_xlim(185, 796.5)
  545. ax.set_ylim(-0.5, 6.5)
  546. ax.set_yticks([0, 1, 2, 3, 4, 5, 6])
  547. figure_dir = ROOT / "outputs" / "figure-panels"
  548. fig.savefig(figure_dir / "supplement-sucrose-tau-increase.pdf", dpi=350, bbox_inches="tight")
  549. plt.close(fig)
  550. print("Saved supplement-sucrose-tau-increase.pdf")
  551. # %%
  552. # Figure 1E - normalised speed reduction vs. change in osmolarity.
  553. # Red circles: total speed decrease (mean ± SD).
  554. # Green triangles + dashed green line: estimated viscosity-driven reduction.
  555. # Corresponds to manuscript panel sucrose.pdf / Fig. 1E.
  556. from scipy.stats import linregress
  557. osmo_excel = ROOT / "data" / "Osmolarity-readings" / "sucrose-old.xlsx"
  558. df_osmo = pd.read_excel(osmo_excel)
  559. df_osmo.columns = df_osmo.columns.str.strip().str.lower()
  560. osmotic_shock_values = df_osmo["change_in_osmolarity"].iloc[1:].values
  561. concentrations_ordered = [200, 300, 400, 500]
  562. A_means, A_stds = [], []
  563. for c in concentrations_ordered:
  564. vals = [v for v in A_values[str(c)] if not np.isnan(v)]
  565. A_means.append(np.mean(vals))
  566. A_stds.append(np.std(vals))
  567. viscosity_dict = {200: 0.18, 300: 0.26, 400: 0.36, 500: 0.43}
  568. viscosities = [viscosity_dict[c] for c in concentrations_ordered]
  569. plt.rcParams.update({"font.size": 25, "font.family": "Arial"})
  570. fig, ax = plt.subplots(figsize=(10, 5.5))
  571. for spine in ["top", "bottom", "left", "right"]:
  572. ax.spines[spine].set_linewidth(2)
  573. ax.tick_params(axis="x", direction="in", labelsize=25, width=2, length=7.5, pad=8)
  574. ax.tick_params(axis="y", direction="in", labelsize=25, width=2, length=7.5, pad=8)
  575. x_data = np.array(osmotic_shock_values)
  576. # total speed decrease
  577. ax.errorbar(x_data, A_means, yerr=A_stds,
  578. marker="o", markeredgecolor="k", markersize=12,
  579. linestyle="None", color="red", ecolor="black", capsize=4)
  580. slope, intercept, *_ = linregress(x_data, A_means)
  581. x_fit = np.linspace(x_data.min(), x_data.max(), 1000)
  582. ax.plot(x_fit, slope * x_fit + intercept, "--", color="black", linewidth=3,
  583. label="Total speed decrease")
  584. # viscosity estimate
  585. ax.errorbar(x_data, viscosities,
  586. marker="^", markeredgecolor="green", markersize=12,
  587. linestyle="None", color="green", ecolor="green", capsize=4)
  588. slope_v, intercept_v, *_ = linregress(x_data, viscosities)
  589. ax.plot(x_fit, slope_v * x_fit + intercept_v, "--", color="green", linewidth=3,
  590. label="Speed decrease due to\nchange in viscosity")
  591. ax.set_xticks(x_data)
  592. ax.set_xlabel("Change in osmolarity (mOsmol/Kg)")
  593. ax.set_ylabel("Speed Decrease (Normalised)")
  594. ax.set_xlim(185, 796.5)
  595. ax.set_ylim(-0.05, 1.15)
  596. ax.legend(loc="upper left", frameon=False)
  597. figure_dir = ROOT / "outputs" / "figure-panels"
  598. fig.savefig(figure_dir / "figure-1e-sucrose-speed-decrease.pdf", dpi=350, bbox_inches="tight")
  599. plt.close(fig)
  600. print("Saved figure-1e-sucrose-speed-decrease.pdf")
  601. # save stats
  602. diff_values = np.array(A_means) - np.array(viscosities)
  603. slope_d, intercept_d, r_d, p_d, err_d = linregress(x_data, diff_values)
  604. stats_path = outputDir / "sucrose_A_linear_fit_stats.txt"
  605. with open(stats_path, "w", encoding="utf-8") as f:
  606. f.write("Linear Fit -- Speed Decrease\n====================\n")
  607. f.write(f"Slope: {slope:.6f}\nIntercept: {intercept:.6f}\n\n")
  608. f.write("Linear Fit -- Viscosity\n====================\n")
  609. f.write(f"Slope: {slope_v:.6f}\nIntercept: {intercept_v:.6f}\n\n")
  610. f.write("Linear Fit -- Difference (Speed - Viscosity)\n====================\n")
  611. f.write(f"Slope: {slope_d:.6f}\nIntercept: {intercept_d:.6f}\nR2: {r_d**2:.6f}\n")
  612. print("Stats saved to", stats_path)
  613. # %%
  614. # Figure 1F – viscosity-corrected speed decrease vs. change in osmolarity.
  615. # Blue squares: (total speed decrease − viscosity estimate), mean ± SD.
  616. # Corresponds to manuscript panel sucrose.pdf / Fig. 1F.
  617. from scipy.stats import linregress
  618. osmo_excel = ROOT / "data" / "Osmolarity-readings" / "sucrose-old.xlsx"
  619. df_osmo = pd.read_excel(osmo_excel)
  620. df_osmo.columns = df_osmo.columns.str.strip().str.lower()
  621. osmotic_shock_values = df_osmo["change_in_osmolarity"].iloc[1:].values
  622. concentrations_ordered = [200, 300, 400, 500]
  623. A_means_20, A_stds_20 = [], []
  624. for c in concentrations_ordered:
  625. vals = [v for v in A_values[str(c)] if not np.isnan(v)]
  626. A_means_20.append(np.mean(vals))
  627. A_stds_20.append(np.std(vals))
  628. viscosity_dict = {200: 0.18, 300: 0.26, 400: 0.36, 500: 0.43}
  629. viscosities = [viscosity_dict[c] for c in concentrations_ordered]
  630. diff_values = np.array(A_means_20) - np.array(viscosities)
  631. plt.rcParams.update({"font.size": 25, "font.family": "Arial"})
  632. fig, ax = plt.subplots(figsize=(10, 5.5))
  633. for spine in ["top", "bottom", "left", "right"]:
  634. ax.spines[spine].set_linewidth(2)
  635. ax.tick_params(axis="x", direction="in", labelsize=25, width=2, length=7.5, pad=8)
  636. ax.tick_params(axis="y", direction="in", labelsize=25, width=2, length=7.5, pad=8)
  637. x_data = np.array(osmotic_shock_values)
  638. ax.errorbar(x_data, diff_values, yerr=A_stds_20,
  639. marker="s", markeredgecolor="blue", markersize=12,
  640. linestyle="None", color="blue", ecolor="blue", capsize=4)
  641. slope_d, intercept_d, *_ = linregress(x_data, diff_values)
  642. x_fit = np.linspace(x_data.min(), x_data.max(), 1000)
  643. ax.plot(x_fit, slope_d * x_fit + intercept_d, "--", color="blue", linewidth=3,
  644. label="Viscosity-corrected speed decrease")
  645. ax.set_xticks(x_data)
  646. ax.set_xlabel("Change in osmolarity (mOsmol/Kg)")
  647. ax.set_ylabel("Speed Decrease (Normalised)")
  648. ax.set_xlim(185, 796.5)
  649. ax.set_ylim(-0.05, 1.15)
  650. ax.legend(loc="upper left", frameon=False)
  651. figure_dir = ROOT / "outputs" / "figure-panels"
  652. fig.savefig(figure_dir / "figure-1f-sucrose-viscosity-corrected.pdf", dpi=350, bbox_inches="tight")
  653. plt.close(fig)
  654. print("Saved figure-1f-sucrose-viscosity-corrected.pdf")
  655. # %%
  656. # Supplemental Figure – combined sucrose + sorbitol viscosity-corrected speed decrease.
  657. # Corresponds to sucrose-sorb-combin-speed-decrease.pdf / sfig:tau-sorb-sucrose-combine.
  658. from scipy.stats import linregress
  659. from matplotlib.lines import Line2D
  660. # ---- sucrose data (already in memory) ----------------------------------------
  661. osmo_suc_xl = ROOT / "data" / "Osmolarity-readings" / "sucrose-old.xlsx"
  662. df_suc_osmo = pd.read_excel(osmo_suc_xl)
  663. df_suc_osmo.columns = df_suc_osmo.columns.str.strip().str.lower()
  664. sucrose_osmo = df_suc_osmo["change_in_osmolarity"].iloc[1:].values
  665. concs_suc = [200, 300, 400, 500]
  666. suc_means = [np.mean(A_values[str(c)]) for c in concs_suc]
  667. suc_stds = [np.std(A_values[str(c)]) for c in concs_suc]
  668. suc_visc = {200: 0.18, 300: 0.26, 400: 0.36, 500: 0.43}
  669. suc_diff = np.array(suc_means) - np.array([suc_visc[c] for c in concs_suc])
  670. # ---- sorbitol A values (compute from parquet) --------------------------------
  671. sorb_A_values = {200: [], 300: [], 400: [], 500: []}
  672. for conc_s in [200, 300, 400, 500]:
  673. for row_s, trace_s in iter_condition_traces(assay="Sorbitol", condition_mM=conc_s):
  674. t_s = trace_s["time_s"].to_numpy()
  675. f_s = trace_s["frequency_hz"].to_numpy()
  676. if len(f_s) == 0:
  677. continue
  678. if f_s[0] < 0:
  679. f_s = f_s * -1
  680. f_filt = signal.medfilt(f_s, kernel_size=median_kernel_size(t_s, window_s=1.0))
  681. t_b, f_b = bin_data(t_s, f_filt)
  682. # normalise
  683. baseline = np.average(f_b[t_b <= 180])
  684. if baseline == 0:
  685. continue
  686. f_norm = f_b / baseline
  687. # extract magnitude
  688. s_init = np.average(f_norm[(t_b >= 155) & (t_b <= 175)])
  689. s_fin = np.average(f_norm[(t_b >= 215) & (t_b <= 235)])
  690. A_sorb = s_init - s_fin
  691. if 0 < A_sorb < 2: # sanity filter
  692. sorb_A_values[conc_s].append(float(A_sorb))
  693. sorb_means = [np.mean(sorb_A_values[c]) for c in concs_suc]
  694. sorb_stds = [np.std(sorb_A_values[c]) for c in concs_suc]
  695. sorb_visc = {200: 0.14, 300: 0.20, 400: 0.26, 500: 0.32}
  696. sorb_diff = np.array(sorb_means) - np.array([sorb_visc[c] for c in concs_suc])
  697. # ---- sorbitol osmolarity -----------------------------------------------------
  698. osmo_sorb_xl = ROOT / "data" / "Osmolarity-readings" / "sorbitol.xlsx"
  699. df_sorb_osmo = pd.read_excel(osmo_sorb_xl)
  700. df_sorb_osmo.columns = df_sorb_osmo.columns.str.strip().str.lower()
  701. # find change_in_osmolarity or compute it
  702. if "change_in_osmolarity" in df_sorb_osmo.columns:
  703. sorbitol_osmo = df_sorb_osmo["change_in_osmolarity"].iloc[1:].values
  704. else:
  705. read_cols = [col for col in df_sorb_osmo.columns if "read" in col]
  706. df_sorb_osmo["avg_osmolarity"] = df_sorb_osmo[read_cols[:2]].mean(axis=1)
  707. df_sorb_osmo["change_in_osmolarity"] = (
  708. df_sorb_osmo["avg_osmolarity"] - df_sorb_osmo["avg_osmolarity"].iloc[0]
  709. )
  710. sorbitol_osmo = df_sorb_osmo["change_in_osmolarity"].iloc[1:].values
  711. # ---- plot --------------------------------------------------------------------
  712. plt.rcParams.update({"font.size": 25, "font.family": "Arial"})
  713. fig, ax = plt.subplots(figsize=(10, 5.5))
  714. for spine in ["top", "bottom", "left", "right"]:
  715. ax.spines[spine].set_linewidth(2)
  716. ax.tick_params(axis="x", direction="in", width=2, length=7.5, pad=8)
  717. ax.tick_params(axis="y", direction="in", width=2, length=7.5, pad=8)
  718. # sucrose
  719. ax.errorbar(sucrose_osmo, suc_diff, yerr=suc_stds,
  720. marker="s", markersize=12, linestyle="None",
  721. color="blue", ecolor="blue", capsize=4)
  722. sl_s, ic_s, *_ = linregress(sucrose_osmo, suc_diff)
  723. x_suc = np.linspace(sucrose_osmo.min(), sucrose_osmo.max(), 1000)
  724. ax.plot(x_suc, sl_s * x_suc + ic_s, "--", color="blue", linewidth=3)
  725. # sorbitol
  726. ax.errorbar(sorbitol_osmo, sorb_diff, yerr=sorb_stds,
  727. marker="^", markersize=12, linestyle="None",
  728. color="blue", ecolor="blue", capsize=4)
  729. sl_r, ic_r, *_ = linregress(sorbitol_osmo, sorb_diff)
  730. x_sor = np.linspace(sorbitol_osmo.min(), sorbitol_osmo.max(), 1000)
  731. ax.plot(x_sor, sl_r * x_sor + ic_r, ":", color="blue", linewidth=3)
  732. ax.set_xlabel("Change in osmolarity (mOsmol/Kg)")
  733. ax.set_ylabel("Speed Decrease (Normalised)")
  734. ax.set_ylim(-0.05, 1.15)
  735. ax.set_xlim(185, 800)
  736. ax.set_yticks(np.arange(0.0, 1.2, 0.2))
  737. ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda y, _: f"{y:.1f}"))
  738. handles = [
  739. Line2D([0], [0], color="blue", marker="s", markersize=12,
  740. linestyle="--", linewidth=3, label="sucrose",
  741. markerfacecolor="blue", markeredgecolor="blue"),
  742. Line2D([0], [0], color="blue", marker="^", markersize=12,
  743. linestyle=":", linewidth=3, label="sorbitol",
  744. markerfacecolor="blue", markeredgecolor="blue"),
  745. ]
  746. ax.legend(handles=handles, loc="upper left", frameon=False)
  747. figure_dir = ROOT / "outputs" / "figure-panels"
  748. fig.savefig(figure_dir / "supplement-sucrose-sorbitol-combined.pdf", dpi=350, bbox_inches="tight")
  749. plt.close(fig)
  750. print("Saved supplement-sucrose-sorbitol-combined.pdf")
  751. print("sorbitol A values computed:", sorb_A_values)

sucrose_shock_analysis.ipynb at commit d14d0ca, no license · at the source

Overview

Authors: Luis Meneses1, Farhad Javi1, Eric M. Dudebout1, Sophia Belser2, Jinming Yang3, Navish Wadhwa1,4
  1. Biodesign Center for Mechanisms of Evolution, Arizona State University, Tempe, AZ, USA
  2. Cavendish Laboratory, University of Cambridge, Cambridge, UK
  3. Department of Physics, Yale University, New Haven, CT, USA
  4. Center for Biological Physics and Department of Physics, Arizona State University, Tempe, AZ, USA
Institutions: Arizona State University (United States); University of Cambridge (United Kingdom); Yale University (United States)
Journal: Biophysical journal, volume 125, issue 11, pages 2618-2631
Dates: received 18 November 2025; accepted 14 April 2026; published online 21 April 2026; in print 2 June 2026
Type: Research article · Language: English
License: CC BY-NC-ND
Identifiers: DOI 10.1016/j.bpj.2026.04.014 · PMID 42021485 · PMCID PMC13235824 · OpenAlex W7155039277
Open access: hybrid, a free copy (OpenAlex)
Status: code verified
Categories: cellular / molecular (subfield)
Methods: fMRI & imaging, Single-unit activity, calcium imaging
MeSH: Escherichia coli*, Osmotic Pressure*, Escherichia coli Proteins, Flagella (* major topic)
Topic: Bacterial Genetics and Biotechnology (Genetics, Biochemistry, Genetics and Molecular Biology), according to OpenAlex
Funding: NIGMS; NIH (R00GM134124); Arizona Biomedical Research Centre (RFGA2023-008-14)
Citations: cited by 1 paper (Europe PMC); 83 references in the paper

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.

Repository

Its files are read in the Code ↔ Paper reader above, with 10 matches between paragraphs and lines of code.

wadhwalab/2026-Meneses-Osmotic

License: none: the authors keep all their rights
State: the link answers, verified on 29 September 2026
Evidence: files inventoried
Commit: d14d0caaa07299f13d1b1121d1e4630454fd724b, 4 May 2026
Languages: Python (7), Jupyter (3)
Size: 115 files, 10 scripts
Software Heritage: not archived
Found in: “Data availability”
Holds: README, environment (environment.yml, requirements.txt), tests, documentation, 3 notebooks
Not found: license file, CITATION.cff, continuous integration
Tools: NumPy (9 files), pandas (9 files), Matplotlib (8 files), SciPy (3 files), scikit-learn (2 files), seaborn (2 files)
Availability: 1 check, the latest on 29 September 2026: the link answers
  • 29 September 2026: the link answers
11 files

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 10 scripts, each with its path and the digest of its content;
  • 10 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Code and data availability statement

The paper has a code and data availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:

Read it in the paper: doi.org/10.1016/j.bpj.2026.04.014.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 29 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 6 authors, 4 MeSH terms, 3 funders, 82 references.

Cite

This paper

Meneses, L., Javi, F., Dudebout, E. M., Belser, S., Yang, J., & Wadhwa, N. (2026). Osmotic stress triggers fast and reversible PMF collapse in Escherichia coli. Biophysical journal, 125(11), 2618-2631. https://doi.org/10.1016/j.bpj.2026.04.014

BibTeX

@article{meneses2026osmotic,
author = {Meneses, Luis and Javi, Farhad and Dudebout, Eric M. and Belser, Sophia and Yang, Jinming and Wadhwa, Navish},
title = {{Osmotic stress triggers fast and reversible PMF collapse in Escherichia coli}},
journal = {Biophysical journal},
year = {2026},
month = apr,
volume = {125},
number = {11},
pages = {2618--2631},
publisher = {The Biophysical Society},
issn = {0006-3495},
doi = {10.1016/j.bpj.2026.04.014},
url = {https://doi.org/10.1016/j.bpj.2026.04.014},
pmid = {42021485},
pmcid = {PMC13235824}
}

RIS

TY - JOUR
AU - Meneses, Luis
AU - Javi, Farhad
AU - Dudebout, Eric M.
AU - Belser, Sophia
AU - Yang, Jinming
AU - Wadhwa, Navish
TI - Osmotic stress triggers fast and reversible PMF collapse in Escherichia coli
T2 - Biophysical journal
J2 - Biophys J
PY - 2026
DA - 2026/04/21
VL - 125
IS - 11
SP - 2618
EP - 2631
SN - 0006-3495
PB - The Biophysical Society
DO - 10.1016/j.bpj.2026.04.014
UR - https://doi.org/10.1016/j.bpj.2026.04.014
LA - en
ER -

CSL-JSON

{
"id": "10.1016/j.bpj.2026.04.014",
"type": "article-journal",
"title": "Osmotic stress triggers fast and reversible PMF collapse in Escherichia coli",
"container-title": "Biophysical journal",
"author": [
{
"family": "Meneses",
"given": "Luis"
},
{
"family": "Javi",
"given": "Farhad"
},
{
"family": "Dudebout",
"given": "Eric M."
},
{
"family": "Belser",
"given": "Sophia"
},
{
"family": "Yang",
"given": "Jinming"
},
{
"family": "Wadhwa",
"given": "Navish"
}
],
"container-title-short": "Biophys J",
"volume": "125",
"issue": "11",
"page": "2618-2631",
"DOI": "10.1016/j.bpj.2026.04.014",
"PMID": "42021485",
"PMCID": "PMC13235824",
"ISSN": "0006-3495",
"publisher": "The Biophysical Society",
"URL": "https://doi.org/10.1016/j.bpj.2026.04.014",
"language": "en",
"issued": {
"date-parts": [
[
2026,
4,
21
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1038/s41467-026-71629-z
Extracellular K&lt;sup&gt;+&lt;/sup&gt; modulates the pore conformations of Cys-loop receptor anion channels.
Journal: Nature communications
In common: cellular / molecular, 1 reference

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.