OSCR

Theta oscillations are an organizational unit of odor processing in the olfactory bulb.

Code ↔ Paper

16 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 16 matches
  1. [1] § MATERIALS AND METHODS › Cross-frequency coupling of theta and gamma ↔ code_fig6.m, lines 426–474 · score 0.95 · Hilbert transform, gamma bursts, 30–60 Hz, gamma peaks, filtered signal, CircStat
  2. [2] § MATERIALS AND METHODS › Cross-frequency coupling of theta and gamma ↔ dataScript.m, lines 498–533 · score 0.94 · low gamma filtered, 30–60 Hz, amplitude envelope, gamma envelope, low gamma frequency, filtered signal
  3. [3] § MATERIALS AND METHODS › Intertrial phase coherence ↔ code_suppFigs.m, lines 543–680 · score 0.83 · respiratory phase bins, circularly shifted, passive inhalations, respiratory epochs, intentional sniffs, Fisher
  4. [4] § RESULTS › Theta oscillations organize the timing of gamma bursts in the olfactory bulb ↔ code_fig6.m, lines 426–474 · score 0.80 · gamma burst, 30–60 Hz, gamma peaks, theta phase, low gamma, sniff onset
  5. [5] § RESULTS › Theta oscillations organize the timing of gamma bursts in the olfactory bulb ↔ code_fig6.m, lines 301–424 · score 0.77 · aligned spectrogram, theta troughs, Theta peak, theta filtered, Amplitude spectrograms, theta frequency
  6. [6] § MATERIALS AND METHODS › Intertrial phase coherence ↔ code_fig4.m, lines 585–639 · score 0.77 · respiratory phase bins, circularly shifted, phase epochs, ITPC, matched, permuted
  7. [7] § MATERIALS AND METHODS › Statistical analysis ↔ code_fig4.m, lines 201–304 · score 0.71 · sign flipped, cluster mass, clusters formed, intentional sniffs, passive, threshold
  8. [8] § RESULTS › High-precision recordings from the human olfactory bulb ↔ code_fig3.m, lines 95–186 · score 0.67 · low gamma band, Low gamma odor, odor detection, High gamma, 150 Hz, 60 Hz
  9. [9] § MATERIALS AND METHODS › Amplitude spectrograms ↔ code_suppFigs.m, lines 403–541 · score 0.66 · temporally smoothed, longest, shortest, S4C, bandwidth, SD
  10. [10] § RESULTS › Intentional sniffs enhance and align theta oscillations in the olfactory bulb ↔ code_fig4.m, lines 201–304 · score 0.57 · cluster corrected permutation, passive inhales, intentional sniffs, theta amplitude, airflow, bar
  11. [11] § RESULTS › Sniffing behavior covaries with olfactory bulb theta oscillations ↔ code_suppFigs.m, lines 403–541 · score 0.55 · sniff duration, passive inhales, intentional sniffs, S4C, subset, overlapping
  12. [12] § RESULTS › Intentional sniffs enhance and align theta oscillations in the olfactory bulb ↔ code_fig4.m, lines 776–805 · score 0.55 · maximum theta ITPC, passive inhales, intentional sniff, resampling, phase, Figure 4
  13. [13] § RESULTS › High-precision recordings from the human olfactory bulb ↔ code_fig2_OdorImageCue.m, lines 329–393 · score 0.55 · image onset, odor cues, FDR corrected, baseline, Figure 2, threshold
  14. [14] § RESULTS › High-precision recordings from the human olfactory bulb ↔ code_fig3.m, lines 274–372 · score 0.53 · Power spectra, Gamma power, cue task, low gamma, contact, Figure 3
  15. [15] § RESULTS › High-precision recordings from the human olfactory bulb ↔ code_fig2_OdorImageCue.m, lines 329–393 · score 0.53 · image cues, Odor cues, FDR corrected, baseline, onset, Figure 2
  16. [16] § RESULTS › Intentional sniffs enhance and align theta oscillations in the olfactory bulb ↔ code_suppFigs.m, lines 682–719 · score 0.51 · Resampled distributions, passive inhales, intentional sniffs, CI, bootstrap, subset

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

MATLAB · 1,002 lines · 45 KB · CC-BY-4.0 · 4 matches

  1. %% Load data
  2. load data_supp
  3. %% Fig S2A - Odor vs. Image spectrograms
  4. % CUE TASK ODOR
  5. % Calculate spectrogram - concatenated 6 subs
  6. tic
  7. ts = data_supp.lfpData_cueSniffOdor_combinedSubs;
  8. sniffonset_conds = { data_supp.sniffonsets_cueOdorSniff_combinedSubs };
  9. para = [];
  10. para.freq = logspace( log10( 1), log10( 200), 100); % center frequencies for filtering
  11. para.bandwidth = logspace( log10( 2), log10( 30), 100); % bandwidth of each center frequency
  12. para.srate = srate; % sampling rate, in Hz
  13. para.events = sniffonset_conds; % in samples
  14. para.epoch = [-1, 5]; % in seconds, relative to events
  15. para.baseline = [-0.6, -0.1]; % in seconds, relative to events
  16. para.smooth_win = 0.01; % temporal smoothing, in secondsj
  17. para.nbpermuts = 10000; % number of permutations,
  18. para.measure = 'amplitude'; % or 'power'
  19. para.baseline_condition = []; % which condition to use as baseline (empty for just before each of multiple conditions)
  20. para.trl = 'mean'; % 'mean' o r'median' value across trials
  21. [stats_cueSniffOdor, epoch_time_cueSniffOdor, ev_epochs_cueSniffOdor, ~, sd_cueSniffOdor] = PermutAmpChange_Commonbaseline(ts, para);
  22. % IMAGE ONSETS
  23. ts = data_supp.lfp_visualCue_combinedSubs;
  24. sniffonset_conds = { data_supp.imageonsets_combinedSubs };
  25. para = [];
  26. para.freq = logspace( log10( 1), log10( 200), 100); % center frequencies for filtering
  27. para.bandwidth = logspace( log10( 2), log10( 30), 100); % bandwidth of each center frequency
  28. para.srate = srate; % sampling rate, in Hz
  29. para.events = sniffonset_conds; % in samples
  30. para.epoch = [-1, 5]; % in seconds, relative to events
  31. para.baseline = [-0.6, -0.1]; % in seconds, relative to events
  32. para.smooth_win = 0.01; % temporal smoothing, in secondsj
  33. para.nbpermuts = 10000; % number of permutations,
  34. para.measure = 'amplitude'; % or 'power'
  35. para.baseline_condition = []; % which condition to use as baseline (empty for just before each of multiple conditions)
  36. para.trl = 'mean'; % 'mean' o r'median' value across trials
  37. [stats_imageOnsets, epoch_time_imageOnsets, ev_epochs_imageOnsets, ~, sd_imageOnsets] = PermutAmpChange_Commonbaseline(ts, para);
  38. % CALCULATE DIFFERENCE BETWEEN CUE TASK SNIFF ODOR AND CUE TASK VISUAL IMAGE
  39. mat = cat(3,ev_epochs_cueSniffOdor{1},ev_epochs_imageOnsets{1});
  40. mat = permute(mat, [3 1 2]);
  41. g = [ones(length(data_supp.sniffonsets_cueOdorSniff_combinedSubs),1) ; 2*ones(length(data_supp.imageonsets_combinedSubs),1)];
  42. AmpChange_CueTasks_OdorVsImage = G_PowChange_Clust(mat, g, epoch_time_imageOnsets, para.freq, 'baseline', para.baseline,'db_transf', 'no','common_bs','no');
  43. % PLOT DIFFERENCE BETWEEN CUE TASK SNIFF ODOR AND CUE TASK VISUAL IMAGE
  44. % Respiratory overlays
  45. resp_epochs_cueSniffOdor_combinedSubs = squeeze(zscore(G_EpochTS(data_supp.resp_cueSniffOdor_combinedSubs, data_supp.sniffonsets_cueOdorSniff_combinedSubs, [-1,5]*srate)));
  46. resp_meanEpochs_cueSniffOdor_combinedSubs = squeeze(mean(resp_epochs_cueSniffOdor_combinedSubs,2));
  47. resp_epochs_imageOnsets_combinedSubs = squeeze(zscore(G_EpochTS(data_supp.resp_visualCue_combinedSubs, data_supp.imageonsets_combinedSubs, [-1,5]*srate)));
  48. resp_meanEpochs_imageOnsets_combinedSubs = squeeze(mean(resp_epochs_imageOnsets_combinedSubs,2));
  49. % Calculate significance threshld using FDR correction
  50. %Odor Cue Task - Sniff Odor
  51. P_cueSniffOdor.P = G_z2p(stats_cueSniffOdor.Z{1});
  52. FDR_cueSniffOdor_Odor.FDR = fdr(P_cueSniffOdor.P(:));
  53. thresh_cueSniffOdor_Odor.thresh = G_p2z(FDR_cueSniffOdor_Odor.FDR);
  54. %Visual Cue Task - Image Onset
  55. P_visualCue_image.P = G_z2p(stats_imageOnsets.Z{1});
  56. FDR_visualCue_image.FDR = fdr(P_visualCue_image.P(:));
  57. thresh_visualCue_image.thresh = G_p2z(FDR_visualCue_image.FDR);
  58. %amplitude difference
  59. AmpChange_P= G_z2p(AmpChange_CueTasks_OdorVsImage.statsmap);
  60. AmpChange_FDR = fdr(AmpChange_P(:));
  61. AmpChange_thresh = G_p2z( AmpChange_FDR);
  62. % Plot spectrogram
  63. Time = -1 : 1/srate : 5;
  64. subplot(3,1,1)
  65. % subplot(1,3,1)
  66. G_Imagesc(stats_cueSniffOdor.Z{1},'yaxis',para.freq,'overlay',resp_meanEpochs_cueSniffOdor_combinedSubs,...
  67. 'xaxis', Time, 'xtick', [-0.3,0:0.5:5], 'threshold',thresh_cueSniffOdor_Odor.thresh,...
  68. 'ytick', [1,3,8,15,30,50,100,200],'Fontsize',14,'clim',[-10,10],'ctick', [-10,0,10],...
  69. 'ytick_precision',0,'xlabel','Time (s)','ylabel','Frequency (Hz)');
  70. xlim([0.69*srate,3.51*srate]);
  71. title('Odor');
  72. subplot(3,1,2)
  73. % subplot(1,3,2)
  74. G_Imagesc(stats_imageOnsets.Z{1},'yaxis',para.freq,'overlay',resp_meanEpochs_imageOnsets_combinedSubs,...
  75. 'xaxis', Time, 'xtick', [-0.3,0:0.5:5], 'threshold',thresh_visualCue_image.thresh,...
  76. 'ytick', [1,3,8,15,30,50,100,200],'Fontsize',14,'clim',[-10,10],'ctick', [-10,0,10],...
  77. 'ytick_precision',0,'xlabel','Time (s)','ylabel','Frequency (Hz)');
  78. xlim([0.69*srate,3.51*srate]);
  79. title('Image');
  80. subplot(3,1,3)
  81. % subplot(1,3,3)
  82. h=G_Imagesc(AmpChange_CueTasks_OdorVsImage.statsmap,'yaxis',para.freq,...
  83. 'xaxis', Time, 'xtick', [-0.3,0:0.5:5], 'threshold',AmpChange_thresh,...
  84. 'ytick', [1,3,8,15,30,50,100,200],'Fontsize',14,...
  85. 'clim',[-5,5],'ctick', [-5,0,5],...
  86. 'ytick_precision',0,'xlabel','Time (s)','ylabel','Frequency (Hz)');
  87. xlim([0.69*srate,3.51*srate]);
  88. title('Odor minus Image');
  89. % for k = 1:length(AmpChange_CueTasks_OdorVsImage.clust{1}.both.info.PixelIdxList)
  90. % mask = zeros(AmpChange_CueTasks_OdorVsImage.clust{1}.both.info.ImageSize);
  91. % if AmpChange_CueTasks_OdorVsImage.clust{1}.both.statssum_pval(k) < 0.05
  92. % mask(AmpChange_CueTasks_OdorVsImage.clust{1}.both.info.PixelIdxList{k}) = 1;
  93. % G_Outline_Pixels(h,mask,'linecolor','m','linewidth',1);
  94. % end
  95. % end
  96. % set(gcf,'pos',[367 571 1722 411]); % For 3 columns x 1 row
  97. set(gcf,'pos',[1000 281 507 957]); % For 1 column x 3 rows
  98. xlim([0.69*srate,3.51*srate]);
  99. ax = gca; ax.LineWidth = 1; ax.FontSize = 16;
  100. %% Fig S2A - Image onsets no sniff
  101. ts = data_supp.lfp_visualCue_combinedSubs;
  102. sniffonset_conds = { data_supp.imageonsets_noSniff_combinedSubs };
  103. para = [];
  104. para.freq = logspace( log10( 1), log10( 200), 100); % center frequencies for filtering
  105. para.bandwidth = logspace( log10( 2), log10( 30), 100); % bandwidth of each center frequency
  106. para.srate = srate; % sampling rate, in Hz
  107. para.events = sniffonset_conds; % in samples
  108. para.epoch = [-1, 5]; % in seconds, relative to events
  109. para.baseline = [-0.6, -0.1]; % in seconds, relative to events
  110. para.smooth_win = 0.01; % temporal smoothing, in secondsj
  111. para.nbpermuts = 10000; % number of permutations,
  112. para.measure = 'amplitude'; % or 'power'
  113. para.baseline_condition = []; % which condition to use as baseline (empty for just before each of multiple conditions)
  114. para.trl = 'mean'; % 'mean' o r'median' value across trials
  115. [stats_imageOnsetsNoSniff, epoch_time_imageOnsetsNoSniff, ev_epochs_imageOnsetsNoSniff, ~, sd_imageOnsetsNoSniff] = PermutAmpChange_Commonbaseline(ts, para);
  116. % Respiratory overlay
  117. resp_epochs_imageOnsetsNoSniff_combinedSubs = squeeze(zscore(G_EpochTS(data_supp.resp_visualCue_combinedSubs, data_supp.imageonsets_noSniff_combinedSubs, [-1,5]*srate)));
  118. resp_meanEpochs_imageOnsetsNoSniff_combinedSubs = squeeze(mean(resp_epochs_imageOnsetsNoSniff_combinedSubs,2));
  119. % Significance threshold
  120. P_imageOnsetsNoSniff.P = G_z2p(stats_imageOnsetsNoSniff.Z{1});
  121. FDR_imageOnsetsNoSniff.FDR = fdr(P_imageOnsetsNoSniff.P(:));
  122. thresh_imageOnsetsNoSniff.thresh = G_p2z(FDR_imageOnsetsNoSniff.FDR);
  123. % Plot spectrogram
  124. G_Imagesc(stats_imageOnsetsNoSniff.Z{1},'yaxis',para.freq,'overlay',resp_meanEpochs_imageOnsetsNoSniff_combinedSubs,...
  125. 'xaxis', Time, 'xtick', [-0.3,0:0.5:5], 'threshold',thresh_imageOnsetsNoSniff.thresh,...
  126. 'ytick', [1,3,8,15,30,50,100,200],'Fontsize',14,'clim',[-10,10],'ctick', [-10,0,10],...
  127. 'ytick_precision',0,'xlabel','Time (s)','ylabel','Frequency (Hz)');
  128. %% Fig S3 - Difference spectrogram for odor vs. air on detection task
  129. % Calculate Subject-wiser Spectrograms at Gamma Frequencies
  130. for sub = 1:num_subs
  131. ts = data_supp.lfp_detect{sub};
  132. sniffonset_conds = { data_supp.sniffonsets_odor{sub}, data_supp.sniffonsets_air{sub} };
  133. para = [];
  134. % para.freq = logspace( log10( 1), log10( 200), 100); % center frequencies for filtering
  135. % para.bandwidth = logspace( log10( 2), log10( 30), 100); % bandwidth of each center frequency
  136. para.freq = linspace( 35, 175, 40); % center frequencies for filtering
  137. para.bandwidth = linspace( 10, 35, length(para.freq)); % bandwidth of each center frequency
  138. para.srate = srate; % sampling rate, in Hz
  139. para.events = sniffonset_conds; % in samples
  140. para.epoch = [-1, 5]; % in seconds, relative to events
  141. para.baseline = [-0.6, -0.1]; % in seconds, relative to events
  142. para.smooth_win = 0.01; % temporal smoothing, in secondsj
  143. para.nbpermuts = 10000; % number of permutations,
  144. para.measure = 'amplitude'; % or 'power'
  145. para.baseline_condition = 1; % which condition to use as baseline (empty for just before each of multiple conditions)
  146. para.trl = 'mean'; % 'mean' o r'median' value across trials
  147. [stats{sub}, epoch_time{sub}, ev_epochs{sub}, ~, sd{sub}] = PermutAmpChange_Commonbaseline(ts, para);
  148. end
  149. z_odor = []; z_air = [];
  150. for sub = 1:num_subs
  151. z_odor(sub,:,:) = stats{sub}.Z{1};
  152. z_air(sub,:,:) = stats{sub}.Z{2};
  153. end
  154. % Average spectrogram output across subjects
  155. z_mean_odor = squeeze(mean(z_odor,1));
  156. z_mean_air = squeeze(mean(z_air,1));
  157. % Calculate amplitude difference between Passive inhale vs. Cued sniff combined subjects
  158. % Combine ev_epochs across subjects
  159. ev_epochs_6normosmics_odor = cat( 3,ev_epochs{1}{1},ev_epochs{2}{1},ev_epochs{3}{1},ev_epochs{4}{1},ev_epochs{5}{1},ev_epochs{6}{1} );
  160. ev_epochs_6normosmics_air = cat( 3,ev_epochs{1}{2},ev_epochs{2}{2},ev_epochs{3}{2},ev_epochs{4}{2},ev_epochs{5}{2},ev_epochs{6}{2} );
  161. mat = cat(3,ev_epochs_6normosmics_odor,ev_epochs_6normosmics_air);
  162. mat = permute(mat, [3 1 2]);
  163. g = [ones(size(ev_epochs_6normosmics_odor,3),1) ; 2*ones(size(ev_epochs_6normosmics_air,3),1)];
  164. AmpChange_odorVair_comBase = G_PowChange_Clust(mat, g, epoch_time{sub}, ...
  165. para.freq, 'baseline', para.baseline,'db_transf', 'no','common_bs','yes');
  166. % Significance threshold
  167. AmpChange_P = []; AmpChange_FDR=[]; AmpChange_thresh=[];
  168. AmpChange_P = G_z2p(AmpChange_odorVair_comBase.statsmap);
  169. AmpChange_FDR = fdr(AmpChange_P(:));
  170. AmpChange_thresh = G_p2z( AmpChange_FDR)
  171. % Plot difference spectrogram
  172. h=G_Imagesc(AmpChange_odorVair_comBase.statsmap,'yaxis',para.freq,...
  173. 'xaxis', Time, 'xtick', [-0.3,0:0.5:5],'threshold',AmpChange_thresh,...
  174. 'ytick', 35:10:175,'Fontsize',14,...
  175. 'clim',[-4,4],'ctick', [-4,-2,0,2,4],...
  176. 'ytick_precision',0);
  177. for k = 1:length(AmpChange_odorVair_comBase.clust{1}.both.info.PixelIdxList)
  178. mask = zeros(AmpChange_odorVair_comBase.clust{1}.both.info.ImageSize);
  179. if AmpChange_odorVair_comBase.clust{1}.both.statssum_pval(k) < 0.05
  180. mask(AmpChange_odorVair_comBase.clust{1}.both.info.PixelIdxList{k}) = 1;
  181. G_Outline_Pixels(h,mask,'linecolor','k','LineWidth',1.5);
  182. end
  183. end
  184. xlim([0.69*srate,3.51*srate]);
  185. title('Difference');
  186. set(gcf,'pos',[334 553 1407 299]);
  187. %% Fig S4 - controlling for sniff features
  188. num_subs=6;
  189. numTrials_passive = cellfun(@length,data_supp.exhale_onsets_passive_bySub);
  190. numTrials_cuedSniff = cellfun(@length,data_supp.exhale_onsets_cuedSniff_bySub);
  191. inhaleDur_passive=[];inhaleDur_cuedSniff=[]; inhalePk_CuedSniff_bySub=[];
  192. time2inhalePk_ind_CuedSniff_bySub=[]; inhalePk_Passive_bySub=[];
  193. time2inhalePk_ind_Passive_bySub=[]; time2inhalePk_ms_Passive=[]; time2inhalePk_ms_CuedSniff=[];
  194. for sub = 1:num_subs
  195. inhaleDur_passive{sub} = (data_supp.exhale_onsets_passive_bySub{sub} - data_supp.sniffonsets_Passive{sub}) / srate;
  196. inhaleDur_cuedSniff{sub} = (data_supp.exhale_onsets_cuedSniff_bySub{sub} - data_supp.sniffonsets_CuedSniff{sub}) / srate;
  197. for i = 1:numTrials_cuedSniff(sub)
  198. [inhalePk_CuedSniff_bySub{sub}(i,1), time2inhalePk_ind_CuedSniff_bySub{sub}(i,1)] = max(resp_epochs_CuedSniff_bySub{sub}(500:1000,i));
  199. end
  200. for i = 1:numTrials_passive(sub)
  201. [inhalePk_Passive_bySub{sub}(i,1), time2inhalePk_ind_Passive_bySub{sub}(i,1)] = max(resp_epochs_Passive_bySub{sub}(500:1000,i));
  202. end
  203. time2inhalePk_ms_Passive{sub} = time2inhalePk_ind_Passive_bySub{sub} / srate * 1000;
  204. time2inhalePk_ms_CuedSniff{sub} = time2inhalePk_ind_CuedSniff_bySub{sub} / srate * 1000;
  205. end
  206. %% Fig S4B - histograms of sniff features
  207. % Check how variables look with histograms for each subject
  208. % Passive
  209. tiledlayout(3,2)
  210. for sub = 1:num_subs
  211. nexttile
  212. histogram(inhaleDur_passive{sub})
  213. % histogram(inhalePk_Passive_bySub{sub})
  214. % histogram(time2inhalePk_ms_Passive{sub})
  215. sgtitle('passive');
  216. end
  217. figure
  218. for sub = 1:num_subs
  219. nexttile
  220. histogram(inhaleDur_cuedSniff{sub})
  221. % histogram(inhalePk_CuedSniff_bySub{sub})
  222. % histogram(time2inhalePk_ms_CuedSniff{sub})
  223. sgtitle('intentional');
  224. end
  225. sub_titles = {'P1','P2','P3','P4','P5','P6'};
  226. tiledlayout(3,2)
  227. for sub = 1:num_subs
  228. nexttile
  229. histogram(inhaleDur_passive{sub},'FaceColor',[1 0.6 0])
  230. hold on
  231. histogram(inhaleDur_cuedSniff{sub},'FaceColor',[0 0.6 1])
  232. if sub == 1
  233. ylabel('Trial count');
  234. legend({'Passive','Intentional'},'Location','northwest');
  235. end
  236. if sub == 5
  237. xlabel('Inhale duration (s)');
  238. end
  239. ax=gca; ax.FontSize = 14; ax.TickDir = 'out'; ax.LineWidth = 1; box off
  240. title(sub_titles{sub});
  241. end
  242. set(gcf,'pos',[300 300 859 702]);
  243. %% Fig S4A - summary statistics of sniff kinematics
  244. % Data structure for mixed effects model
  245. nSub = 6;
  246. DV1_all = [];
  247. DV2_all = [];
  248. DV3_all = [];
  249. Condition = [];
  250. Subject = [];
  251. for s = 1:nSub
  252. % ===== Condition 1 =====
  253. dv1 = inhaleDur_passive{s}(:);
  254. dv2 = inhalePk_Passive_bySub{s}(:);
  255. dv3 = time2inhalePk_ms_Passive{s}(:);
  256. % Sanity check (important!)
  257. assert(numel(dv1)==numel(dv2) && numel(dv1)==numel(dv3), ...
  258. 'Mismatch in trial counts for subject %d, condition C1', s);
  259. n = numel(dv1);
  260. DV1_all = [DV1_all; dv1];
  261. DV2_all = [DV2_all; dv2];
  262. DV3_all = [DV3_all; dv3];
  263. Condition = [Condition; repmat("C1", n, 1)];
  264. Subject = [Subject; repmat(s, n, 1)];
  265. % ===== Condition 2 =====
  266. dv1 = inhaleDur_cuedSniff{s}(:);
  267. dv2 = inhalePk_CuedSniff_bySub{s}(:);
  268. dv3 = time2inhalePk_ms_CuedSniff{s}(:);
  269. assert(numel(dv1)==numel(dv2) && numel(dv1)==numel(dv3), ...
  270. 'Mismatch in trial counts for subject %d, condition C2', s);
  271. n = numel(dv1);
  272. DV1_all = [DV1_all; dv1];
  273. DV2_all = [DV2_all; dv2];
  274. DV3_all = [DV3_all; dv3];
  275. Condition = [Condition; repmat("C2", n, 1)];
  276. Subject = [Subject; repmat(s, n, 1)];
  277. end
  278. % Build the table
  279. tbl = table;
  280. tbl.Subject = categorical(Subject);
  281. tbl.Condition = categorical(Condition);
  282. tbl.duration = DV1_all;
  283. tbl.peak = DV2_all;
  284. tbl.timeToPeak = DV3_all;
  285. tbl.Condition = reordercats(tbl.Condition,{'C1','C2'});
  286. % Sanity checks
  287. height(tbl)
  288. varfun(@numel,tbl,'InputVariables','duration','GroupingVariables',{'Subject','Condition'})
  289. varfun(@numel, tbl,'InputVariables','peak','GroupingVariables',{'Subject','Condition'})
  290. varfun(@numel, tbl,'InputVariables','timeToPeak','GroupingVariables',{'Subject','Condition'})
  291. % % Baseline mixed-effects model - Is there a fixed effect of Condition
  292. % accounting for subject-to-subject differences?
  293. % For each dependent variable, start with a random intercept model
  294. lme1 = fitlme(tbl, 'duration ~ Condition + (1|Subject)')
  295. % If each subject may respond differently to condition manipution,
  296. % include random slopes
  297. lme1_2 = fitlme(tbl, 'duration ~ Condition + (Condition|Subject)')
  298. % Compare models
  299. compare(lme1,lme1_2)
  300. % The small pvalue and large likelihood ratio stat show that including
  301. % random slopes is better, but stick with
  302. anova(lme1_2)
  303. anova(lme1)
  304. % Effect sizes
  305. beta = lme1.Coefficients.Estimate(2)
  306. sd_resid = sqrt(lme1.MSE)
  307. d = beta / sd_resid
  308. beta = lme1_2.Coefficients.Estimate(2)
  309. sd_resid = sqrt(lme1_2.MSE)
  310. d = beta / sd_resid
  311. % % Handling 3 dependent variables
  312. % Separate models + correction
  313. % Fit one model per DV:
  314. lme1 = fitlme(tbl, 'duration ~ Condition + (1|Subject)');
  315. lme2 = fitlme(tbl, 'peak ~ Condition + (1|Subject)');
  316. lme3 = fitlme(tbl, 'timeToPeak ~ Condition + (1|Subject)');
  317. % Correct for multiple testing
  318. p = [lme1.Coefficients.pValue(2), lme2.Coefficients.pValue(2), lme3.Coefficients.pValue(2)];
  319. p_fdr = mafdr(p,'BHFDR',true);
  320. % Visualization
  321. tiledlayout(1,3)
  322. nexttile
  323. boxchart(tbl,"Condition","duration",'MarkerStyle','none','BoxFaceColor','k')
  324. ax=gca; ax.TickDir='out'; ax.LineWidth=1; ax.FontSize=14; box off
  325. ylabel('Duration (s)');
  326. ax.XTickLabel = {'Passive','Intentional'};
  327. xlabel('');
  328. hold on
  329. for s = categories(tbl.Subject)'
  330. idx = tbl.Subject == s{1};
  331. means = groupsummary(tbl(idx,:), 'Condition', 'mean', 'duration');
  332. plot([1 2], means.mean_duration, '-o', 'Color',[.7 .7 .7]);
  333. end
  334. nexttile
  335. boxchart(tbl,"Condition","peak",'MarkerStyle','none','BoxFaceColor','k')
  336. ax=gca; ax.TickDir='out'; ax.LineWidth=1; ax.FontSize=14; box off
  337. ylabel('Peak airflow (au)');
  338. ax.XTickLabel = {'Passive','Intentional'};
  339. xlabel('');
  340. hold on
  341. for s = categories(tbl.Subject)'
  342. idx = tbl.Subject == s{1};
  343. means = groupsummary(tbl(idx,:), 'Condition', 'mean', 'peak');
  344. plot([1 2], means.mean_peak, '-o', 'Color',[.7 .7 .7]);
  345. end
  346. nexttile
  347. boxchart(tbl,"Condition","timeToPeak",'MarkerStyle','none','BoxFaceColor','k')
  348. ax=gca; ax.TickDir='out'; ax.LineWidth=1; ax.FontSize=14; box off
  349. ylabel('Time to peak (ms)');
  350. ax.XTickLabel = {'Passive','Intentional'};
  351. xlabel('');
  352. hold on
  353. for s = categories(tbl.Subject)'
  354. idx = tbl.Subject == s{1};
  355. means = groupsummary(tbl(idx,:), 'Condition', 'mean', 'timeToPeak');
  356. plot([1 2], means.mean_timeToPeak, '-o', 'Color',[.7 .7 .7]);
  357. end
  358. set(gcf,'pos',[300 300 1658 508]);
  359. %% Fig S4C - spectrogram of subset of passive inhales matched for duration of intentional sniffs
  360. % 1b. Bin intentional sniffs and passive inhales by overlapping duration within each subject
  361. % Apart from Subject 2, all subjects show increase in duration for
  362. % intentional vs. passive sniff.
  363. % So try sorting sniff duration for each condition, and take shortest
  364. % duration bin from each subject, except for subject 2 take longest
  365. % duration bin
  366. % Try for 20 subsampled trials of passive inhales for each subject
  367. % Ascending order by default
  368. [inhaleDur_passive_bySub_sorted, inhaleDur_passive_bySub_sortInd] = cellfun(@sort,inhaleDur_passive,'UniformOutput',false);
  369. % Bin sniff onsets by the 20 trials of shortest duration for 5 of 6 subs
  370. sub_inds = [1,3,4,5,6];
  371. for i = 1:length(sub_inds)
  372. sniffonsets_subset_passive(:,sub_inds(i)) = sniffonsets_Passive{sub_inds(i)}(inhaleDur_passive_bySub_sortInd{sub_inds(i)}(1:20));
  373. end
  374. sniffonsets_subset_passive(:,2) = sniffonsets_Passive{2}(inhaleDur_passive_bySub_sortInd{2}(end-19:end));
  375. % Caclulate spectrogram by subjects for the subset of passive inhales
  376. for sub = 1:num_subs
  377. ts = lfp_Passive{sub};
  378. sniffonset_conds = { sniffonsets_subset_passive(:,sub) };
  379. para = [];
  380. para.freq = logspace( log10( 1), log10( 200), 100); % center frequencies for filtering
  381. para.bandwidth = logspace( log10( 2), log10( 30), 100); % bandwidth of each center frequency
  382. para.srate = srate; % sampling rate, in Hz
  383. para.events = sniffonset_conds; % in samples
  384. para.epoch = [-1, 5]; % in seconds, relative to events
  385. para.baseline = [-0.6, -0.1]; % in seconds, relative to events
  386. para.smooth_win = 0.01; % temporal smoothing, in secondsj
  387. para.nbpermuts = 10000; % number of permutations,
  388. para.measure = 'amplitude'; % or 'power'
  389. para.baseline_condition = []; % which condition to use as baseline (empty for just before each of multiple conditions)
  390. para.trl = 'mean'; % 'mean' o r'median' value across trials
  391. [stats_passive{sub}, epoch_time_passive{sub}, ev_epochs_passive{sub}, ~, sd_passive{sub}] = PermutAmpChange_Commonbaseline(ts, para);
  392. end
  393. save('spectro_passive_subsetMatchDur_bySub','para','stats_passive','ev_epochs_passive','epoch_time_passive','sd_passive','-v7.3');
  394. % Combined subs spectrogram for subset of Passive Inhales
  395. % % Subset is for 20 trials that are shortest duration inhale which overlap
  396. % with duration of intentional sniffs, except for sub2 they are the fastest
  397. % Load in previously calculated cued sniff spectrograms to compare
  398. % Should have variable stats_6normosmic_cuedSniff
  399. for sub = 1:6 % 6 normosmics
  400. z_cuedSniff(sub,:,:) = data_supp.stats_6normosmic_cuedSniff{sub}.Z{1};
  401. z_passiveSubset(sub,:,:) = stats_passive{sub}.Z{1};
  402. end
  403. % Average spectrogram output across subjects
  404. z_mean_cuedSniff = squeeze(mean(z_cuedSniff,1));
  405. z_mean_passiveSubset = squeeze(mean(z_passiveSubset,1));
  406. P.P_passive = G_z2p(z_mean_passiveSubset);
  407. FDR.FDR_passive = fdr(P.P_passive(:));
  408. thresh.thresh_passive = G_p2z(FDR.FDR_passive);
  409. P = []; FDR = []; thresh=[];
  410. P.P_cuedSniff = G_z2p(z_mean_cuedSniff);
  411. FDR.FDR_cuedSniff = fdr(P.P_cuedSniff(:));
  412. thresh.thresh_cuedSniff = G_p2z(FDR.FDR_cuedSniff);
  413. for sub = 1:num_subs
  414. resp_epochs_passiveSubset_bySub{sub} = squeeze(G_EpochTS(resp_Passive{sub},sniffonsets_subset_passive(:,sub),[-1,5]*srate));
  415. resp_epochs_cuedSniff_bySub{sub} = squeeze(G_EpochTS(resp_CuedSniff{sub},sniffonsets_CuedSniff{sub},[-1,5]*srate));
  416. end
  417. resp_epochs_passiveSubset_acrossSubs = cell2mat(resp_epochs_passiveSubset_bySub);
  418. resp_mean_epoch_passiveSubset = mean(resp_epochs_passiveSubset_acrossSubs,2);
  419. resp_epochs_cuedSniff_acrossSubs = cell2mat(resp_epochs_cuedSniff_bySub);
  420. resp_mean_epoch_cuedSniff = mean(resp_epochs_cuedSniff_acrossSubs,2);
  421. % Calculate amplitude difference between Passive inhale vs. Cued sniff combined subjects
  422. % Combine ev_epochs across subjects
  423. ev_epochs_6normosmic_cuedSniff = data_supp.ev_epochs_6normosmic_cuedSniff;
  424. ev_epochs_6normosmics_cuedSniff = cat( 3,ev_epochs_6normosmic_cuedSniff{1}{1},ev_epochs_6normosmic_cuedSniff{2}{1},ev_epochs_6normosmic_cuedSniff{3}{1},...
  425. ev_epochs_6normosmic_cuedSniff{4}{1},ev_epochs_6normosmic_cuedSniff{5}{1},ev_epochs_6normosmic_cuedSniff{6}{1} );
  426. ev_epochs_6normosmics_passiveSubset = cat( 3,ev_epochs_passive{1}{1},ev_epochs_passive{2}{1},ev_epochs_passive{3}{1},...
  427. ev_epochs_passive{4}{1},ev_epochs_passive{5}{1},ev_epochs_passive{6}{1} );
  428. tic
  429. mat = cat(3,ev_epochs_6normosmics_cuedSniff,ev_epochs_6normosmics_passiveSubset);
  430. mat = permute(mat, [3 1 2]);
  431. g = [ones(size(ev_epochs_6normosmics_cuedSniff,3),1) ; 2*ones(size(ev_epochs_6normosmics_passiveSubset,3),1)];
  432. AmpChange_cuedVsPassiveSubsetMatchedDur = G_PowChange_Clust(mat, g, epoch_time_passive{1}, para.freq, 'baseline', para.baseline,'db_transf', 'no','common_bs','yes');
  433. save('AmpChange_cuedVsPassiveSubsetMatchedDur','AmpChange_cuedVsPassiveSubsetMatchedDur','para','-v7.3');
  434. toc
  435. AmpChange_P = []; AmpChange_FDR=[]; AmpChange_thresh=[];
  436. AmpChange_P = G_z2p(AmpChange_cuedVsPassiveSubsetMatchedDur.statsmap);
  437. AmpChange_FDR = fdr(AmpChange_P(:));
  438. AmpChange_thresh = G_p2z( AmpChange_FDR)
  439. % Plot mean spectrogram for Cued Sniff vs. Passive Inhale averaged across subjects
  440. Time = -1 : 1/srate : 5;
  441. tiledlayout(1,3)
  442. nexttile
  443. [h, c, clusts, clustinfo]=G_Imagesc(z_mean_cuedSniff,'yaxis',freq,...
  444. 'xaxis', Time, 'xtick', [-0.3,0:0.5:5],'threshold',thresh.thresh_cuedSniff,...
  445. 'ytick', [1,2,3,8,15,30,50,100,200],'Fontsize',16,'overlay',resp_mean_epoch_cuedSniff,...
  446. 'clim',[-3,3],'ctick', [-3,0,3],...
  447. 'ytick_precision',0,'xlabel','Time (s)','ylabel','Frequency (Hz)');
  448. ax=gca;
  449. ax.FontSize = 16; ax.LineWidth = 1; box off
  450. xlim([0.69*srate,3.51*srate]);
  451. title('Intentional Sniff');
  452. colorbar off
  453. nexttile
  454. G_Imagesc(z_mean_passiveSubset,'yaxis',freq,...
  455. 'xaxis', Time, 'xtick', [-0.3,0:0.5:5],'threshold',thresh.thresh_passive,...
  456. 'ytick', [1,2,3,8,15,30,50,100,200],'Fontsize',16,'overlay',resp_mean_epoch_passiveSubset,'overlay_ref',resp_mean_epoch_cuedSniff,...
  457. 'clim',[-3,3],'ctick', [-3,0,3],'clabel','Amplitude (z score)',...
  458. 'ytick_precision',0);
  459. xlim([0.69*srate,3.51*srate]);
  460. title('Passive Inhale');
  461. ylabel('');
  462. xlabel('');
  463. ax=gca;
  464. ax.FontSize = 16; ax.LineWidth = 1; box off
  465. % set(gcf,'pos',[758 784 1047 478]); % without difference plot
  466. nexttile
  467. h=G_Imagesc(AmpChange_cuedVsPassiveSubsetMatchedDur.statsmap,'yaxis',para.freq,...
  468. 'xaxis', Time, 'xtick', [-0.3,0:0.5:5], ...
  469. 'ytick', [1,2,8,15,30,50,100,200],'Fontsize',16,...
  470. 'clim',[-3,3],'ctick', [-3,0,3],'clabel','Amplitude Difference (z score)',...
  471. 'ytick_precision',0);
  472. xlabel(''); ylabel('');
  473. title('Difference');
  474. for k = 1:length(AmpChange_cuedVsPassiveSubsetMatchedDur.clust{1}.both.info.PixelIdxList)
  475. mask = zeros(AmpChange_cuedVsPassiveSubsetMatchedDur.clust{1}.both.info.ImageSize);
  476. if AmpChange_cuedVsPassiveSubsetMatchedDur.clust{1}.both.statssum_pval(k) < 0.05
  477. mask(AmpChange_cuedVsPassiveSubsetMatchedDur.clust{1}.both.info.PixelIdxList{k}) = 1;
  478. G_Outline_Pixels(h,mask,'linecolor','k','LineWidth',1.5);
  479. end
  480. end
  481. xlim([0.69*srate,3.51*srate]);
  482. set(gcf,'pos',[758 784 1499 478]); % With difference plot
  483. ax.FontSize = 16; ax.LineWidth = 1; box off
  484. %% Fig S4D to E
  485. ph_epochs_byRespPhaseBin = data_supp.ph_epochs_byRespPhaseBin;
  486. ph_epochs_byRespPhase_combinedSubs{1} = cat(3,ph_epochs_byRespPhaseBin{1}{1}(:,:,inhaleDur_passive_bySub_sortInd{1}),ph_epochs_byRespPhaseBin{1}{2}(:,:,inhaleDur_passive_bySub_sortInd{2}),...
  487. ph_epochs_byRespPhaseBin{1}{3}(:,:,inhaleDur_passive_bySub_sortInd{3}),ph_epochs_byRespPhaseBin{1}{4}(:,:,inhaleDur_passive_bySub_sortInd{4}),...
  488. ph_epochs_byRespPhaseBin{1}{5}(:,:,inhaleDur_passive_bySub_sortInd{5}),ph_epochs_byRespPhaseBin{1}{6}(:,:,inhaleDur_passive_bySub_sortInd{6}));
  489. ph_epochs_byRespPhase_combinedSubs{2} = cat(3,ph_epochs_byRespPhaseBin{2}{1},ph_epochs_byRespPhaseBin{2}{2},ph_epochs_byRespPhaseBin{2}{3},...
  490. ph_epochs_byRespPhaseBin{2}{4},ph_epochs_byRespPhaseBin{2}{5},ph_epochs_byRespPhaseBin{2}{6});
  491. ph_epochs_cuedSniff = data_supp.ph_epochs_byRespPhase_combinedSubs{2};
  492. ph_epochs_passive = cat(3,ph_epochs_byRespPhaseBin{1}{1},ph_epochs_byRespPhaseBin{1}{2},...
  493. ph_epochs_byRespPhaseBin{1}{3},ph_epochs_byRespPhaseBin{1}{4},...
  494. ph_epochs_byRespPhaseBin{1}{5},ph_epochs_byRespPhaseBin{1}{6});
  495. param.freq = logspace( log10( 1), log10( 100), 80); % logarithmic steps in frequency for filtering
  496. param.bandwidth = logspace( log10( 2), log10( 20), 80); % logarithmic steps in bandwidth for filtering
  497. % Calculate ITPC difference between combined subs respiratory-normalized conditions
  498. % % Calculate ITPC difference between Cued sniff and Passive
  499. ITPCdiff_normResp_combinedSubs = data_supp.ITPC_normResp_combinedSubs{2} - data_supp.ITPC_normResp_combinedSubs{1};
  500. ITPCdiff_fisherZ_normResp_combinedSubs = atanh(ITPCdiff_normResp_combinedSubs);
  501. % % Permutation of ITPC over circularly shifted data, 1000 iterations
  502. numpts = size(ph_epochs_byRespPhase_combinedSubs{2},2);
  503. num_perms = 1000;
  504. for perm = 1:num_perms
  505. fprintf( 'Permutation: %d/%d \r', perm, num_perms);
  506. ph_tmp_passive=ph_epochs_byRespPhase_combinedSubs{1};
  507. ph_tmp_cuedSniff=ph_epochs_byRespPhase_combinedSubs{2};
  508. for trial = 1:size(ph_epochs_byRespPhase_combinedSubs{1},3)
  509. shift_amount = circshift( 1:numpts, randsample( 1:numpts, 1));
  510. ph_tmp_passive( :, :, trial) = ph_epochs_byRespPhase_combinedSubs{1}( :, shift_amount, trial );
  511. end
  512. for trial = 1:size(data_supp.ph_epochs_byRespPhase_combinedSubs{2},3)
  513. shift_amount = circshift( 1:numpts, randsample( 1:numpts, 1));
  514. ph_tmp_cuedSniff( :, :, trial) = ph_epochs_byRespPhase_combinedSubs{2}( :, shift_amount, trial);
  515. end
  516. permuteITPC_passive_tmp = []; permuteITPC_cuedSniff_tmp = [];
  517. permuteITPC_passive_tmp(:,:,perm) = squeeze(abs(mean(exp(1i * ph_tmp_passive), 3)));
  518. permuteITPC_cuedSniff_tmp(:,:,perm) = squeeze(abs(mean(exp(1i * ph_tmp_cuedSniff), 3)));
  519. permuteITPCdiff_normResp_combinedSubs(:,:,perm) = permuteITPC_cuedSniff_tmp(:,:,perm) - permuteITPC_passive_tmp(:,:,perm);
  520. permuteITPCdiff_fisherZ_passiveVcuedsniff_normResp(:,:,perm) = atanh(permuteITPCdiff_normResp_combinedSubs(:,:,perm));
  521. end
  522. % % % Cluster-based correction
  523. [h_clust_normResp_combinedSubs_ITPCdiff, p_clust_normResp_combinedSubs_ITPCdiff, clustinfo_normResp_combinedSubs_ITPCdiff] = cluster_test(ITPCdiff_fisherZ_normResp_combinedSubs, permuteITPCdiff_fisherZ_passiveVcuedsniff_normResp);
  524. % Plot ITPC difference between Cued Sniff and passive with Resp Overlay
  525. % Respiratory epochs for overlay
  526. resp_epochs_byRespPhaseBin = data_supp.resp_epochs_byRespPhaseBin;
  527. resp_epochs_byRespPhaseBin_allsubs_cuedSniff = squeeze(cat(3,resp_epochs_byRespPhaseBin{2}{1},resp_epochs_byRespPhaseBin{2}{2},resp_epochs_byRespPhaseBin{2}{3},...
  528. resp_epochs_byRespPhaseBin{2}{4},resp_epochs_byRespPhaseBin{2}{5},resp_epochs_byRespPhaseBin{2}{6}));
  529. resp_meanEpoch_byRespPhaseBin_cuedSniff = mean(resp_epochs_byRespPhaseBin_allsubs_cuedSniff,2);
  530. resp_epochs_byRespPhaseBin_allsubs_Passive = squeeze(cat(3,resp_epochs_byRespPhaseBin{1}{1},resp_epochs_byRespPhaseBin{1}{2},resp_epochs_byRespPhaseBin{1}{3},...
  531. resp_epochs_byRespPhaseBin{1}{4},resp_epochs_byRespPhaseBin{1}{5},resp_epochs_byRespPhaseBin{1}{6}));
  532. resp_meanEpoch_byRespPhaseBin_Passive = mean(resp_epochs_byRespPhaseBin_allsubs_Passive,2);
  533. [redblue,red,blue]=CustomColormap;
  534. % subplot(3,2,sub);
  535. h=G_Imagesc(ITPCdiff_fisherZ_normResp_combinedSubs,'yaxis',param.freq,...
  536. 'ytick',[1,2,3,8,15,30,50,100],'xtick',1:50:202,'ytick_precision',1,'clim',[0,0.5],...
  537. 'ylabel','Frequency (Hz)','xlabel','','overlay',resp_meanEpoch_byRespPhaseBin_cuedSniff);
  538. title(sub_IDs{sub});
  539. for k = 1:length(clustinfo_normResp_combinedSubs_ITPCdiff.pos_clusters)
  540. if clustinfo_normResp_combinedSubs_ITPCdiff.pos_clusters(k).p < 0.05
  541. G_Outline_Pixels(h,clustinfo_normResp_combinedSubs_ITPCdiff.pos_clusters(k).inds,'linecolor','k','linewidth',1.5);
  542. end
  543. end
  544. colormap(red);
  545. title('Intentional Sniff vs. Passive Nasal Inhalation');
  546. ax=gca;
  547. ax.XTickLabel={'-\pi','','0','','\pi'};
  548. ax.FontSize = 16;
  549. ax.XLabel.String='Respiratory Phase';
  550. cb=colorbar;
  551. cb.Label.String='ITPC difference value (Fisher-transformed)';
  552. cb.Limits = [0,0.5];
  553. cb.Ticks = 0:0.25:1;
  554. % cb.Label.String='ITPCz';
  555. % cb.Limits = [0,16];
  556. % %% Plot combined subjects cued sniff and passive normalized by respiratory phase
  557. [redblue,red,blue]=CustomColormap;
  558. tiledlayout(1,2)
  559. nexttile
  560. h=G_Imagesc(ITPC_normResp_combinedSubs{2},'yaxis',param.freq,...
  561. 'ytick',[1,2,3,8,15,30,50,100],'xtick',1:50:202,'ytick_precision',1,'clim',[0,0.5],...
  562. 'ylabel','Frequency (Hz)','xlabel','','overlay',resp_meanEpoch_byRespPhaseBin_cuedSniff);
  563. title(sub_IDs{sub});
  564. for k = 1:length(clustinfo_normResp_combinedSubs{2}.pos_clusters)
  565. if clustinfo_normResp_combinedSubs{2}.pos_clusters(k).p < 0.05
  566. G_Outline_Pixels(h,clustinfo_normResp_combinedSubs{2}.pos_clusters(k).inds,'linecolor','k','linewidth',1.5);
  567. end
  568. end
  569. colormap(red);
  570. title('Intentional Sniff');
  571. ax=gca;
  572. ax.XTickLabel={'-\pi','','0','','\pi'};
  573. ax.FontSize = 16;
  574. ax.XLabel.String='Respiratory Phase';
  575. cb=colorbar;
  576. cb.Label.String='ITPC';
  577. cb.Limits = [0,0.5];
  578. cb.Ticks = 0:0.25:1;
  579. colorbar off
  580. % Plot combined subjects passive normalized by respiratory phase
  581. [redblue,red,blue]=CustomColormap;
  582. nexttile
  583. % subplot(3,2,sub);
  584. h=G_Imagesc(ITPC_normResp_combinedSubs{1},'yaxis',param.freq,...
  585. 'ytick',[1,2,3,8,15,30,50,100],'xtick',1:50:202,'ytick_precision',1,'clim',[0,0.5],...
  586. 'ylabel','Frequency (Hz)','xlabel','','overlay',resp_meanEpoch_byRespPhaseBin_Passive);
  587. title(sub_IDs{sub});
  588. for k = 1:length(clustinfo_normResp_combinedSubs{1}.pos_clusters)
  589. if clustinfo_normResp_combinedSubs{1}.pos_clusters(k).p < 0.05
  590. G_Outline_Pixels(h,clustinfo_normResp_combinedSubs{1}.pos_clusters(k).inds,'linecolor','k','linewidth',1.5);
  591. end
  592. end
  593. colormap(red);
  594. title('Passive Inhalation');
  595. ax=gca;
  596. ax.XTickLabel={'-\pi','','0','','\pi'};
  597. ax.FontSize = 16;
  598. ax.XLabel.String='Respiratory Phase';
  599. cb=colorbar;
  600. cb.Label.String='ITPC';
  601. cb.Limits = [0,0.5];
  602. cb.Ticks = 0:0.25:1;
  603. xlabel('');
  604. ylabel('');
  605. % cb.Label.String='ITPCz';
  606. % cb.Limits = [0,16];
  607. % saveas(gcf,'ITPC_Passive_normResp_combined6subs','fig');
  608. % saveas(gcf,'ITPC_Passive_normResp_combined6subs','png');
  609. set(gcf,'pos',[537 818 1023 420]);
  610. %% ITPC Permuted Distribution for Cued sniff vs. Passive inhale Subset matched inhale dur
  611. total_trials_cued = size(ph_epochs_cuedSniff,3);
  612. total_trials_passive = size(ph_epochs_passive,3);
  613. freq = logspace( log10( 1), log10( 100), 80); % logarithmic steps in frequency for filtering
  614. theta_ind = find(freq>=2&freq<=8);
  615. tic
  616. for i = 1:1000
  617. % rand_passive_inds = randperm( total_trials, num_fixedTrials ); % old
  618. rand_inds_cued = randi( total_trials_cued, [1, total_trials_cued] );
  619. ph_epochs_resample_cuedSniff = ph_epochs_cuedSniff(:,:,rand_inds_cued);
  620. ITPC_resample_cuedSniff{i} = squeeze(abs(mean(exp(1i * ph_epochs_resample_cuedSniff), 3))); % length of mean vector over trials (dimension 3 of phase mat) = ITPC
  621. ITPC_distr_maxTheta_cuedSniff(i) = max(ITPC_resample_cuedSniff{i}(theta_ind,:),[],'all');
  622. rand_inds_passive = randi( total_trials_passive, [1, total_trials_passive] );
  623. ph_epochs_resample_passive = ph_epochs_passive(:,:,rand_inds_passive);
  624. ITPC_resample_passive{i} = squeeze(abs(mean(exp(1i * ph_epochs_resample_passive), 3))); % length of mean vector over trials (dimension 3 of phase mat) = ITPC
  625. ITPC_distr_maxTheta_passive(i) = max(ITPC_resample_passive{i}(theta_ind,:),[],'all');
  626. end
  627. toc
  628. % Plot resampled distribution of passive vs. intentional sniff
  629. histogram(ITPC_distr_maxTheta_cuedSniff,32,'FaceColor',[0 0.6 1])
  630. hold on
  631. histogram(ITPC_distr_maxTheta_passive,32,'FaceColor',[1 0.6 0])
  632. legend('Intentional Sniff', 'Passive Inhale','box','off');
  633. ax=gca; ax.TickDir = 'out'; ax.LineWidth=1; box off; ax.FontSize=16;
  634. xlabel('ITPC');
  635. ylabel('Resample #');
  636. saveas(gcf,'ITPC_resampled_cuedVsPassiveSubsetMatchedDur','fig');
  637. exportgraphics(gcf,'ITPC_resampled_cuedVsPassiveSubsetMatchedDur.pdf','ContentType','vector');
  638. ITPC_distr_maxTheta_cuedSniff - ITPC_distr_maxTheta_passive;
  639. histogram(ans,32)
  640. % Stats on bootstrapped ITPC difference between Intentional sniff and
  641. % Passive Inhale
  642. bootstrap_ITPCdiff = ITPC_distr_maxTheta_cuedSniff - ITPC_distr_maxTheta_passive;
  643. [muHat_ITPCdiff,sigmaHat_ITPCdiff,muCI_ITPCdiff,sigmaCI_ITPCdiff] = normfit(bootstrap_ITPCdiff, 0.05)
  644. %% Fig S4F
  645. ev_epochs_6normosmic_Passive = data_supp.ev_epochs_6normosmic_Passive;
  646. sd_6_normosmic_Passive = data_supp.sd_6_normosmic_Passive;
  647. para = data_supp.para;
  648. for sub = 1:6 % first 6 subs are normosmic
  649. resp_epochs_Passive_bySub{sub} = squeeze(zscore(G_EpochTS(data_supp.resp_Passive{sub}, (data_supp.sniffonsets_Passive{sub}, [-1,5]*srate)));
  650. resp_meanEpoch_Passive_bySub{sub} = squeeze(mean(resp_epochs_Passive_bySub{sub},2));
  651. end
  652. % Passive - first 6 subs are normosmics
  653. baseline = para.baseline;
  654. baselineloc = G_RangeLoc( epoch_time{1}, baseline);
  655. freq = para.freq;
  656. % theta_ind = find(freq >= 3 & freq <= 8);
  657. theta_ind = find(freq > 2 & freq <= 8);
  658. num_freqs = size(ev_epochs_6normosmic_Passive{1}{1},1);
  659. numtrials=[]; avg_Passive=[]; response_Passive=[]; amp_epochs_corec_Passive=[]; amp_epochs_theta_Passive=[]; mean_amp_theta_Passive=[]; sem_amp_theta_Passive=[];
  660. for sub = 1:6 % first 6 subs are normosmics
  661. numtrials(sub) = size(ev_epochs_6normosmic_Passive{sub}{1},3);
  662. avg_Passive{sub} = mean(ev_epochs_6normosmic_Passive{sub}{1}(:,baselineloc),2);
  663. rand_passive_inds_sub{sub} = randperm( size(ev_epochs_6normosmic_Passive{sub}{1},3),size(ev_epochs_6normosmic_cuedSniff{sub}{1},3) );
  664. response_Passive{sub} = bsxfun( @minus, ev_epochs_6normosmic_Passive{sub}{1}, avg_Passive{sub} );
  665. response_theta_Passive{sub} = squeeze(mean(response_Passive{sub}(theta_ind,:,:),1));
  666. amp_epochs_corec_Passive{sub} = bsxfun( @rdivide, response_Passive{sub}, sd_6_normosmic_Passive{sub}{1} );
  667. amp_epochs_theta_Passive{sub} = squeeze(mean(amp_epochs_corec_Passive{sub}(theta_ind,:,:),1));
  668. inc_theta_Passive{sub} = squeeze(mean(amp_epochs_theta_Passive{sub}(post500ms_ind,rand_passive_inds_sub{sub}) - amp_epochs_theta_Passive{sub}(pre500ms_ind,rand_passive_inds_sub{sub}),1));
  669. inc_theta_Passive{sub} = mean(inc_theta_Passive{sub},1);
  670. mean_amp_theta_Passive(:,sub) = mean(amp_epochs_theta_Passive{sub},2);
  671. sem_amp_theta_Passive(:,sub) = std(amp_epochs_theta_Passive{sub},[],2) / sqrt(numtrials(sub));
  672. end
  673. % collapse across subjects
  674. thetaAmp_epochs_Passive=[]; resp_epochs_Passive_acrossSubs=[];
  675. thetaAmp_epochs_Passive = cell2mat(amp_epochs_theta_Passive);
  676. resp_epochs_Passive_acrossSubs = cell2mat(resp_epochs_Passive_bySub(1:6));
  677. inc_theta_Passive_allSubs = cell2mat(inc_theta_Passive);
  678. inc_theta_Passive_allSubs = cell2mat(inc_theta_Passive);
  679. % calculate inhale peak, latency to inhale peak, theta peak,
  680. % latency to theta peak for Passive
  681. inhalePk_Passive=[]; time2inhalePk_ind_Passive=[]; thetaPk_CuedSniff=[]; time2thetaPk_ind_Passive=[]; numTrials_Passive_allSubs=[];
  682. numTrials_Passive_allSubs = size(thetaAmp_epochs_Passive,2);
  683. for trial = 1:size(resp_epochs_Passive_acrossSubs,2)
  684. [inhalePk_Passive(trial), time2inhalePk_ind_Passive(trial)] = max(resp_epochs_Passive_acrossSubs(500:1500,trial));
  685. [thetaPk_Passive(trial), time2thetaPk_ind_Passive(trial)] = max(thetaAmp_epochs_Passive(500:1500,trial));
  686. end
  687. time2inhalePk_ms_Passive=[]; time2thetaPk_ms_Passive=[];
  688. time2inhalePk_ms_Passive = ((time2inhalePk_ind_Passive) / srate) * 1000;
  689. time2thetaPk_ms_Passive = ((time2thetaPk_ind_Passive) / srate) * 1000;
  690. isoutlier(thetaPk_Passive);
  691. thetaPk_Passive_outliersRem = thetaPk_Passive(~isoutlier(thetaPk_Passive));
  692. inhalePk_Passive_outliersRem = inhalePk_Passive(~isoutlier(thetaPk_Passive));
  693. [r,p] = corr(thetaPk_Passive_outliersRem',inhalePk_Passive_outliersRem')
  694. scatter(inhalePk_Passive_outliersRem', thetaPk_Passive_outliersRem', 'filled','MarkerEdgeColor', [0 0 0.7],'MarkerFaceColor',[0 0 0.7])
  695. ax=gca; ax.TickDir='out'; ax.LineWidth = 1; box off; ax.FontSize=16;
  696. xlabel('Peak inhale magnitude (z score)');
  697. ylabel('Maximal Theta Amplitude (normalized)');
  698. ax = gca; ax.TickDir = 'out'; ax.FontSize = 16; ax.LineWidth = 1;
  699. text(0.75,27.5,append('r = ',num2str(r,2)),'FontSize',14)
  700. text(0.75,25,append('p = ',num2str(p,1)),'FontSize',14)
  701. l=lsline;
  702. l.LineWidth = 2;
  703. set(gcf,'pos',[1000 728 815 510]);
  704. %% Fig S5 - Modulation index on proximal vs. distal contacts
  705. % Filtered steps for phase frequency across theta band - ODOR
  706. para.ph_freq = linspace(2,8,19);
  707. para.ph_bandwidth = 2;
  708. para.amp_freq = logspace(log10(29),log10(153),32);
  709. para.amp_bandwidth = logspace(log10(15),log10(42),32);
  710. nbamps = length( para.amp_freq);
  711. nbphs = length(para.ph_freq);
  712. ph=[]; amp=[]; ph_epochs=[]; amp_epochs=[]; ph_concat=[]; amp_concat=[];
  713. ph_allTrials=[]; amp_allTrials=[]; amp_ts=[]; ph_ts=[];
  714. for sub = 1:num_subs
  715. for freq_idx = 1 : nbphs
  716. bpfreq = para.ph_freq( freq_idx) + [-0.5, 0.5]*para.ph_bandwidth;
  717. % fprintf( 'phase filtering %d %d\n', bpfreq(1), bpfreq(2));
  718. if bpfreq(1) < eps
  719. ph_ts{sub}( freq_idx, :) = ft_preproc_lowpassfilter( distalCh_odorTasks{sub}, srate, bpfreq(2), [], 'fir');
  720. else
  721. ph_ts{sub}( freq_idx, :) = ft_preproc_bandpassfilter( distalCh_odorTasks{sub}, srate, bpfreq, [], 'fir');
  722. end
  723. end
  724. ph{sub} = angle( hilbert( ph_ts{sub}'));
  725. ph{sub} = wrapTo2Pi( ph{sub});
  726. ph{sub} = ph{sub}';
  727. ph_epochs{sub} = squeeze(G_EpochTS(ph{sub}, sniffonsets_odorTasks{sub}, [1,4]*srate));
  728. % ph_epochs{sub} = squeeze(G_EpochTS(ph{sub}, trough_thresh_times{sub}, [-0.25,0.25]*srate));
  729. for freq_idx = 1 : nbamps
  730. bpfreq = para.amp_freq( freq_idx) + [-0.5, 0.5]*para.amp_bandwidth( freq_idx);
  731. fprintf( 'amplitude filtering %d %d\n', bpfreq(1), bpfreq(2));
  732. amp_ts{sub}( freq_idx, :) = ft_preproc_bandpassfilter( lfp_odorTasks{sub}, srate, bpfreq, [], 'fir');
  733. end
  734. amp{sub} = abs(hilbert(amp_ts{sub}));
  735. amp_epochs{sub} = squeeze(G_EpochTS(amp{sub}, sniffonsets_odorTasks{sub}, [1,4]*srate));
  736. % amp_epochs{sub} = squeeze(G_EpochTS(amp{sub}, trough_thresh_times{sub}, [-0.25,0.25]*srate));
  737. end
  738. % Concatenate phase and amplitude epochs
  739. ph_allTrials = cat(3,ph_epochs{1},ph_epochs{2},ph_epochs{3},ph_epochs{4},...
  740. ph_epochs{5}, ph_epochs{6});
  741. ph_concat = reshape(ph_allTrials, size(ph_allTrials,1), []);
  742. amp_allTrials = cat(3,amp_epochs{1},amp_epochs{2},amp_epochs{3},...
  743. amp_epochs{4},amp_epochs{5},amp_epochs{6});
  744. amp_concat = reshape(amp_allTrials,length(para.amp_freq),[]);
  745. % Calculate MI
  746. para.nbsurrogate = 200;
  747. para.nbbins = 18;
  748. [raw_MI_distal, norm_MI_distal, bin_amp_distal, ph_wins_distal] = G_MI( ph_concat, amp_concat, para.nbsurrogate, para.nbbins, srate);
  749. % Plot proximal vs. distal
  750. norm_MI_proximal = data_supp.MI_proximal.norm_MI;
  751. norm_MI_p = G_z2p(norm_MI_proximal);
  752. norm_MI_FDR = fdr(norm_MI_p(:));
  753. norm_MI_thresh = G_p2z(norm_MI_FDR)
  754. norm_MI_distal = norm_MI;
  755. plot(para.amp_freq,norm_MI_proximal(:,7),'k','LineWidth',2)
  756. yline(norm_MI_thresh(2),'Color',[0.7 0 0],'LineStyle','--','LineWidth',1)
  757. hold on
  758. plot(para.amp_freq,norm_MI_distal(:,7),'Color',[0.6 0.6 0.6],'LineWidth',2)
  759. legend({'Proximal','','Distal'},'box','off');
  760. ax=gca; ax.FontSize = 16; ax.LineWidth=1; ax.TickDir='out'; box off
  761. xlim([29 150]);
  762. xlabel('Frequency (Hz)');
  763. ylabel('Modulation Index (z score)');
  764. set(gcf,'pos',[300 300 673 571]);
  765. %% Fig S6 - Sniffs with and without intranasal electrode in place
  766. %% Fig S6A - Sniff traces
  767. Time = -0.5 : 1/srate : 3;
  768. figure
  769. for sub = 1:7
  770. plot(Time,data_supp.resp_epochs{sub},'Color',[1 0 0 0.1],'LineWidth',1)
  771. hold on
  772. end
  773. for sub = 8:13
  774. plot(Time,data_supp.resp_epochs{sub-7},'Color',[0 0 1 0.1],'LineWidth',1);
  775. hold on
  776. end
  777. % Create "dummy" lines with max alpha (alpha = 1) for the legend only
  778. % Set 'HandleVisibility' to 'off' so they don't show up in the main plot
  779. h1_legend = plot(NaN, NaN, 'r-', 'LineWidth', 2, 'HandleVisibility', 'off');
  780. h2_legend = plot(NaN, NaN, 'b-', 'LineWidth', 2, 'HandleVisibility', 'off');
  781. % Note: The 'HandleVisibility' does not affect how they appear in the legend
  782. % once you explicitly include their handles.
  783. % Create the legend using the handles of the dummy lines
  784. legend([h1_legend, h2_legend], {'Without', 'With'}, 'Location', 'best','box','off');
  785. hold off;
  786. set(gcf,'pos',[300 300 913 732]);
  787. ax=gca; ax.TickDir = 'out'; ax.LineWidth=1; box off; ax.FontSize=16;
  788. ylabel('Airflow (z score)');
  789. xlabel('Time (s)');
  790. %% Fig S6B - histogram of sniff duration with and without intranasal electrode
  791. % Mark exhale onsets to find duration
  792. nbsamps = size(data_supp.resp_epochs{1},1);
  793. base_idx = zeros([1,nbsamps]);
  794. % Time = -1 : 1/srate : 5;
  795. % base_idx(Time >= -1 & Time <= 0) = ones([1,length(0:1*srate)]);
  796. Time = -0.5 : 1/srate : 3.5;
  797. base_idx(1:0.5*srate+1) = ones([1,length(1:0.5*srate+1)]);
  798. resp_meanCorec_epochs = [];
  799. % baseline correct with scale - values fall from 0 to 1
  800. for sub = 1:num_total_subs
  801. resp_bc_epochs{sub} = G_BaselineCorrect(data_supp.resp_epochs{sub},1,base_idx,'scale');
  802. resp_meanCorec_epochs(:,sub) = mean(resp_bc_epochs{sub},2);
  803. end
  804. % Inputs:
  805. % resp_data : 1x13 cell array
  806. % each cell = [samples x trials] matrix
  807. % time : [samples x 1] time vector from -0.5 to 3 s
  808. nSub = numel(resp_bc_epochs);
  809. % Preallocate output
  810. exhale_sample = cell(nSub,1);
  811. exhale_time = cell(nSub,1);
  812. % Define detection window
  813. win_idx = find(Time >= 0.25 & Time <= 3);
  814. for s = 1:nSub
  815. data = resp_bc_epochs{s}; % samples x trials
  816. [nSamp, nTrials] = size(data);
  817. exhale_sample{s} = nan(1,nTrials);
  818. exhale_time{s} = nan(1,nTrials);
  819. for tr = 1:nTrials
  820. y = data(:,tr);
  821. % Restrict to detection window
  822. y_win = y(win_idx);
  823. t_win = Time(win_idx);
  824. % Find falling crossings of 0.5
  825. cross_idx = find(y_win(1:end-1) >= 0.5 & ...
  826. y_win(2:end) < 0.5);
  827. if ~isempty(cross_idx)
  828. % Take first falling crossing
  829. idx = cross_idx(1);
  830. % Linear interpolation for better precision
  831. y1 = y_win(idx);
  832. y2 = y_win(idx+1);
  833. t1 = t_win(idx);
  834. t2 = t_win(idx+1);
  835. frac = (0.5 - y1) / (y2 - y1);
  836. t_cross = t1 + frac * (t2 - t1);
  837. % Store
  838. exhale_time{s}(tr) = t_cross;
  839. % Convert to nearest global sample index
  840. [~, global_idx] = min(abs(Time - t_cross));
  841. exhale_sample{s}(tr) = global_idx;
  842. end
  843. end
  844. end
  845. % Calculate sniff duration for ECoG patients vs. OBE participants
  846. ECoG_IDs = data_supp.resp_epochs_IDs(1:7);
  847. OB_IDs = data_supp.resp_epochs_IDs(8:13);
  848. num_ECoG = length(ECoG_IDs);
  849. num_OB = length(OB_IDs);
  850. for sub = 1:num_ECoG
  851. sniff_duration_ECoG{sub} = (exhale_sample{sub} - 0.5*srate)/srate;
  852. end
  853. meanBySub_sniff_duration_ECoG = cellfun(@mean, sniff_duration_ECoG);
  854. mean_sniff_duration_ECoG = mean(meanBySub_sniff_duration_ECoG)
  855. sem_sniff_duration_ECoG = std(meanBySub_sniff_duration_ECoG,[])/sqrt(num_ECoG);
  856. for sub = 1:num_OB
  857. sniff_duration_OB{sub} = (exhale_sample{sub+num_ECoG} - 0.5*srate) / srate;
  858. end
  859. meanBySub_sniff_duration_OB = cellfun(@mean, sniff_duration_OB);
  860. mean_sniff_duration_OB = mean(meanBySub_sniff_duration_OB);
  861. sem_sniff_duration_OB = std(meanBySub_sniff_duration_OB,[])/sqrt(num_OB);
  862. % Bar plot of sniff duration between OBE and ECoG
  863. [h,p,ci,stats]=ttest2(meanBySub_sniff_duration_OB, meanBySub_sniff_duration_ECoG)
  864. mean_sniffDur_OB_ECoG(1) = mean_sniff_duration_OB;
  865. mean_sniffDur_OB_ECoG(2) = mean_sniff_duration_ECoG;
  866. sem_sniffDur_OB_ECoG(1) = sem_sniff_duration_OB;
  867. sem_sniffDur_OB_ECoG(2) = sem_sniff_duration_ECoG;
  868. % plot_IDs2 = {'','P1','P2','P3','P4','P5','P6'};
  869. bar( mean_sniffDur_OB_ECoG, 'FaceColor','none',...
  870. 'EdgeColor','k','LineWidth',1)
  871. hold on
  872. scatter(1, meanBySub_sniff_duration_OB, 75, sub_colors,'LineWidth',1)
  873. scatter(2, meanBySub_sniff_duration_ECoG, 75, 'k', 'LineWidth',1)
  874. er = errorbar(mean_sniffDur_OB_ECoG, sem_sniffDur_OB_ECoG);
  875. er.Color = [0 0 0];
  876. er.LineStyle = 'none';
  877. er.LineWidth = 1;
  878. set(gcf,'pos',[300 300 566 603]);
  879. xticklabels({'With','Without'});
  880. xlabel('Presence of intranasal electrode');
  881. ylabel('Sniff duration (s)');
  882. ax=gca; ax.TickDir='out'; ax.LineWidth=1; ax.FontSize=16; box off

code_suppFigs.m, under CC-BY-4.0 · at the source

Overview

Authors: Andrew Sheriff1, Mahmoud Omidbeigi1, Gregory Lane1, Qiaohan Yang1, Guangyu Zhou1, Adam Dede1, Naelly Arriaga1, Vivek Sagar1, Ania M. Holubecki1, Rodrigo M. Braga1, Leslie M. Kay2,3, Bruce K. Tan4, Christina Zelano1
  1. Department of Neurology, Northwestern University Feinberg School of Medicine, Chicago, IL, USA
  2. Department of Psychology, The University of Chicago, Chicago, IL, USA
  3. Institute for Mind and Biology, The University of Chicago, Chicago, IL, USA
  4. Department of Otolaryngology—Head and Neck Surgery, Northwestern University Feinberg School of Medicine, Chicago, IL, USA
Institutions: Northwestern University (United States); University of Chicago (United States)
Journal: Science advances, volume 12, issue 27, article eaee1002
Dates: received 20 November 2025; accepted 14 May 2026; published online 3 July 2026; in print July 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1126/sciadv.aee1002 · PMID 42397924 · PMCID PMC13330860 · OpenAlex W7167286658
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: human (organism)
Methods: Spectral & time-frequency, Preprocessing, Connectivity, Statistics, Single-unit activity, calcium imaging, Smoothing, state filtering, decompositions, Physiology & signal measures
MeSH: Odorants*, Olfactory Bulb*, Olfactory Perception*, Smell*, Theta Rhythm*, Humans (* major topic)
Journal subjects: Neuroscience, Neurophysiology
Topic: Olfactory and Sensory Function Studies (Sensory Systems, Neuroscience), according to OpenAlex
Funding: National Institute on Deafness and Other Communication Disorders (R01DC016364, R01DC018539, R01DC021663); NINDS (T32NS047987)
Citations: cited by 1 paper (Europe PMC); 121 references in the paper
Research resources: RRID:SCR_001622, via the PsychToolBox extension RRID:SCR_002881, RRID:SCR_004849

Abstract

When sampling odors, humans typically take a single long sniff, unlike most other mammals who sample odors through bouts of rapid repetitive sniffing. Decades of work has established that rapid sniffing rhythms underlie the organizational principles of odor coding, with sniff speed clocking odor responses in the olfactory bulb. In the absence of rapid sniffing, how are odor responses organized in the human olfactory system? Because most mammals sniff at rates centered around the theta frequency, we hypothesized that the olfactory bulb exploits a theta-range neural oscillation to set the pace of odor coding in the system. Here, we used high-precision neural recordings from the human olfactory bulb to show that initiation of a sniff elicits and temporally aligns theta oscillations in the bulb and that sniff-induced theta oscillations organize the timing and amplitude of odor responses. These findings suggest that, despite the lack of rapid sniffing bouts in humans, the system has preserved a similarly timed unit of olfactory processing.

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 16 matches between paragraphs and lines of code.

Zenodo 18791231

License: CC-BY-4.0
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Languages: MATLAB (22)
Size: 49 files, 22 scripts
Software Heritage: not checked
Found in: “Data, code, and materials availability:”
Holds: README
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: Statistics and Machine Learning Toolbox (7 files), FieldTrip (6 files), Image Processing Toolbox (6 files), Signal Processing Toolbox (5 files), shadedErrorBar (3 files), CircStat (2 files), Chronux (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers (HTTP 200)
  • 27 September 2026: the link answers (HTTP 200)
22 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;
  • 22 scripts, each with its path and the digest of its content;
  • 16 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

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

Data

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

Data, code, and materials availability

All data and code needed to evaluate and reproduce the results in the paper are present in the paper and/or the Supplementary Materials. In addition, data and code are available on Zenodo (DOI: 10.5281/zenodo.18791231 (http://dx.doi.org/10.5281/zenodo.18791231), available at https://zenodo.org/records/18791231). This study did not generate new materials.

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, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 13 authors, 6 MeSH terms, 2 funders, 118 references, 3 RRIDs.

Cite

This paper

Sheriff, A., Omidbeigi, M., Lane, G., Yang, Q., Zhou, G., Dede, A., Arriaga, N., Sagar, V., Holubecki, A. M., Braga, R. M., Kay, L. M., Tan, B. K., & Zelano, C. (2026). Theta oscillations are an organizational unit of odor processing in the olfactory bulb. Science advances, 12(27), eaee1002. https://doi.org/10.1126/sciadv.aee1002

BibTeX

@article{sheriff2026theta,
author = {Sheriff, Andrew and Omidbeigi, Mahmoud and Lane, Gregory and Yang, Qiaohan and Zhou, Guangyu and Dede, Adam and Arriaga, Naelly and Sagar, Vivek and Holubecki, Ania M. and Braga, Rodrigo M. and Kay, Leslie M. and Tan, Bruce K. and Zelano, Christina},
title = {{Theta oscillations are an organizational unit of odor processing in the olfactory bulb}},
journal = {Science advances},
year = {2026},
month = jul,
volume = {12},
number = {27},
pages = {eaee1002},
publisher = {American Association for the Advancement of Science},
issn = {2375-2548},
doi = {10.1126/sciadv.aee1002},
url = {https://doi.org/10.1126/sciadv.aee1002},
pmid = {42397924},
pmcid = {PMC13330860}
}

RIS

TY - JOUR
AU - Sheriff, Andrew
AU - Omidbeigi, Mahmoud
AU - Lane, Gregory
AU - Yang, Qiaohan
AU - Zhou, Guangyu
AU - Dede, Adam
AU - Arriaga, Naelly
AU - Sagar, Vivek
AU - Holubecki, Ania M.
AU - Braga, Rodrigo M.
AU - Kay, Leslie M.
AU - Tan, Bruce K.
AU - Zelano, Christina
TI - Theta oscillations are an organizational unit of odor processing in the olfactory bulb
T2 - Science advances
J2 - Sci Adv
PY - 2026
DA - 2026/07/03
VL - 12
IS - 27
SP - eaee1002
SN - 2375-2548
PB - American Association for the Advancement of Science
DO - 10.1126/sciadv.aee1002
UR - https://doi.org/10.1126/sciadv.aee1002
LA - en
ER -

CSL-JSON

{
"id": "10.1126/sciadv.aee1002",
"type": "article-journal",
"title": "Theta oscillations are an organizational unit of odor processing in the olfactory bulb",
"container-title": "Science advances",
"author": [
{
"family": "Sheriff",
"given": "Andrew"
},
{
"family": "Omidbeigi",
"given": "Mahmoud"
},
{
"family": "Lane",
"given": "Gregory"
},
{
"family": "Yang",
"given": "Qiaohan"
},
{
"family": "Zhou",
"given": "Guangyu"
},
{
"family": "Dede",
"given": "Adam"
},
{
"family": "Arriaga",
"given": "Naelly"
},
{
"family": "Sagar",
"given": "Vivek"
},
{
"family": "Holubecki",
"given": "Ania M."
},
{
"family": "Braga",
"given": "Rodrigo M."
},
{
"family": "Kay",
"given": "Leslie M."
},
{
"family": "Tan",
"given": "Bruce K."
},
{
"family": "Zelano",
"given": "Christina"
}
],
"container-title-short": "Sci Adv",
"volume": "12",
"issue": "27",
"page": "eaee1002",
"DOI": "10.1126/sciadv.aee1002",
"PMID": "42397924",
"PMCID": "PMC13330860",
"ISSN": "2375-2548",
"publisher": "American Association for the Advancement of Science",
"URL": "https://doi.org/10.1126/sciadv.aee1002",
"language": "en",
"issued": {
"date-parts": [
[
2026,
7,
3
]
]
}
}

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-73106-z [code]
Respiratory pauses highlight sleep architecture in mice.
Journal: Nature communications
In common: Chronux, CircStat, FieldTrip, 3 other tools, 4 references
[2] doi:10.1371/journal.pbio.3003810 [code]
Olfactory bulb-cortex oscillations encode perceived odor intensity rather than concentration.
Journal: PLoS biology
In common: FieldTrip, Image Processing Toolbox, Statistics and Machine Learning Toolbox, 7 references
[3] doi:10.7554/elife.110795 [code]
REM sleep prefrontal high-frequency oscillation chains mediate distinct cortical - hippocampal reactivation patterns compared to NREM sleep.
Journal: eLife
In common: Chronux, shadedErrorBar, CircStat, 4 other tools, 3 references
[4] doi:10.1038/s41467-026-72445-1 [code]
Perception and neural representation of intermittent odor stimuli in mice.
Journal: Nature communications
In common: 8 references
[5] doi:10.64898/2026.03.12.710517 [code]
Cortical excitability inversely modulates fMRI connectivity via low-frequency neuronal coupling
Journal: bioRxiv (preprint)
In common: Chronux, shadedErrorBar, CircStat, 4 other tools, 1 reference
[6] doi:10.1038/s41467-026-73001-7 [code]
Separable and integrated pleasantness coding for appetitive and aversive odors across olfactory and ventral prefrontal cortices.
Journal: Nature communications
In common: Signal Processing Toolbox, Statistics and Machine Learning Toolbox, 2 authors
[7] doi:10.1126/sciadv.aed3610 [code]
Dexterous single sniffs for ethological active olfaction.
Journal: Science advances
In common: 8 references
[8] doi:10.1016/j.isci.2026.116240 [code]
Physical activity enhances theta-periodicity of visual attentional allocation.
Journal: iScience
In common: shadedErrorBar, CircStat, Signal Processing Toolbox, 1 other tool, 4 references
[9] doi:10.1371/journal.pbio.3003938 [code]
Theta oscillations tag episodic memories for sleep-dependent consolidation.
Journal: PLoS biology
In common: shadedErrorBar, CircStat, FieldTrip, 3 other tools, 2 references
[10] doi:10.1016/j.celrep.2026.117646 [code]
Medial entorhinal-hippocampal desynchronization parallels the emergence of memory impairment in a mouse model of Alzheimer's disease pathology.
Journal: Cell reports
In common: Chronux, CircStat, FieldTrip, 3 other tools, 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.