OSCR

LPS-induced inflammation differentially affects endogenous Ca<sup>2</sup>⁺ activity in mouse and human iPSC-derived astrocytes.

Code ↔ Paper

1 match 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 1 match
  1. [1] § Methods › LDA, component analysis and weight matrix generation ↔ 2026-06-05_LPS_Paper_Figs_GS_AZ_KL.ipynb, lines 119–263 · score 0.57 · MaxSize, Negative Matrix Factorization, weights, distance, components, NMF

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 · 640 lines · 23 KB · GPL-3.0 · 1 match

  1. # %% [markdown]
  2. # ## LPS-induced inflammation differentially affects endogenous Ca2⁺ activity in mouse and human iPSC-derived astrocytes
  3. # ### Franziska E. Müller, Flavian Ivanov, Anne-Catharine Studt, Ida Nitzsche, Frauke S. Bahr, Anna-Lena Krüger, Josephine Labus, Ghanendra Singh, Evgeni G. Ponimaskin, Kerstin Lenk* & Andre Zeug*
  4. # \* authors contributed equally
  5. # Mol Med 32, 52 (2026). https://doi.org/10.1186/s10020-026-01450-3
  6. #
  7. # **Contribution to code:** Ghanendra Singh, Andre Zeug, Kerstin Lenk
  8. # %% [markdown]
  9. # **Install libraries if needed:**
  10. # pip install seaborn, used version seaborn-0.13.2
  11. # pip install scikit-learn, used version scikit-learn-1.9.0
  12. # pip install openpyxl, used version openpyxl-3.1.5
  13. # %%
  14. # Import Library
  15. import pandas as pd
  16. import numpy as np
  17. import matplotlib.pyplot as plt
  18. from matplotlib import cm, ticker
  19. import matplotlib.colors as colors
  20. %matplotlib inline
  21. import seaborn as sns
  22. import os
  23. from matplotlib.backends.backend_pdf import PdfPages
  24. from datetime import datetime
  25. from matplotlib.colors import Normalize
  26. # ML Methods
  27. from sklearn.manifold import TSNE
  28. from sklearn.decomposition import PCA, FastICA, NMF
  29. # Importing different scalars
  30. from sklearn.preprocessing import StandardScaler,MinMaxScaler
  31. # Handy to ignore warnings
  32. import warnings
  33. warnings.filterwarnings("ignore")
  34. # %% [markdown]
  35. # ### Load data from Excel file
  36. # #### Note:
  37. # * Place your excel file in the same folder as jupyter notebook or use custom path
  38. # * Ignores the first header row for simplicity
  39. # * Index starts from zero
  40. # %%
  41. # Load data from the excel file into a dataframe using pandas pd
  42. path = 'MLData_comp_LPS_Treatment_Ch2_updated_file.xlsx'
  43. xl = pd.ExcelFile(path)
  44. # Check multipe sheets in the excel file #xl.sheet_names
  45. df_b18_g_5 = xl.parse(1,header=1) # pass sheet index as 1 for 18bin with gamma 0.5 excel data
  46. # %%
  47. # Visualize first five observations
  48. df_b18_g_5.head()
  49. # %% [markdown]
  50. # ### Dataframe with multi Index
  51. # * Data columm contains (first five sub columns)
  52. # * Here it is fixed (metadata size = 5)
  53. # %%
  54. # Function to load data with proper columns names
  55. # Default binsize in the function is 18
  56. def load_data(df,bs=18): # bs is bin size in short
  57. md = 5 #md = 5 as metadata size
  58. df_xl = pd.concat([df.iloc[:,0:md],
  59. df.iloc[:,md:1*bs*6+md],
  60. df.iloc[:,1*bs*6+md:2*bs*6+md],
  61. df.iloc[:,2*bs*6+md:3*bs*6+md],
  62. df.iloc[:,3*bs*6+md:4*bs*6+md]],
  63. axis=1,
  64. keys=['Data','maxArea','duration','maxDist','slopeCa'])
  65. return df_xl
  66. # %% [markdown]
  67. # ### Load the whole data file (HIP, PFC + iPSCs)
  68. # * Add the path to iPSCs data
  69. # %%
  70. path2 = 'MLData_hiPSC-Astros-AAV-V5-basal_Ch2.xlsx'
  71. xl2 = pd.ExcelFile(path2)
  72. df_b18_g_5_iPSC = xl2.parse(0,header=1)
  73. df = load_data(df_b18_g_5_iPSC, binsize)
  74. df.head()
  75. # %% [markdown]
  76. # #### Data representation can also be further simplified as
  77. # * Metadata (First five columns) or more for later datasets
  78. # * Main data (More no. of features can be considered)
  79. # * Generally a single name is used to just modify binsize and run whole cells at once.
  80. # * Dataframes are named with bin sizes to ensure to choose right binsize and dataframe.
  81. # %% [markdown]
  82. # ### Main dataframe
  83. # %%
  84. # Always use this bin size
  85. binsize = 18 #default binsize
  86. # change dataframe and binsize for different data
  87. df = load_data(df_b18_g_5, binsize)
  88. # %% [markdown]
  89. # ### Plotting different x-axis for bin size 18 for flame plots
  90. # %%
  91. bins = 18 # binsize
  92. bins_area = np.logspace(np.log10(0.44), np.log10(4000), num=bins, base=10)
  93. bins_duration = np.logspace(np.log10(0.6), np.log10(300), num=bins, base=10)
  94. bins_distance = np.logspace(np.log10(0.5), np.log10(100), num=bins, base=10)
  95. bins_slope = np.logspace(np.log10(0.001), np.log10(8), num=bins, base=10)
  96. x_axis_bins = [bins_area,bins_duration,bins_distance,bins_slope]
  97. y_axis_thresholds = [.5,1,2,5,10,20]
  98. # %% [markdown]
  99. # ## NMF (Non-Negative Matrix Factorization)
  100. # %% [markdown]
  101. # ### On entire dataset
  102. # %%
  103. # Load data
  104. from matplotlib.backends.backend_pdf import PdfPages
  105. from datetime import datetime
  106. def nmf_whole(nmf_Comp=6, plot_contour_flag=True, region="Whole_data"):
  107. # df = load_data(df_b18_g_5)
  108. df = load_data(df_b18_g_5, 18)
  109. features = ["maxArea", "duration", "maxDist", "slopeCa"]
  110. legend_names = ["Ctrl.", "3 h LPS", "24 h LPS"]
  111. if region == "HIP":
  112. dfc = df[df["Data"]["Condition"] <= 3]
  113. elif region == "PFC":
  114. dfc = df[df["Data"]["Condition"] > 3]
  115. elif region == "iPSC":
  116. dfc = load_data(df_b18_g_5_iPSC, 18)
  117. print("iPSC data loaded")
  118. else:
  119. dfc = df
  120. X = dfc.iloc[:, 5:] # data (ignore first five columns)
  121. y = dfc.iloc[:, 0] # labels
  122. nmfC = nmf_Comp # of NMF components
  123. nF = 4 # no. of features
  124. nmf = NMF(n_components=nmfC, init="random", random_state=0, tol=1e-3)
  125. W = nmf.fit_transform(X)
  126. H = nmf.components_
  127. # Normalize H to have a maximum value of 1 and adjust W accordingly
  128. W = W * H.max(axis=1)
  129. H = H / H.max(axis=1, keepdims=True)
  130. # Manually reorganize matrices to fit color sceme
  131. if region == "HIP":
  132. if nmf_Comp == 2:
  133. H = H[[1, 0], :]
  134. W = W[:, [1, 0]]
  135. # elif nmf_Comp == 3:
  136. # H = H[[0, 2, 1], :]
  137. # W = W[:, [0, 2, 1]]
  138. elif region == "iPSC":
  139. # if nmf_Comp == 2:
  140. # H = H[[1, 0], :]
  141. # W = W[:, [1, 0]]
  142. if nmf_Comp == 3:
  143. H = H[[0, 2, 1], :]
  144. W = W[:, [0, 2, 1]]
  145. # print(H[:5, :5])
  146. # print(H_reorganized[:5, :5])
  147. H = H.reshape(nmfC, 4, 6, 18)
  148. vmin = 0
  149. vmax = 1 # H.max()
  150. levels = np.linspace(vmin, vmax, 11) # Define levels for contourf
  151. levelsC = np.linspace(0.2, vmax, 6) # Define levels for contour
  152. norm = colors.Normalize(vmin=vmin, vmax=vmax)
  153. # Generate a timestamp
  154. timestamp = datetime.now().strftime("%Y-%m-%d_%H.%M.%S")
  155. # Create a PdfPages object with a timestamped file name
  156. file_name = f'tmp/NMF_{region}_{nmf_Comp}_{timestamp}.pdf'
  157. file_name_W = f'tmp/NMF_{region}_{nmf_Comp}_{timestamp}_W.csv'
  158. # Convert W to a DataFrame
  159. W_df = pd.DataFrame(W)
  160. # Save the DataFrame to a CSV file
  161. W_df.to_csv(file_name_W, index=False)
  162. #print("W matrix saved as:", file_name_W)
  163. # Set the default font size
  164. plt.rcParams.update({'font.size': 16}) # Change 16 to your desired font size
  165. feature_labels = [r"MaxSize [$\mu$m$^2$]",
  166. "Duration [s]",
  167. r"Distance [$\mu$m]",
  168. r"Ca$^{2+}$ change [a.u.]"
  169. ]
  170. legend_names = ["Ctrl.", "3 h LPS", "24 h LPS"]
  171. with PdfPages(file_name) as pdf:
  172. ## Contours overlayed on top of each other
  173. cmap = ["Greys", "Greens", "Reds", "Blues", "Purples", "Oranges"]
  174. res = plt.subplots(1, nF+1, figsize=(18, 4),facecolor='none')
  175. axes : list[plt.Axes] = res[1] # Initialize axes list
  176. fig2 = res[0]
  177. for f in range(nF):
  178. sax = plt.subplot(1, nF+1, f + 1)
  179. for nmf_i in range(nmfC):
  180. contour = plt.contour(x_axis_bins[f], range(0, 6, 1), H[nmf_i, f], levels=levelsC,
  181. vmin=vmin, vmax=vmax, extend="neither", cmap=cmap[nmf_i],
  182. alpha=0.7, linewidths=3, norm=norm)
  183. # fp.cmap.set_over('black')
  184. # fp.cmap.set_under('white')
  185. plt.xscale("log") # Set x-axis to logarithmic scale
  186. plt.yticks(range(0, 6, 1), y_axis_thresholds)
  187. plt.xlim(x_axis_bins[f].min(), x_axis_bins[f].max()) # Set x-axis limits
  188. # plt.autoscale(enable=True, axis='x', tight=True)
  189. # contour.set_clim(vmin, vmax) # Set colorbar limits explicitly
  190. contour.cmap.set_under("white")
  191. # plt.title(features[f], pad=20)
  192. plt.ylabel("Threshold (NMF Comp. %d)" % (nmf_i + 1),fontsize=15)
  193. plt.xlabel(feature_labels[f],fontsize=15)
  194. sax.patch.set_alpha(0) # Make the axes background transparent
  195. plt.tight_layout()
  196. sax.set_aspect(aspect="auto") # Options: 'equal', 'auto', or a numeric value
  197. # plt.show()
  198. # Weight distribution
  199. cmap=["grey","green","red","blue","purple","orange"]
  200. clabel = dfc["Data"]["Cond.Label"].unique()
  201. cond_u = dfc["Data"]["Condition"].unique()
  202. cond_u_int = list(map(int, cond_u))
  203. WW = np.zeros((cond_u.shape[0], nmfC))
  204. list_cond = []
  205. for i,cond_i in enumerate(cond_u):
  206. W_i = W[dfc["Data"]["Condition"] == cond_i, :]
  207. list_cond.append(W_i)
  208. W_i = np.mean(W_i, axis=0)
  209. WW[i, :] = W_i
  210. # list_cond[0][:,0]
  211. # plt.figure(figsize=(12, 6))
  212. plt.subplot(1, nF+1, nF + 1)
  213. for i in range(WW.shape[1]): # Loop over the number of NMF components
  214. plt.plot(cond_u,WW[:,i],ls="--",marker="s",
  215. label=f"NMF Comp{i+1}", color=cmap[i % len(cmap)]) # Use colors from cmap
  216. # plt.legend()
  217. plt.title("Weight Distribution", pad=20)
  218. plt.xticks(cond_u,legend_names)
  219. plt.xlabel("Condition Index")
  220. plt.ylabel("Weight")
  221. plt.ylim(bottom=0)
  222. plt.gca().patch.set_alpha(0) # Make the axes background transparent#
  223. plt.savefig("tmp/NMF_Weight_Distribution_%d.svg"%nmfC, dpi=300, bbox_inches='tight', transparent=True)
  224. plt.show()
  225. #pdf.savefig(fig2, transparent=True) # Save the current figure to the PDF
  226. # %%
  227. from datetime import datetime
  228. def nmf_whole(nmf_Comp=6, plot_contour_flag=True, region="Whole_data",suptext="", suptextcolor=""):
  229. # Load data
  230. df = load_data(df_b18_g_5, 18)
  231. if region == "HIP":
  232. dfc = df[df["Data"]["Condition"] <= 3]
  233. elif region == "PFC":
  234. dfc = df[df["Data"]["Condition"] > 3]
  235. elif region == "iPSC":
  236. dfc = load_data(df_b18_g_5_iPSC, 18)
  237. #print("iPSC data loaded")
  238. else:
  239. dfc = df
  240. # Labels
  241. feature_labels = [r"MaxSize [$\mu$m$^2$]","Duration [s]",r"Distance [$\mu$m]",r"Ca$^{2+}$ change [a.u.]",]
  242. legend_names = ["Ctrl.", "3 h LPS", "24 h LPS"]
  243. # NMF
  244. X = dfc.iloc[:, 5:]
  245. nmf = NMF(n_components=nmf_Comp,init="random",random_state=0,tol=1e-3,)
  246. W = nmf.fit_transform(X)
  247. H = nmf.components_
  248. # Normalize
  249. W = W * H.max(axis=1)
  250. H = H / H.max(axis=1, keepdims=True)
  251. # Optional component reordering
  252. if region == "HIP" and nmf_Comp == 2:
  253. H = H[[1, 0], :]
  254. W = W[:, [1, 0]]
  255. elif region == "iPSC" and nmf_Comp == 3:
  256. H = H[[0, 2, 1], :]
  257. W = W[:, [0, 2, 1]]
  258. # Save W matrix
  259. timestamp = datetime.now().strftime("%Y-%m-%d_%H.%M.%S")
  260. pd.DataFrame(W).to_csv(f"tmp/NMF_{region}_{nmf_Comp}_{timestamp}_W.csv",index=False,)
  261. # Reshape H
  262. H = H.reshape(nmf_Comp, 4, 6, 18)
  263. vmin, vmax = 0, 1
  264. levelsC = np.linspace(0.2, vmax, 6)
  265. norm = colors.Normalize(vmin=vmin, vmax=vmax)
  266. # Figure layout
  267. plt.rcParams.update({"font.size": 14})
  268. fig, axes = plt.subplots(1,5,figsize=(16, 4),constrained_layout=True,)
  269. contour_cmaps = ["Greys","Greens","Reds","Blues","Purples","Oranges",]
  270. # Contour plots (first 4 columns)
  271. y_axis_thresholds = [0.5, 1, 2, 5, 10, 20]
  272. for f in range(4):
  273. ax = axes[f]
  274. for nmf_i in range(nmf_Comp):
  275. contour = ax.contour(x_axis_bins[f],range(6),H[nmf_i, f],levels=levelsC,cmap=contour_cmaps[nmf_i],
  276. alpha=0.7,linewidths=3,norm=norm,)
  277. contour.cmap.set_under("white")
  278. ax.set_xscale("log")
  279. ax.set_xlim(x_axis_bins[f].min(),x_axis_bins[f].max(),)
  280. ax.set_xlabel(feature_labels[f])
  281. ax.set_yticks(range(6))
  282. if f == 0:
  283. ax.set_yticks(range(6))
  284. ax.set_yticklabels(y_axis_thresholds)
  285. ax.tick_params(axis="y", labelleft=True)
  286. ax.set_ylabel(r"$Ca^{2+}$ threshold")
  287. # Force x ticks to show on log scale
  288. ax.tick_params(axis="x", which="both", labelbottom=True)
  289. ax.set_aspect("auto")
  290. fig.supylabel(suptext, color=suptextcolor,fontsize=16, fontweight="bold")
  291. # Weight distribution (last column)
  292. ax_w = axes[4]
  293. cmap_weights = ["grey","green","red","blue","purple","orange",]
  294. cond_u = dfc["Data"]["Condition"].unique()
  295. WW = np.zeros((len(cond_u), nmf_Comp))
  296. for i, cond_i in enumerate(cond_u):
  297. W_i = W[dfc["Data"]["Condition"] == cond_i]
  298. WW[i] = np.mean(W_i, axis=0)
  299. for i in range(nmf_Comp):
  300. ax_w.plot(cond_u,WW[:, i],ls="--",marker="s",color=cmap_weights[i % len(cmap_weights)],label=f"NMF {i + 1}",)
  301. ax_w.set_xticks(cond_u)
  302. ax_w.set_xticklabels(legend_names)
  303. ax_w.set_xlabel("Condition")
  304. ax_w.set_ylabel("Weight")
  305. ax_w.set_ylim(bottom=0)
  306. ax_w.tick_params(axis="x", which="both", labelbottom=True)
  307. ax_w.tick_params(axis="y", which="both", labelleft=True)
  308. ax_w.legend(frameon=False)
  309. # Export
  310. #plt.savefig(f"tmp/NMF_{region}_{nmf_Comp}.svg",dpi=300,bbox_inches="tight",transparent=True)
  311. plt.show()
  312. # %%
  313. os.makedirs("tmp", exist_ok=True)
  314. nmf_whole(nmf_Comp=3, plot_contour_flag=True, region="HIP", suptext="HC", suptextcolor="#2F4F8F")
  315. nmf_whole(nmf_Comp=3, plot_contour_flag=True, region="PFC", suptext="PFC",suptextcolor="#A01B8E")
  316. nmf_whole(nmf_Comp=3, plot_contour_flag=True, region="iPSC", suptext="PFC",suptextcolor="#18C7A0")
  317. # %% [markdown]
  318. # ## PCA, NMF and LDA
  319. # %%
  320. from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
  321. def lda_compare(region="Whole_data"):
  322. # df = load_data(df_b18_g_5)
  323. features = ["maxArea", "duration", "maxDist", "slopeCa"]
  324. legend_names = ["Ctrl.", "3 h LPS", "24 h LPS"]
  325. if region == "HIP":
  326. df = load_data(df_b18_g_5, 18)
  327. dfc = df[df["Data"]["Condition"] <= 3]
  328. colors_rgb = [(144, 191, 249), (139, 144, 206), (0, 0, 192)] # RGB colors
  329. elif region == "PFC":
  330. df = load_data(df_b18_g_5, 18)
  331. dfc = df[df["Data"]["Condition"] > 3]
  332. colors_rgb = [(255, 160, 160), (193, 139, 183), (128, 0, 128)] # RGB colors
  333. elif region == "iPSC":
  334. dfc = load_data(df_b18_g_5_iPSC, 18)
  335. print("iPSC data loaded")
  336. colors_rgb = [(169, 254, 169), (15, 153, 178), (3, 78, 97)] # RGB colors
  337. else:
  338. df = load_data(df_b18_g_5, 18)
  339. dfc = df
  340. colors_rgb = np.array(colors_rgb) / 255.0 # Normalize RGB values to [0, 1]
  341. X = dfc.iloc[:, 5:] # data (ignore first five columns)
  342. y = dfc.iloc[:, 0] # labels
  343. noC = 2 # no. of components
  344. lda = LDA(n_components=noC) #,shrinkage='Auto',solver='lsqr',priors=None,store_covariance=False, tol=0.0001
  345. pca = PCA(n_components=noC,random_state=22)
  346. #nmf = NMF(n_components=noC)
  347. nmf = NMF(n_components=noC, init='random', random_state=22)
  348. Xs = StandardScaler().fit_transform(X)
  349. Xs = MinMaxScaler().fit_transform(X)
  350. X_lda = lda.fit_transform(X,y)
  351. X_pca = pca.fit_transform(X)
  352. X_nmf = nmf.fit_transform(X)
  353. # Define RGB colors for each condition
  354. unique_conditions = np.unique(y)
  355. color_map = {condition: colors_rgb[i % len(colors_rgb)] for i, condition in enumerate(unique_conditions)}
  356. condition_colors = [color_map[condition] for condition in y]
  357. # Generate a timestamp
  358. timestamp = datetime.now().strftime("%Y-%m-%d_%H.%M.%S")
  359. # Create a PdfPages object with a timestamped file name
  360. file_name = f'tmp/LDA_{region}.pdf' #_{timestamp}
  361. with PdfPages(file_name) as pdf:
  362. res = plt.subplots(1, 3, figsize=(16, 4), constrained_layout=False)
  363. axes : list[plt.Axes] = res[1] # Initialize axes list
  364. fig = res[0]
  365. print("Default font size:", plt.rcParams['font.size'])
  366. adjusted_font_size = plt.rcParams['font.size']-2 # Adjust font size for legends
  367. # PCA scatter plot
  368. for i, condition in enumerate(unique_conditions):
  369. mask = y == condition
  370. axes[0].scatter(X_pca[mask, 0], X_pca[mask, 1], color=color_map[condition], label=legend_names[i])
  371. axes[0].set_title('PCA')
  372. axes[0].set_xlabel('PCA 1')
  373. axes[0].set_ylabel('PCA 2')
  374. axes[0].set_aspect('equal') # Set equal scaling
  375. axes[0].legend(fontsize=adjusted_font_size)
  376. # NMF scatter plot
  377. for i, condition in enumerate(unique_conditions):
  378. mask = y == condition
  379. axes[1].scatter(X_nmf[mask, 0], X_nmf[mask, 1], color=color_map[condition], label=legend_names[i])
  380. axes[1].set_title('NMF')
  381. axes[1].set_xlabel('NMF 1')
  382. axes[1].set_ylabel('NMF 2')
  383. axes[1].set_aspect('equal') # Set equal scaling
  384. axes[1].legend(fontsize=adjusted_font_size)
  385. # LDA scatter plot
  386. for i, condition in enumerate(unique_conditions):
  387. mask = y == condition
  388. axes[2].scatter(X_lda[mask, 0], X_lda[mask, 1], color=color_map[condition], label=legend_names[i])
  389. axes[2].set_title('LDA')
  390. axes[2].set_xlabel('LDA 1')
  391. axes[2].set_ylabel('LDA 2')
  392. axes[2].set_aspect('equal') # Set equal scaling , adjustable='box'
  393. axes[2].legend(fontsize=adjusted_font_size)
  394. # Add legend
  395. axes[2].legend(loc='best', fontsize='small', framealpha=0.5, borderaxespad=0.2,handletextpad=0.2) #, edgecolor='black'
  396. #axes[2].legend(loc='center left', bbox_to_anchor=(1.05, 0.5), fontsize='small', framealpha=0.5, borderaxespad=0.2) #, edgecolor='black'
  397. # Adjust layout for better spacing
  398. plt.tight_layout()
  399. plt.show()
  400. pdf.savefig(fig)
  401. # %%
  402. lda_compare('HIP')
  403. # %%
  404. lda_compare('PFC')
  405. # %% [markdown]
  406. # ## Supplementary Figure 8
  407. # %%
  408. # load data
  409. from matplotlib.backends.backend_pdf import PdfPages
  410. from datetime import datetime
  411. from matplotlib.colors import Normalize
  412. def nmf_whole_modified(nmf_Comp=6, plot_contour_flag=True, region="Whole_data"):
  413. # df = load_data(df_b18_g_5)
  414. df = load_data(df_b18_g_5, 18)
  415. features = ["maxArea", "duration", "maxDist", "slopeCa"]
  416. legend_names = ["Ctrl.", "3 h LPS", "24 h LPS"]
  417. if region == "HIP":
  418. dfc = df[df["Data"]["Condition"] <= 3]
  419. elif region == "PFC":
  420. dfc = df[df["Data"]["Condition"] > 3]
  421. elif region == "iPSC":
  422. dfc = load_data(df_b18_g_5_iPSC, 18)
  423. print("iPSC data loaded")
  424. else:
  425. dfc = df
  426. X = dfc.iloc[:, 5:] # data (ignore first five columns)
  427. y = dfc.iloc[:, 0] # labels
  428. nmfC = nmf_Comp # of NMF components
  429. nF = 4 # no. of features
  430. nmf = NMF(n_components=nmfC, init="random", random_state=0, tol=1e-3)
  431. W = nmf.fit_transform(X)
  432. H = nmf.components_
  433. # Normalize H to have a maximum value of 1 and adjust W accordingly
  434. W = W * H.max(axis=1)
  435. H = H / H.max(axis=1, keepdims=True)
  436. # Manually reorganize matrices to fit color sceme
  437. if region == "HIP":
  438. if nmf_Comp == 2:
  439. H = H[[1, 0], :]
  440. W = W[:, [1, 0]]
  441. # elif nmf_Comp == 3:
  442. # H = H[[0, 2, 1], :]
  443. # W = W[:, [0, 2, 1]]
  444. elif region == "iPSC":
  445. # if nmf_Comp == 2:
  446. # H = H[[1, 0], :]
  447. # W = W[:, [1, 0]]
  448. if nmf_Comp == 3:
  449. H = H[[0, 2, 1], :]
  450. W = W[:, [0, 2, 1]]
  451. # print(H[:5, :5])
  452. # print(H_reorganized[:5, :5])
  453. H = H.reshape(nmfC, 4, 6, 18)
  454. vmin = 0
  455. vmax = 1 # H.max()
  456. levels = np.linspace(vmin, vmax, 11) # Define levels for contourf
  457. levelsC = np.linspace(0.2, vmax, 6) # Define levels for contour
  458. norm = colors.Normalize(vmin=vmin, vmax=vmax)
  459. # Generate a timestamp
  460. timestamp = datetime.now().strftime("%Y-%m-%d_%H.%M.%S")
  461. # Create a PdfPages object with a timestamped file name
  462. file_name = f'tmp/NMF_{region}_{nmf_Comp}_{timestamp}.pdf'
  463. file_name_W = f'tmp/NMF_{region}_{nmf_Comp}_{timestamp}_W.csv'
  464. # Convert W to a DataFrame
  465. W_df = pd.DataFrame(W)
  466. # Save the DataFrame to a CSV file
  467. #W_df.to_csv(file_name_W, index=False)
  468. #print("W matrix saved as:", file_name_W)
  469. # Set the default font size
  470. plt.rcParams.update({'font.size': 12}) # Change 16 to your desired font size
  471. plt.rcParams['xtick.labelsize']=12
  472. plt.rcParams['xtick.bottom'] = True
  473. plt.rcParams['xtick.labelbottom'] = False
  474. plt.rcParams['ytick.left'] = True
  475. plt.rcParams['ytick.labelleft'] = False
  476. plt.rcParams['ytick.labelsize']=12
  477. with PdfPages(file_name) as pdf:
  478. #fig1 = plt.figure(figsize=(18,12))
  479. res = plt.subplots(nmfC,nF, figsize=(18,nmfC*4),sharex=False, sharey=True)
  480. axes1 : list[plt.Axes] = res[1] # Initialize axes list
  481. fig1 = res[0]
  482. plt.suptitle("# NMF components: %d"%nmfC, y=0.92) # Adjust the y parameter for more space
  483. #plt.subplots_adjust(top=0.95) # Adjust the top spacing for more gap
  484. for nmf_i in range(nmfC):
  485. for f in range(nF):
  486. plt.subplot(nmfC,nF,nF*nmf_i +f+1)
  487. if plot_contour_flag:
  488. contour = plt.contourf(x_axis_bins[f],range(0,6,1),H[nmf_i,f],cmap='jet',levels=levels,
  489. vmin=vmin,vmax=vmax, extend='neither')
  490. else:
  491. contour = plt.imshow(H[nmf_i,f], interpolation='bicubic',aspect='auto',
  492. origin='lower',cmap='jet',vmin=0)
  493. contour.set_clim(vmin, vmax) # Set colorbar limits explicitly
  494. if f==0:
  495. plt.tick_params(left=True,labelleft=True)
  496. plt.ylabel(r'$Ca^{2+}$ Threshold')
  497. if nmf_i == nmfC-1:
  498. plt.tick_params(bottom=True,labelbottom=True)
  499. plt.xlabel(features[f])
  500. #fp.cmap.set_over('black')
  501. #fp.cmap.set_under('white')
  502. plt.xscale('log') # Set x-axis to logarithmic scale
  503. plt.yticks(range(0,6,1),y_axis_thresholds,fontsize=15)
  504. #plt.xlim(x_axis_bins[f].min(), x_axis_bins[f].max()) # Set x-axis limits
  505. plt.xticks(fontsize=12)
  506. plt.yticks(fontsize=12)
  507. plt.autoscale(enable=True, axis='x', tight=True)
  508. plt.subplots_adjust(hspace=0.04,wspace=0.04)
  509. cbar = fig1.colorbar(contour,ax=axes1.ravel().tolist(),shrink=0.5,pad=0.02) # ,fontsize=15
  510. cbar.mappable.set_clim(vmin, vmax) # Set colorbar limits explicitly
  511. #fig1.tight_layout()
  512. plt.gca().patch.set_alpha(0) # Make the axes background transparent#
  513. pdf.savefig(fig1) # Save the current figure to the PDF
  514. #plt.savefig("NMF_Components_%d.pdf"%nmfC, dpi=300, bbox_inches='tight')
  515. plt.savefig('NMF_Components_%d_iPSC.svg'%nmfC,format="svg", dpi=600, bbox_inches='tight',transparent=True)
  516. plt.show()
  517. n_nmfc=3
  518. nmf_whole_modified(n_nmfc,plot_contour_flag=True,region = "HIP")

2026-06-05_LPS_Paper_Figs_GS_AZ_KL.ipynb at commit 1e9979f, under GPL-3.0 · at the source

Overview

Authors: Franziska E Müller1, Flavian Ivanov1, Anne-Catharine Studt1, Ida Nitzsche1, Frauke S Bahr1, Anna-Lena Krüger1, Josephine Labus1, Ghanendra Singh2, Evgeni G Ponimaskin1, Kerstin Lenk2, Andre Zeug1
  1. Cellular Neurophysiology, Institute of Neurophysiology, Hannover Medical School, Hannover, Germany
  2. Institute of Neural Engineering, Graz University of Technology, Graz, Austria
Journal: Molecular medicine (Cambridge, Mass.), volume 32, issue 1, article 52
Dates: received 15 August 2025; accepted 26 February 2026; published online 5 March 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1186/s10020-026-01450-3 · PMID 41787269 · PMCID PMC13064076 · OpenAlex W7133828055
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: human (organism), mouse (organism), cellular / molecular (subfield)
Methods: Statistics, Machine learning, fMRI & imaging
Keywords: Astrocytes, Lipopolysaccharide, Ca2+ signalling, Calcium, Heterogeneity
MeSH: Astrocytes*, Calcium*, Induced Pluripotent Stem Cells*, Inflammation*, Lipopolysaccharides*, Animals, Calcium Signaling, Cells, Cultured, Humans, Mice (* major topic)
Topic: Protein Kinase Regulation and GTPase Signaling (Molecular Biology, Biochemistry, Genetics and Molecular Biology), according to OpenAlex
Funding: Medizinische Hochschule Hannover (MHH)
Citations: cited by 1 paper (Europe PMC); 58 references in the paper

Abstract

Background: Mouse and human astrocytes exhibit substantial species-specific differences in both morphology and function. Their response to inflammatory stimuli, however, remains underexplored despite being crucial for understanding bidirectional astrocyte-neuron signaling dynamics and for translating preclinical findings to human-relevant applications. Induced pluripotent stem cell-based models thus offer a powerful platform to investigate these mechanisms in the context of the human neural connectome.

Methods: We apply two well-established in vitro protocols by exposing cultured astrocytes to lipopolysaccharide (LPS) for either 3 or 24 h to trigger an inflammatory response. We investigated how LPS-induced inflammation affects the endogenous Ca2+ activity in astrocytes derived from the mouse hippocampus (HC) and prefrontal cortex (PFC), as well as human induced pluripotent stem cell (hiPSC)-derived astrocytes. Both, morphological changes and Ca2+ activity were analyzed using the volume fraction (VF) approach and our previously developed multi-threshold event detection (MTED) combined with machine learning-driven non-negative matrix factorization (NMF).

Results: The comprehensive assessment of Ca2+ activity patterns and their relation to cell morphology revealed significant alterations in response to LPS treatment, and further between mouse and human hiPSC-derived astrocytes. While both mouse and human astrocytes show increased Ca2+ event frequency after short-term LPS exposure, after 24 h of LPS treatment Ca2+ activity is severely restricted in PFC astrocytes but substantially increased in human astrocytes.

Conclusions: Our findings highlight the unique properties of human iPSC-derived astrocytes and provide detailed insights into how Ca2+ signaling becomes dysregulated under neuroinflammatory conditions. Understanding the species-specific responses is essential for advancing stem cell-based models of human astrocyte-neuron signaling circuits and for developing targeted therapeutic strategies to alleviate neuroinflammation and Ca2+-related dysregulation in neurological diseases.

Supplementary Information: The online version contains supplementary material available at 10.1186/s10020-026-01450-3.

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

Repository

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

kerstinlenk/QuantifyCalciumDynamics

License: GPL-3.0
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Commit: 1e9979fdbc30fc6f47736777ae562e15338a2aa6, 7 June 2026
Languages: Jupyter (1)
Size: 3 files, 1 script
Software Heritage: not archived
Found in: “Data availability”
Holds: README, license file, 1 notebook
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Tools: Matplotlib (1 file), NumPy (1 file), pandas (1 file), scikit-learn (1 file), seaborn (1 file)
Availability: 1 check, the latest on 30 September 2026: the link answers
  • 30 September 2026: the link answers
3 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;
  • 1 script, each with its path and the digest of its content;
  • 1 match 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

Data availability

The data and MATLAB code developed in this study are available from the corresponding author upon request. The Python code is available on GitHub using the following link: https://github.com/kerstinlenk/QuantifyCalciumDynamics.

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

Versions

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

Version 1, 30 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 11 authors, 5 keywords, 10 MeSH terms, 1 funder, 57 references.

Cite

This paper

Müller, F. E., Ivanov, F., Studt, A.-C., Nitzsche, I., Bahr, F. S., Krüger, A.-L., Labus, J., Singh, G., Ponimaskin, E. G., Lenk, K., & Zeug, A. (2026). LPS-induced inflammation differentially affects endogenous Ca&lt;sup&gt;2&lt;/sup&gt;⁺ activity in mouse and human iPSC-derived astrocytes. Molecular medicine (Cambridge, Mass.), 32(1), 52. https://doi.org/10.1186/s10020-026-01450-3

BibTeX

@article{muller2026lps,
author = {Müller, Franziska E and Ivanov, Flavian and Studt, Anne-Catharine and Nitzsche, Ida and Bahr, Frauke S and Krüger, Anna-Lena and Labus, Josephine and Singh, Ghanendra and Ponimaskin, Evgeni G and Lenk, Kerstin and Zeug, Andre},
title = {{LPS-induced inflammation differentially affects endogenous Ca\&lt;sup\&gt;2\&lt;/sup\&gt;⁺ activity in mouse and human iPSC-derived astrocytes}},
journal = {Molecular medicine (Cambridge, Mass.)},
year = {2026},
month = mar,
volume = {32},
number = {1},
pages = {52},
publisher = {The Feinstein Institute for Medical Research},
issn = {1076-1551},
doi = {10.1186/s10020-026-01450-3},
url = {https://doi.org/10.1186/s10020-026-01450-3},
pmid = {41787269},
pmcid = {PMC13064076}
}

RIS

TY - JOUR
AU - Müller, Franziska E
AU - Ivanov, Flavian
AU - Studt, Anne-Catharine
AU - Nitzsche, Ida
AU - Bahr, Frauke S
AU - Krüger, Anna-Lena
AU - Labus, Josephine
AU - Singh, Ghanendra
AU - Ponimaskin, Evgeni G
AU - Lenk, Kerstin
AU - Zeug, Andre
TI - LPS-induced inflammation differentially affects endogenous Ca&lt;sup&gt;2&lt;/sup&gt;⁺ activity in mouse and human iPSC-derived astrocytes
T2 - Molecular medicine (Cambridge, Mass.)
J2 - Mol Med
PY - 2026
DA - 2026/03/05
VL - 32
IS - 1
SP - 52
SN - 1076-1551
PB - The Feinstein Institute for Medical Research
DO - 10.1186/s10020-026-01450-3
UR - https://doi.org/10.1186/s10020-026-01450-3
LA - en
ER -

CSL-JSON

{
"id": "10.1186/s10020-026-01450-3",
"type": "article-journal",
"title": "LPS-induced inflammation differentially affects endogenous Ca&lt;sup&gt;2&lt;/sup&gt;⁺ activity in mouse and human iPSC-derived astrocytes",
"container-title": "Molecular medicine (Cambridge, Mass.)",
"author": [
{
"family": "Müller",
"given": "Franziska E"
},
{
"family": "Ivanov",
"given": "Flavian"
},
{
"family": "Studt",
"given": "Anne-Catharine"
},
{
"family": "Nitzsche",
"given": "Ida"
},
{
"family": "Bahr",
"given": "Frauke S"
},
{
"family": "Krüger",
"given": "Anna-Lena"
},
{
"family": "Labus",
"given": "Josephine"
},
{
"family": "Singh",
"given": "Ghanendra"
},
{
"family": "Ponimaskin",
"given": "Evgeni G"
},
{
"family": "Lenk",
"given": "Kerstin"
},
{
"family": "Zeug",
"given": "Andre"
}
],
"container-title-short": "Mol Med",
"volume": "32",
"issue": "1",
"page": "52",
"DOI": "10.1186/s10020-026-01450-3",
"PMID": "41787269",
"PMCID": "PMC13064076",
"ISSN": "1076-1551",
"publisher": "The Feinstein Institute for Medical Research",
"URL": "https://doi.org/10.1186/s10020-026-01450-3",
"language": "en",
"issued": {
"date-parts": [
[
2026,
3,
5
]
]
}
}

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

Similar papers

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

[1] doi:10.1111/jnc.70470 [code]
Calcium Buffering in Astrocytes and Its Relevance for Experimental Data Interpretation and Computational Modeling.
Journal: Journal of neurochemistry
In common: cellular / molecular, 5 references, 2 authors
[2] doi:10.1186/s12974-026-03837-9
PTP1B in astrocytes drives pathogen-induced neurodegeneration.
Journal: Journal of neuroinflammation
In common: mouse, cellular / molecular, 6 references
[3] doi:10.1038/s41467-026-73435-z [code]
Cellular and subcellular heterogeneity of astrocytic Na⁺ homeostasis tuning astrocytes into functionally distinct subgroups in the mouse brain.
Journal: Nature communications
In common: seaborn, pandas, Matplotlib, 1 other tool, mouse, cellular / molecular, 3 references
[4] doi:10.3390/ijms27135902
Functional Expression of Nicotinic Receptors on iPSC-Derived Astrocytes and Signalling Disturbances by a Panel of Neonicotinoid Pesticides and Their Metabolites.
Journal: International journal of molecular sciences
In common: cellular / molecular, 5 references
[5] doi:10.1167/iovs.67.5.73 [code]
Identification of a Small-Molecule Modulator of Astrocyte Reactivity for Optic Nerve Protection.
Journal: Investigative ophthalmology & visual science
In common: scikit-learn, pandas, Matplotlib, 1 other tool, mouse, cellular / molecular, 3 references
[6] doi:10.1002/alz.71745 [code]
Regional astrocyte dysregulation and altered glymphatic-related markers in Alzheimer's disease frontal cortex.
Journal: Alzheimer's & dementia : the journal of the Alzheimer's Association
In common: seaborn, pandas, Matplotlib, 1 other tool, cellular / molecular, 3 references
[7] doi:10.1111/ejn.70480 [code]
Astrocyte Proximity Protects Synapses From Human Amyloid-Beta Induced Degeneration in a Mouse Ex Vivo Model of Early Alzheimer's Disease.
Journal: The European journal of neuroscience
In common: seaborn, scikit-learn, pandas, 2 other tools, mouse, cellular / molecular, 2 references
[8] doi:10.1016/j.celrep.2026.117803
APOE3 and APOE4 human astrocytes differentially modulate Alzheimer's disease pathology and microglial responses in chimeric mice.
Journal: Cell reports
In common: mouse, cellular / molecular, 4 references
[9] doi:10.1038/s41514-026-00391-9 [code]
Region-specific transcriptional signatures of brain aging in the absence of neuropathology at the single-cell level.
Journal: npj aging
In common: seaborn, scikit-learn, pandas, 2 other tools, cellular / molecular, 2 references
[10] doi:10.1038/s41467-026-76837-1 [code]
Drug screen and machine learning predict neuroprotective agents in a preclinical human model of childhood dementia.
Journal: Nature communications
In common: seaborn, scikit-learn, pandas, 2 other tools, 2 references

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.