A modular multi-color fluorescence microscope for simultaneous tracking of cellular activity and behavior.
The 6 matches
- [1] § Methods › Statistical analysis of calcium imaging ↔ TRN_MixedLinearModel.ipynb, lines 78–91 · score 0.64 · mixed linear model, post, calcium, worms, reversals, signal
- [2] § Methods › Gait identification of tardigrades ↔ tardigrade_cluster.ipynb, lines 299–323 · score 0.62 · pairwise distance, UMAP, embedding, clustering, tardigrades
- [3] § Methods › Statistical analysis of prey signal ↔ GFP_preycontact.ipynb, lines 243–299 · score 0.59 · biting events, bite onset, baseline, ratio, GFP, signal
- [4] § Methods › Pose estimation of tardigrades ↔ tardigrade_cluster.ipynb, lines 128–152 · score 0.59 · stance duration, duty factor, swing, tardigrades
- [5] § Results › Brightfield tracking of tardigrades ↔ tardigrade_cluster.ipynb, lines 336–365 · score 0.50 · silhouette score, embedding, cycles, clustering, leg, tardigrades
- [6] § Results › Ratiometric imaging of single neurons in moving animals ↔ Dros_fig2.ipynb, lines 14–137 · score 0.50 · motion correction, ratiometric imaging, R0, tracked
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 · 602 lines · 24 KB · no license · 3 matches
- # %%
- import pandas as pd
- import numpy as np
- from scipy.signal import savgol_filter
- import os
- import yaml
- import matplotlib.pyplot as plt
- from tslearn import metrics, barycenters
- import matplotlib.pyplot as plt
- from tslearn.preprocessing import TimeSeriesScalerMeanVariance, \
- TimeSeriesResampler
- from scipy.signal import find_peaks
- import tardigrade_functions as tg
- from sklearn.cluster import AgglomerativeClustering
- from sklearn.metrics import silhouette_score
- from matplotlib.gridspec import GridSpec
- from scipy import stats
- from umap import UMAP
- # %%
- # settings for dataset
- date = 20251209
- home = os.path.expanduser("~")
- expID = 'LB016'
- inpath = os.path.join(home, f'data/{expID}/out_{date}')
- pose_path = os.path.join(home, f'data/{expID}/poseestimation')
- pose_file = 'DLC_Resnet50_TardiLocoOct23shuffle1_snapshot_200.csv'
- # %%
- # load config
- config_path = "config.yaml"
- config = yaml.safe_load(open(config_path, "r"))
- fps = config['video']['fps']
- bodyparts = config['analysis']['bodyparts']
- limbs = config['analysis']['limbs']
- limbs_ord = config['analysis']['limbs_ord']
- pairs = config['analysis']['pairs']
- bp_pairs = config['analysis']['bp_pairs']
- bp_color_dict = config['color']['bp_color']
- roi_defined = config['analysis']['roi_defined']
- idx = pd.IndexSlice
- # %%
- Trkangle_all = pd.DataFrame([])
- CMSvelo_all = pd.DataFrame([])
- CLbp_all = pd.DataFrame([])
- swing_all = pd.DataFrame([])
- turns_all = pd.DataFrame([])
- straight_all = pd.DataFrame([])
- lastmaxidx = 0
- for f in os.listdir(inpath):
- if expID in f and not '.' in f:
- # load angle of tardigrade track
- fangle = pd.read_csv(os.path.join(inpath,f,f'{f}_trackangle.csv'), index_col=0).fillna(0)
- _ = savgol_filter(fangle, fps, 2, axis=0)
- fangle_smooth = pd.DataFrame(_, columns=fangle.columns, index=fangle.index)
- fangle_smooth.index = pd.MultiIndex.from_product([[f],fangle_smooth.index])
- Trkangle_all = pd.concat([Trkangle_all,fangle_smooth])
- # load cms coordinates
- cms = pd.read_csv(os.path.join(inpath,f,f'{f}_stage.csv'),index_col=0).loc[:,['Xcms','Ycms']]
- cmsvelo = np.sqrt(cms.iloc[:,0].diff(5)**2 + cms.iloc[:,1].diff(5)**2).interpolate(limit_direction='both')/5*fps
- cmsvelo.index = pd.MultiIndex.from_product([[f],cmsvelo.index])
- CMSvelo_all = pd.concat([CMSvelo_all,cmsvelo.to_frame()])
- # load leg position on centerline
- f_clbp = pd.read_csv(os.path.join(inpath,f,f'{f}_CLbp.csv'), index_col=0, header=[0,1,2])
- f_clbp_norm = (f_clbp - f_clbp.mean(axis=0)) / f_clbp.std(axis=0)
- f_clbp_x = f_clbp_norm.loc[:,idx[:,:,'x']].droplevel(level=[0,2],axis=1).fillna(0)
- CLbp_all = pd.concat([CLbp_all,f_clbp_x])
- # load bool for swings
- fswing = pd.read_csv(os.path.join(inpath,f,f'{f}_swing.csv'), index_col=0)
- fswing.index = pd.MultiIndex.from_product([[f],fswing.index])
- swing_all = pd.concat([swing_all,fswing])
- # load bool for turns
- fturns = pd.read_csv(os.path.join(inpath,f,f'{f}_turns.csv'), index_col=0)+lastmaxidx
- fturns.index = pd.MultiIndex.from_product([[f],fturns.index])
- turns_all = pd.concat([turns_all,fturns])
- # load bool for straight walks
- fstraight = pd.read_csv(os.path.join(inpath,f,f'{f}_straight.csv'), index_col=0)
- fstraight.index = pd.MultiIndex.from_product([[f],fstraight.index])
- st_offset = pd.DataFrame([False]*(len(f_clbp)-len(fstraight)), columns=fstraight.columns)
- st_offset.index = pd.MultiIndex.from_product([[f],st_offset.index])
- straight_all = pd.concat([straight_all, fstraight, st_offset])
- lastmaxidx += len(f_clbp)
- # %%
- lastmaxidx = 0
- rec_cycinfo = {}
- cyc_bouts = pd.Series([])
- cyc_rec = pd.Series([])
- # split recordings into bins based on swings
- for i,rec in enumerate(swing_all.index.get_level_values(0).unique()):
- # get swing onsets
- swing_dict = tg.get_bouts(swing_all.loc[rec][limbs])
- swing_on = {k:[s[0] for s in t] for k,t in swing_dict.items()}
- swing_dur = {k:[s[1] for s in t] for k,t in swing_dict.items()}
- on_df = pd.DataFrame().from_dict(swing_on, orient='index').T
- # use the first new swing onset of leg to determine bin end
- cyc = on_df.min(axis=1).astype(int)
- cyc = pd.concat([pd.Series([0]), cyc, pd.Series(len(swing_all.loc[rec]))])
- cyc_ = cyc + lastmaxidx
- # keep bin duration and bin index for each rec
- cyc_info = cyc_.diff().rename('dur').dropna().reset_index(drop=True).to_frame()
- cyc_info['idx'] = cyc_info.index + len(cyc_bouts) #use +len(cyc_bouts) to have one index for all recs
- rec_cycinfo[rec] = cyc_info
- # keep rec index for bins
- cyc_bouts = pd.concat([cyc_bouts,cyc_[:-1]])
- cyc_rec = pd.concat([cyc_rec,pd.Series(np.full_like(cyc_[:-1], i))])
- lastmaxidx += len(swing_all.loc[rec])
- cyc_bouts = cyc_bouts.reset_index(drop=True)
- len(cyc_bouts), cyc_bouts.diff().mean(), cyc_bouts.diff().std()
- # %%
- # duty factor
- stancedur_all = pd.DataFrame([])
- phasedur_all = pd.DataFrame([])
- phasevelo_all = pd.DataFrame([])
- for i,rec in enumerate(swing_all.index.get_level_values(0).unique()):
- stance_dict = tg.get_bouts(~swing_all.loc[rec][limbs])
- stance_on = {k:[s[0] for s in t] for k,t in stance_dict.items()}
- stance_dur = {k:[s[1] for s in t] for k,t in stance_dict.items()}
- on_df = pd.DataFrame().from_dict(stance_on, orient='index').T
- stancedur = pd.DataFrame().from_dict(stance_dur, orient='index').T
- stancedur.index = pd.MultiIndex.from_product([[rec],stancedur.index])
- stancedur_all = pd.concat([stancedur_all,stancedur])
- phasedur = pd.DataFrame([np.diff(on_df[bp], append=len(swing_all.loc[rec]), axis=0) for bp in limbs], index=limbs).T
- phasedur.index = pd.MultiIndex.from_product([[rec],phasedur.index])
- phasedur_all = pd.concat([phasedur_all,phasedur])
- bp_grouper = pd.DataFrame([on_df[bp].dropna().set_axis(on_df[bp].dropna()).reindex(np.arange(len(swing_all.loc[rec])), method='ffill') for bp in limbs], index=limbs).T
- phasevelo = pd.concat([CMSvelo_all.loc[rec].groupby(bp_grouper[bp].values).mean().reset_index(drop=True) for bp in limbs], axis=1)
- phasevelo.columns = limbs
- phasevelo.index = pd.MultiIndex.from_product([[rec],phasevelo.index])
- phasevelo_all = pd.concat([phasevelo_all,phasevelo])
- duty_factor = stancedur_all / phasedur_all
- # %%
- # set base outpath
- outpath = os.path.join(inpath, 'cluster', 'out_cluster_050226')
- if not os.path.exists(outpath):
- os.makedirs(outpath)
- # %%
- np.sum(duty_factor.count(axis=0).values.reshape(4,2),axis=1), duty_factor.count(axis=0)
- # %%
- duty_factor.mean(axis=0)
- # %%
- #pair_color = ['r', 'g', 'b', 'y']
- fig, axs = plt.subplots(1,5, figsize=(10,3), width_ratios=[1,1,1,1,.05])
- for i,p in enumerate(['leg_1', 'leg_2', 'leg_3', 'hindleg']):
- pair = [bp for bp in duty_factor.columns if p in bp]
- or_nan = (phasevelo_all[pair].isna().any(axis=1) | duty_factor[pair].isna().any(axis=1))
- phasevelo_pair = phasevelo_all[pair][~or_nan]#.values.flatten()
- phasevelo_pair = pd.concat([phasevelo_pair[c] for c in pair])
- duty_factor_pair = duty_factor[pair][~or_nan]#.values.flatten()
- duty_factor_pair = pd.concat([duty_factor_pair[c] for c in pair])
- # test individually
- rs, pvs = [],[]
- for rec in phasevelo_pair.index.get_level_values(0).unique():
- r,pv = stats.spearmanr(phasevelo_pair.loc[rec], duty_factor_pair.loc[rec])
- rs.append(r)
- pvs.append(pv)
- # combine after fisher, using fisher-z transformation for rho and fisher method for p values
- fisher_z = np.arctanh(rs)
- fisher_z_mean = np.mean(fisher_z)
- r_mean = np.tanh(fisher_z_mean)
- chi2, p_comb = stats.combine_pvalues(pvs)
- print(p, r_mean, p_comb, chi2)
- Z, xedges, yedges = np.histogram2d(phasevelo_pair, duty_factor_pair, bins=[50,30], range=[[0,150],[0,1]], density=True)
- _ = axs[i].pcolormesh(xedges, yedges, Z.T, cmap='magma', vmin=0, vmax=.21)
- axs[i].set_title(p)
- axs[i].text(1,.01,f'r: {np.round(r_mean, 2)}; p: {np.format_float_scientific(p_comb, precision=2)}', c='w', )
- plt.colorbar(_, cax=axs[-1])
- plt.savefig(os.path.join(outpath,f'duty_factor.pdf'), bbox_inches='tight')
- plt.show()
- # %%
- # example bins
- fig, axs = plt.subplots(1,10, figsize=(20,2), sharex=True)
- s = 100
- for i,j in enumerate(range(s,s+10)):
- axs[i].imshow(swing_all.iloc[cyc_bouts[j]:cyc_bouts[j+1]][limbs_ord].T, aspect='auto', interpolation='None')
- plt.savefig(os.path.join(outpath,'examplecycle.png'), bbox_inches='tight')
- # %% [markdown]
- # ### cross-similarity
- # %%
- # wrangle bins for each data type into lists
- X_train = [CLbp_all[limbs].iloc[cyc_bouts[j]:cyc_bouts[j+1]].values for j in range(len(cyc_bouts)-1)] #limbs / frontlegs
- swing_train = [swing_all[limbs].iloc[cyc_bouts[j]:cyc_bouts[j+1]].values for j in range(len(cyc_bouts)-1)] #limbs / frontlegs
- straight_train = [straight_all.iloc[cyc_bouts[j]:cyc_bouts[j+1]].values for j in range(len(cyc_bouts)-1)]
- angle_train = [Trkangle_all.iloc[cyc_bouts[j]:cyc_bouts[j+1]].values for j in range(len(cyc_bouts)-1)]
- cmsvelo_train = [CMSvelo_all.iloc[cyc_bouts[j]:cyc_bouts[j+1]].values for j in range(len(cyc_bouts)-1)]
- cyc_idx_turns = np.concatenate([np.where((cyc_bouts[:-1]<t).values&(cyc_bouts[1:]>t).values)[0] for t in turns_all.values.flatten()])
- cyc_trainidx = np.arange(len(cyc_bouts)-1)#[np.array(legs_in_swing) >= 8]
- dur_train = [len(x) for x in X_train]
- len(dur_train), np.mean(dur_train), np.std(dur_train)
- # %%
- # resample into same length
- ts_resampler = TimeSeriesResampler(sz=15)
- X_train = ts_resampler.fit_transform(X_train)
- X_train = TimeSeriesScalerMeanVariance().fit_transform(X_train)
- # transform bools for swing and straight accordingly
- swing_train_new = ts_resampler.transform(swing_train) #TODO check if necessary when below step
- swing_train_new = np.round(swing_train_new).astype(int)
- straight_train_new = ts_resampler.transform(straight_train) #TODO check if necessary when below step
- straight_train_new = np.round(straight_train_new).astype(int)
- # %%
- # get mean angle and velocity for each bin
- angle_train_new = np.vstack([np.nanmean(v) for v in angle_train]).flatten()
- cmsvelo_train_new = np.vstack([np.nanmean(v) for v in cmsvelo_train]).flatten()
- cmsvelo_train_new.shape
- # %%
- # compute cross similarity matrix for different sets of bodyparts, using dynamical time warping
- dtw_list = []
- dtw_range = []
- for r in [(0,8), (0,6)]:
- x_train = X_train[:,:,r[0]:r[1]]
- dtw_dist = metrics.cdist_dtw((x_train), (x_train))
- dtw_list.append(dtw_dist)
- dtw_range.append(r)
- # %%
- # plot cross-similarity in comparable way between sets
- triu_idx = np.triu_indices(dtw_list[0].shape[0])
- dtw_list_max = np.max([np.max(d) for d in dtw_list])
- dtw_list_diff_max = np.max([np.max(abs(dtw_list[0]-d)) for d in dtw_list])+1
- for d,r in zip(dtw_list,dtw_range):
- plt.imshow(d, vmin=0, vmax=dtw_list_max)
- plt.colorbar()
- plt.savefig(os.path.join(outpath,f'dtw_crosssimilaritydistance_{r[0]}-{r[1]}.pdf'), bbox_inches='tight')
- plt.show()
- plt.imshow(dtw_list[0]-d,vmin=0, vmax=dtw_list_diff_max)
- plt.colorbar()
- plt.savefig(os.path.join(outpath,f'dtw_crosssimilaritydistance_{r[0]}-{r[1]}_difffull.pdf'), bbox_inches='tight')
- plt.show()
- # %%
- # calculate the agreement between pairwise cross-similarity matrices using spearman r
- agree_mat = np.zeros((len(dtw_list),len(dtw_list)))
- for i in range(len(dtw_list)):
- for j in range(len(dtw_list)):
- dtw_i = dtw_list[i]
- dtw_j = dtw_list[j]
- rho, p = stats.spearmanr(dtw_i[triu_idx], dtw_j[triu_idx])
- agree_mat[i,j] = rho
- # plot agreement
- plt.imshow(agree_mat, vmin=0, vmax=1)
- plt.colorbar()
- plt.savefig(os.path.join(outpath,f'dtw_spearmanr.pdf'), bbox_inches='tight')
- np.savetxt(os.path.join(outpath,f'dtw_spearmanr.csv'), agree_mat, delimiter=',')
- # %% [markdown]
- # ### clustering
- # %%
- # define x_train for one set of bodyparts, redo for others
- dtw_dist = dtw_list[0] # [1]
- dtw_name = dtw_range[0] # [1]
- x_train = X_train[:,:,dtw_name[0]:dtw_name[1]]
- outpath_leg = os.path.join(outpath,f'legs{dtw_name[0]}-{dtw_name[1]}')
- if not os.path.exists(outpath_leg):
- os.makedirs(outpath_leg)
- # %%
- # embedd bins into lower dim space using umap and similarity matrix as pairwise distance
- rng = np.random.default_rng(seed=42) #set random seeds to evaluate randomness in UMAP
- # UMAP paramaters
- neigh = 30
- neg_rate = 30
- min_dist = 0
- disc_dist = 12
- rep_str = 1
- # define UMAP
- emb = UMAP(metric='precomputed', init='random', n_neighbors=neigh, min_dist=min_dist,
- negative_sample_rate=neg_rate,
- disconnection_distance=disc_dist,
- repulsion_strength=rep_str,
- )
- # perform embedding multiple times
- Xemb_l = []
- while len(Xemb_l) < 6:
- emb.random_state = rng.integers(1000)
- print(emb.random_state)
- Xemb = emb.fit_transform(dtw_dist)
- Xemb_l.append(Xemb)
- # %%
- # plot UMAP embeddings
- fig, axs = plt.subplots(2, len(Xemb_l)//2, figsize=(10,5))
- axs = axs.T.flatten()
- for i in range(len(Xemb_l)):
- sc = axs[i].scatter(*Xemb_l[i].T, alpha=1, s=1, c=cmsvelo_train_new)
- axs[i].axis('equal')
- plt.colorbar(sc)
- plt.savefig(os.path.join(outpath_leg,f'UMAP_neigh{neigh}_mindist{min_dist}_negrate{neg_rate}_discdist{disc_dist}_repstr{rep_str}_legs{dtw_name[0]}-{dtw_name[1]}.pdf'), bbox_inches='tight')
- plt.show()
- # %%
- # perform clustering on embeddings
- # cluster settings
- nclust = 4
- metr = 'euclidean'
- link = 'ward'
- # cluster fr all embeddings
- cluster_l = []
- silscore_l = []
- for i in range(len(Xemb_l)):
- cluster = AgglomerativeClustering(n_clusters=nclust, metric=metr, linkage=link).fit(Xemb_l[i])
- cluster_l.append(cluster)
- silscore_l.append(silhouette_score(Xemb_l[i], cluster.labels_))
- # plot clustering
- plt.rcParams["axes.prop_cycle"] = plt.cycler("color", plt.cm.viridis(np.linspace(0,1,len(np.unique(cluster.labels_)))))
- fig, axs = plt.subplots(4, len(Xemb_l)//2, figsize=(10,5), height_ratios=(1,.1,1,.1))
- axs = axs.T.flatten()
- for i,j in enumerate(range(0,len(Xemb_l)*2,2)):
- for c in np.unique(cluster_l[i].labels_):
- axs[j].scatter(*Xemb_l[i][cluster_l[i].labels_==c].T, alpha=1,s=2, label=c)
- axs[j].axis('equal')
- axs[j+1].text(0,0, f'silscore: {np.round(float(silscore_l[i]),4)}')
- axs[j+1].axis('off')
- plt.legend()
- plt.savefig(os.path.join(outpath_leg,f'AgglomClust_ncl{nclust}_metric{metr}_link{link}.pdf'), bbox_inches='tight')
- plt.show()
- plt.rcdefaults()
- # %%
- # calculate agreement matrix between clusters
- agree_mat = np.zeros((len(Xemb_l),len(Xemb_l)))
- for i in range(len(Xemb_l)):
- for j in range(len(Xemb_l)):
- # get labels for two clusters
- labels_i = cluster_l[i].labels_.copy()
- labels_j = cluster_l[j].labels_.copy()
- # calcualte overlap between clusters to map clusters with same identity
- maparr = np.zeros((len(np.unique(labels_i)),len(np.unique(labels_i))))
- for c in np.unique(labels_i):
- n = np.zeros(len(np.unique(labels_i)))
- c_, n_ = np.unique(labels_j[labels_i==c], return_counts=True) # how many of the rows contain the same labels in j as in i
- n[c_] = n_
- maparr[c] = n/np.sum(n)
- # maps clusters with highest overlap, create dict of corresponding clusters
- mapper = {k:np.nan for k in np.unique(labels_i)}
- for c in np.argsort(np.max(maparr, axis=1))[::-1]: # sorts cluster with highest overlap to map first
- m_cand = np.argsort(maparr[c])[::-1] # sorts index of clusters in j, according to overlap with clusters in i
- for m in m_cand:
- if m in mapper.values(): # checks if cluster already assigned
- continue
- mapper[c] = m # assigns cluster
- break
- if not all([o in np.unique(list(mapper.values())) for o in np.unique(labels_i)]):
- raise ValueError(f"Not all values are assigned properly, current mapping dictionary is {mapper}")
- # rename labels in i
- for c in np.unique(labels_i):
- labels_i[labels_i==c] = -mapper[c] #first to negative to not merge different clusters
- labels_i = abs(labels_i) #revert negative with abs
- # after clusters are mapped, calculate agreement
- agree_mat[i,j] = np.sum(labels_i == labels_j)/len(labels_i)
- # plot agreement
- plt.imshow(agree_mat, vmin=0, vmax=1)
- plt.colorbar()
- plt.savefig(os.path.join(outpath_leg,f'agreement.pdf'), bbox_inches='tight')
- np.savetxt(os.path.join(outpath_leg,f'agreement.csv'), agree_mat, delimiter=',')
- print(np.mean(agree_mat), np.std(agree_mat))
- # %%
- # decide for best embedding
- best_embedd = 0
- Xemb = Xemb_l[best_embedd]
- cluster = cluster_l[best_embedd]
- # %%
- np.savetxt(os.path.join(outpath_leg,f'Xemb.csv'), Xemb, delimiter=',')
- np.savetxt(os.path.join(outpath_leg,f'clusterlabels.csv'), cluster.labels_, delimiter=',')
- # %%
- # get barycenter for different clusters
- barycent_clust = {}
- for c in np.unique(cluster.labels_):
- x_c = X_train[cluster.labels_==c]
- barycent_clust[c] = {}
- for bp in range(X_train.shape[2]):
- barycent_clust[c][bp] = barycenters.softdtw_barycenter(x_c[:,:,bp])
- # %%
- # plot hildebrandt style heatmap of clusters, after each bin has been time warped to barycenter
- fig, axs = plt.subplots(len(np.unique(cluster.labels_)),2, figsize=(6,len(np.unique(cluster.labels_))*2.2), sharey=True)
- gridspec = axs.flatten()[0].get_subplotspec().get_gridspec()
- subfigs = [fig.add_subfigure(gs) for gs in gridspec]
- limbs_ord_num = [6,4,2,0,1,3,5,7]
- for c in np.unique(cluster.labels_):
- x_train_c = X_train[cluster.labels_==c]
- bary_c = np.hstack(list(barycent_clust[c].values()))
- x_shift = []
- d_shift = []
- print(len(x_train_c))
- # shift each trace in comparision to barycenter
- for x_ in x_train_c:
- path,d = metrics.dtw_path(bary_c, x_)
- path = np.array(path)
- u,ui = np.unique(path[:,0], return_index=True)
- path = path[ui]
- x_shift.append(x_[path[:,1]])
- d_shift.append(d)
- x_shift = np.stack(x_shift)
- x_shift_mean = np.mean(x_shift, axis=0)
- # plot mean
- subfigs[0].suptitle(f'mean')
- axs[c,0].set_title(f'Cluster {c}')
- im = axs[c,0].imshow(x_shift_mean.T, vmin=-1, vmax=1) #[limbs_ord_num]
- axs[c,0].set_yticks(range(x_shift_mean.shape[1]))
- axs[c,0].set_yticklabels(limbs) #limbs_ord
- plt.colorbar(im)
- # plot barycenter
- subfigs[1].suptitle(f'barycenter')
- axs[c,1].set_title(f'Cluster {c}')
- im = axs[c,1].imshow(bary_c.T, vmin=-1, vmax=1) #[limbs_ord_num]
- plt.colorbar(im)
- plt.savefig(os.path.join(outpath_leg,f'hildeheatmap_c{c}.pdf'), bbox_inches='tight')
- plt.show()
- # %%
- # plot the movement wave of each leg for the different clusters
- limb_color_list = list(pd.Series(bp_color_dict)[limbs])
- for c in np.unique(cluster.labels_):
- rndm = np.random.choice(np.arange(len(cluster.labels_))[cluster.labels_==c], min(500, len(cluster.labels_[cluster.labels_==c])))
- bary_c = np.hstack(list(barycent_clust[c].values()))
- x_rndm = []
- d_rndm = []
- for r in rndm:
- path,d = metrics.dtw_path(bary_c[:,dtw_name[0]:dtw_name[1]], x_train[r])
- path = np.array(path)
- u,ui = np.unique(path[:,0], return_index=True)
- path = path[ui]
- x_rndm.append(X_train[r,path[:,1]])
- d_rndm.append(d)
- plt.rcParams["axes.prop_cycle"] = plt.cycler("color", plt.cm.viridis(np.linspace(0,1,len(rndm))))
- fig = plt.figure(layout="constrained", figsize=(4,10))
- gs = GridSpec(5, 2, figure=fig)
- axs = []
- for i in range(4):
- axs.append(fig.add_subplot(gs[i, 0]))
- axs.append(fig.add_subplot(gs[i, 1]))
- axs.append(fig.add_subplot(gs[i+1, :2]))
- axs = np.array(axs)
- plt.suptitle(f'Cluster {c}')
- r_i = np.argsort(d_rndm)[::-1]
- for i in range(X_train.shape[2]):
- for j,r in enumerate(r_i):
- x_r = x_rndm[r][:,i]
- axs[i].plot(x_r, alpha=.1, zorder=j)
- axs[i].plot(barycent_clust[c][i], ls=':', c=limb_color_list[i], zorder=len(rndm)+1, lw=3)
- axs[-1].plot(barycent_clust[c][i], ls=':', c=limb_color_list[i], lw=5)
- plt.savefig(os.path.join(outpath_leg,f'gaitwave_c{c}.pdf'), bbox_inches='tight')
- plt.show()
- plt.rcdefaults()
- # %% [markdown]
- # ### Statistics
- # %%
- # create a new dataframe o extract statistics
- turns_train_new = np.zeros(len(cluster.labels_))
- turns_train_new[cyc_idx_turns] = 1
- label_tracktype = pd.DataFrame([cluster.labels_, cmsvelo_train_new, angle_train_new, np.round(np.mean(straight_train_new, axis=1)).astype(int).flatten(), turns_train_new], index=['label','velocity','angle','straight','turn']).T
- label_tracktype_rec = label_tracktype.groupby([cyc_rec.reset_index(drop=True),label_tracktype['label']]).mean() # group by biological replicant and label
- # %%
- # ANOVA
- velosamples = []
- anglesamples = []
- for name, gr in label_tracktype_rec.groupby('label'):
- if name < 0:
- continue
- velosamples.append(gr['velocity'].dropna())
- anglesamples.append(gr['angle'].dropna())
- stats.f_oneway(*velosamples), stats.f_oneway(*anglesamples),
- # %%
- # tukey_hsd
- velores = stats.tukey_hsd(*velosamples)
- velo_sig_comb=np.unique(np.array(np.where(velores.pvalue < .05)).T, axis=0)
- velo_sig_combuniq=np.unique((np.sort(velo_sig_comb, axis=1)), axis=0)
- velo_pv_combuniq=velores.pvalue[velo_sig_combuniq[:,0],velo_sig_combuniq[:,1]]
- angleres = stats.tukey_hsd(*anglesamples)
- angle_sig_comb=np.unique(np.array(np.where(angleres.pvalue < .05)).T, axis=0)
- angle_sig_combuniq=np.unique((np.sort(angle_sig_comb, axis=1)), axis=0)
- angle_pv_combuniq=angleres.pvalue[angle_sig_combuniq[:,0],angle_sig_combuniq[:,1]]
- velores.pvalue, angleres.pvalue
- # %%
- def sigbracket_loc(x0, x1, y, h):
- return (x0,y), (x0,y+h), (x1,y+h), (x1,y)
- # %%
- # Plot boxplot for cluster (animal mean) for velocity and angle
- fig, axs = plt.subplots(2, figsize=(7,7))
- for name, gr in label_tracktype_rec.groupby('label'):
- if name < 0:
- continue
- axs[0].boxplot(gr['velocity'].dropna(), positions=[name], showfliers=False)
- axs[0].scatter(np.full_like(gr['velocity'].dropna(),name-.3), gr['velocity'].dropna(), c='k')
- axs[1].boxplot(gr['angle'].dropna(), positions=[name], showfliers=False)
- axs[1].scatter(np.full_like(gr['angle'].dropna(),name-.3), gr['angle'].dropna(), c='k')
- for pv in range(len(velo_pv_combuniq)):
- p0,p1,p2,p3 = sigbracket_loc(velo_sig_combuniq[pv][0],velo_sig_combuniq[pv][1], 90+(10*velo_sig_combuniq[pv][0]),5)
- axs[0].plot(*np.array([p0,p1,p2,p3]).T)
- for pv in range(len(angle_sig_combuniq)):
- p0,p1,p2,p3 = sigbracket_loc(angle_sig_combuniq[pv][0],angle_sig_combuniq[pv][1], .15+(.1*angle_sig_combuniq[pv][0]),.03)
- axs[1].plot(*np.array([p0,p1,p2,p3]).T)
- axs[0].set_ylim(0,100)
- axs[1].set_ylim(0,0.4)
- plt.savefig(os.path.join(outpath_leg,f'clusterdist_velo-angle.pdf'), bbox_inches='tight')
- plt.show()
- # %%
- # plot usage and normalized usage of cluster during turn
- counts = pd.DataFrame([], index=np.unique(cluster.labels_))
- for name, gr in label_tracktype.groupby('turn'):
- count, bin_edge = np.histogram(gr['label'], bins=len(np.unique(cluster.labels_)), range=(0,max(np.unique(cluster.labels_))+1))
- counts.loc[bin_edge[:-1].astype(int),name] = count
- if name == 0:
- continue
- plt.hist(gr['label'], histtype='bar', bins=len(np.unique(cluster.labels_)), range=(0,max(np.unique(cluster.labels_))+1))
- plt.savefig(os.path.join(outpath_leg,f'clusteruse_turn.pdf'), bbox_inches='tight')
- plt.show()
- plt.bar(counts.loc[0:].index, counts.loc[0:,1]/counts.loc[0:,0], .9)
- plt.savefig(os.path.join(outpath_leg,f'clusteruse_turn_norm.pdf'), bbox_inches='tight')
- # %%
- # save cluster labels for each recording
- all_label_org = pd.DataFrame([])
- for f in os.listdir(inpath):
- if expID in f and '.' not in f:
- rec_label = np.full(len(rec_cycinfo[f]), np.nan)
- rec_idx = [t for t in rec_cycinfo[f]['idx'] if t <= len(cluster.labels_)-1]
- rec_label[:len(rec_idx)] = cluster.labels_[rec_idx]
- rec_label_org = np.concatenate([np.repeat(rec_label[i], rec_cycinfo[f]['dur'][i]) for i in range(len(rec_label))]).astype(int)
- print(f, np.unique(rec_label_org, return_counts=True))
- np.savetxt(os.path.join(outpath_leg,f'{f}_gait.csv'), rec_label_org, delimiter=',', fmt='%i')
- rec_label_org = pd.DataFrame(rec_label_org)
- rec_label_org.index = pd.MultiIndex.from_product([[f],rec_label_org.index])
- all_label_org = pd.concat([all_label_org, rec_label_org])
- # %%
tardigrade_cluster.ipynb at commit 6841dc7, no license · at the source
Overview
- Max Planck Research Group Neural Information Flow, Max Planck Institute for Neurobiology of Behavior—Caesar, Bonn, Germany
- International Max Planck Research School for Brain and Behavior, Bonn, Germany
- Max Planck Research Group Genetics of Behavior, Max Planck Institute for Neurobiology of Behavior—Caesar, Bonn, Germany
Abstract
We present a modular epifluorescence tracking microscope which enables ratiometric imaging of muscles, neurons, and other structures in moving animals. The microscope is assembled entirely from commercial parts within 3 h, making the system broadly accessible. Leveraging the improved brightness and bleaching characteristics of recent genetically encoded indicators and fluorophores, the simple microscope is even suitable for calcium imaging of neurons in behaving animals, as we demonstrate in C. elegans. We also show how muscle dynamics in D. melanogaster larvae can be analyzed and how dual color fluorescence tracking elucidates inter-species interactions by visualizing both predatory nematodes and their prey. Finally, we showcase a configuration for brightfield imaging by tracking tardigrade gait as an example of utility for non-labeled species. The affordability of the hardware and ease of use of the accompanying software make this a suitable tool for education in addition to its use in research.
Reproduced under the paper's license (CC BY), from the paper cited above.
Repositories
Its files are read in the Code ↔ Paper reader above, with 6 matches between paragraphs and lines of code.
scholz-lab.github.io/glowtracker
Availability: 1 check, the latest on 28 September 2026: the link answers (HTTP 200)
- 28 September 2026: the link answers (HTTP 200)
scholz-lab/Macroscope-Paper
6841dc791e28c7d1bcfdcc2ab4940f0b6899e933, 23 March 2026Availability: 1 check, the latest on 28 September 2026: the link answers
- 28 September 2026: the link answers
16 files
- CreateScaledDualColorIma
ges.ipynb , Jupyter, 56 lines - Dros_fig2.ipynb, Jupyter, 1,314 lines, 1 match
- Fig_longterm.ipynb, Jupyter, 765 lines
- GFP_preycontact.ipynb, Jupyter, 465 lines, 1 match
- GFP_preycontact_function
s.py , Python, 170 lines - Predations_analysis.ipyn
b , Jupyter, 337 lines - TRN_MixedLinearModel.ipy
nb , Jupyter, 123 lines, 1 match - TRN_anaylsis.ipynb, Jupyter, 277 lines
- TRN_functions.py, Python, 286 lines
- TRN_mask.py, Python, 212 lines
- drosophila_pglow.py, Python, 276 lines
- tardigrade_cluster.ipynb
, Jupyter, 602 lines, 3 matches - tardigrade_clusterplot.i
pynb , Jupyter, 372 lines - tardigrade_functions.py, Python, 423 lines
- tardigrade_indivanalysis
.ipynb , Jupyter, 423 lines - README.md, Text, 11 lines
scholz-lab/GlowTracker
5585048feccaee94ba5a2fd4e7f2c9059f0126a8, 15 July 2026Availability: 1 check, the latest on 28 September 2026: the link answers
- 28 September 2026: the link answers
31 files
- glowtracker/
AutoFocus.py , Python, 197 lines - glowtracker/
Basler_control.py , Python, 362 lines - glowtracker/
DAQ_control.py , Python, 615 lines - glowtracker/
GlowTracker.py , Python, 5,221 lines - glowtracker/
MacroScript.py , Python, 425 lines - glowtracker/
Microscope_macros.py , Python, 1,404 lines - glowtracker/
TestFunctions/ , Python, 52 linesSimpleStageExample.py - glowtracker/
TestFunctions/ , Python, 46 linesTest2.py - glowtracker/
TestFunctions/ , Python, 129 linesTestDO.py - glowtracker/
TestFunctions/ , Python, 88 linesTestKivy.py - glowtracker/
TestFunctions/ , Python, 58 linesTest_AnimationDAQ.py - glowtracker/
TestFunctions/ , Python, 55 linesTest_AnimationDAQ2.py - glowtracker/
TestFunctions/ , Python, 28 linesTestusingOtherFunction.p y - glowtracker/
TestFunctions/ , Python, 143 linescompareFocusEstimation.p y - glowtracker/
TestFunctions/ , Python, 280 linescompareGrabSpeed.py - glowtracker/
TestFunctions/ , Python, 201 lineshardware_trigger_grab.py - glowtracker/
TestFunctions/ , Python, 410 linesmacro_language.py - glowtracker/
TestFunctions/ , Python, 37 linesmotiontest.py - glowtracker/
TestFunctions/ , Python, 120 linessensor_readout_time_vs_r oi.py - glowtracker/
TestFunctions/ , Python, 130 linessimpleAcquisition.py - glowtracker/
TestFunctions/ , Python, 78 linestestCameraIO.py - glowtracker/
TestFunctions/ , Python, 486 linestestDualColorCalibration .py - glowtracker/
TestFunctions/ , Python, 339 linestestHardWareTriggerAcqui sition.py - glowtracker/
TestFunctions/ , Python, 76 linestestLabJack.py - glowtracker/
TestFunctions/ , Python, 84 linestestObjectDetection.py - glowtracker/
TestFunctions/ , Python, 85 linestestZaberStageMovementSp eed.py - glowtracker/
Zaber_control.py , Python, 573 lines - glowtracker/
__init__.py , Python, 3 lines - glowtracker/
__main__.py , Python, 17 lines - LICENSE.txt, License, 674 lines
- README.md, Text, 100 lines
Code availability
The Glowtracker software is available on GitHub https://
Reproduced under the paper's license (CC BY), from the paper cited above.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 3 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 44 scripts, each with its path and the digest of its content;
- 6 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
Data Availability Statement
The data underlying this manuscript are shown in the figures and supplementary tables. Extended behavioral and imaging data are available at https://
The Glowtracker software is available on GitHub https://
Reproduced under the paper's license (CC BY), from the paper cited above.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 1, 28 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 6 authors, 3 keywords, 8 MeSH terms, 2 funders, 71 references.
Cite
This paper
Ramahefarivo, E., Böger, L., Saichol, T., Shomali, B., Alvarez, L., & Scholz, M. (2026). A modular multi-color fluorescence microscope for simultaneous tracking of cellular activity and behavior. Nature communications, 17(1), 4412. https://
BibTeX
@article{ramahefarivo202
author = {Ramahefarivo, Euphrasie and Böger, Leonard and Saichol, Takkasila and Shomali, Behzad and Alvarez, Luis and Scholz, Monika},
title = {{A modular multi-color fluorescence microscope for simultaneous tracking of cellular activity and behavior}},
journal = {Nature communications},
year = {2026},
month = may,
volume = {17},
number = {1},
pages = {4412},
publisher = {Nature Publishing Group},
issn = {2041-1723},
doi = {10.1038/
url = {https://
pmid = {42156370},
pmcid = {PMC13187449}
}
RIS
TY - JOUR
AU - Ramahefarivo, Euphrasie
AU - Böger, Leonard
AU - Saichol, Takkasila
AU - Shomali, Behzad
AU - Alvarez, Luis
AU - Scholz, Monika
TI - A modular multi-color fluorescence microscope for simultaneous tracking of cellular activity and behavior
T2 - Nature communications
J2 - Nat Commun
PY - 2026
DA - 2026/
VL - 17
IS - 1
SP - 4412
SN - 2041-1723
PB - Nature Publishing Group
DO - 10.1038/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1038/
"type": "article-journal",
"title": "A modular multi-color fluorescence microscope for simultaneous tracking of cellular activity and behavior",
"container-title": "Nature communications",
"author": [
{
"family": "Ramahefarivo",
"given": "Euphrasie"
},
{
"family": "Böger",
"given": "Leonard"
},
{
"family": "Saichol",
"given": "Takkasila"
},
{
"family": "Shomali",
"given": "Behzad"
},
{
"family": "Alvarez",
"given": "Luis"
},
{
"family": "Scholz",
"given": "Monika"
}
],
"container-title-short":
"volume": "17",
"issue": "1",
"page": "4412",
"DOI": "10.1038/
"PMID": "42156370",
"PMCID": "PMC13187449",
"ISSN": "2041-1723",
"publisher": "Nature Publishing Group",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
5,
19
]
]
}
}
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-72709-w [code]
- An epifluorescence microscope design for naturalistic behavior and cellular activity in freely moving Caenorhabditis elegans.Journal: Nature communicationsIn common: tifffile, OpenCV, scikit-image, 5 other tools, C. elegans, histology / microscopy, 12 references
- [2] doi:10.1038/s41593-026-02257-5 [code]
- Neural sequences underlying directed turning in Caenorhabditis elegans.Journal: Nature neuroscienceIn common: tifffile, Pillow, pandas, 3 other tools, C. elegans, 7 references
- [3] doi:10.1038/s41467-026-72437-1 [code]
- High-speed whole-brain imaging in Drosophila.Journal: Nature communicationsIn common: OpenCV, scikit-image, Pillow, 6 other tools, drosophila, 5 references
- [4] doi:10.1371/journal.pcbi.1013441 [code]
- Large vision model framework for automated C. elegans analysis: From static morphometry to dynamic neural activity.Journal: PLoS computational biologyIn common: tifffile, OpenCV, scikit-image, 5 other tools, C. elegans, optical imaging (calcium, voltage, 2-photon), 3 references
- [5] doi:10.1111/ejn.70582 [code]
- Multifiber Array-Based Photometry System for Multiregional Functional Mapping in the Mouse Brain.Journal: The European journal of neuroscienceIn common: tifffile, OpenCV, scikit-image, 5 other tools, optical imaging (calcium, voltage, 2-photon), 3 references
- [6] doi:10.1016/j.isci.2026.116206 [code]
- Gut distension evokes rapid neural dynamics in vagal and hindbrain populations of larval zebrafish.Journal: iScienceIn common: tifffile, OpenCV, scikit-image, 7 other tools, optical imaging (calcium, voltage, 2-photon), 1 reference
- [7] doi: [code]
- Real-time closed-loop feedback system for mouse mesoscale cortical signal and movement controlJournal: eLifeIn common: tifffile, OpenCV, scikit-image, 7 other tools, optical imaging (calcium, voltage, 2-photon), 1 reference
- [8] doi:10.1038/s41467-026-74823-1 [code]
- Cerebellar activity is triggered by reach endpoint during learning of a complex locomotor task.Journal: Nature communicationsIn common: tifffile, UMAP, OpenCV, 7 other tools, 1 reference
- [9] doi:10.7554/elife.109717 [code]
- Retrosplenial cortex enables context-dependent goal-directed sensorimotor transformation.Journal: eLifeIn common: tifffile, OpenCV, scikit-image, 7 other tools, 1 reference
- [10] doi:10.1038/s41467-026-75352-7 [code]
- Mechanosensory encoding of surface mechanics optimizes locomotion.Journal: Nature communicationsIn common: tifffile, OpenCV, scikit-image, 4 other tools, C. elegans, 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.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 3 repositories of the authors' code, each at its verified commit and with its license, 44 scripts, and 6 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:6bc5641e6ca2ee99…
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.
