OSCR

Conserved post-odor dynamics in the olfactory systems of mice and locusts.

Code ↔ Paper

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

The 1 match
  1. [1] § STAR★Methods › Method details › Odor presentation paradigms ↔ Script_Fig6.m, lines 114–172 · score 0.63 · ani, asa, cin, ep, ieg, mpz

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,148 lines · 41 KB · CC-BY-4.0 · 1 match

  1. %% ==================== Figure 6 ====================
  2. close all; clc; clear;
  3. fileNameKNN = {'m129_1_2_dataset'; 'm149_3_1_dataset';};
  4. % Fig6a, b, g, h, i: KNN Classification of mouse glom dataset and average over sessions
  5. % a/b => fileIter = 2;
  6. % h => fileIter = 1;
  7. KNN = 10;
  8. numTrial = 10;
  9. for fileIter = 1:length(fileNameKNN)
  10. clearvars -except fileNameKNN fileIter KNN numTrial
  11. load([fileNameKNN{fileIter},'\filtData2.mat']);
  12. if fileIter == 1, numOdor = length(fieldnames(filtData)) - 1;
  13. else, numOdor = length(fieldnames(filtData));
  14. end
  15. for OnOffIter = 1:2
  16. if OnOffIter == 1
  17. range = preDuration*fps+1:(preDuration+onDuration)*fps;
  18. else
  19. range = (preDuration+onDuration)*fps+1:(afterDuration+preDuration+onDuration)*fps;
  20. end
  21. numBinAvg = 1 + 3*(OnOffIter==2);
  22. % ====== Restructure the dataset ======
  23. newBlockData = cell(numOdor,1);
  24. clear tempFiltData2
  25. names = fieldnames(filtData);
  26. for i = 1:numOdor
  27. eval(strcat('tempData = filtData.', names{i+(fileIter==1)},';'));
  28. tempFiltData = tempData(1:numTrial,range,:);
  29. iter = ceil(length(range)/numBinAvg);
  30. tempFiltData2 = zeros(numTrial, iter, size(tempFiltData,3));
  31. for ii = 1:numBinAvg:length(range)
  32. try
  33. tempFiltData2(:,(ii-1)/numBinAvg+1,:) = mean(tempFiltData(:,ii:ii+numBinAvg-1,:),2);
  34. catch
  35. tempFiltData2(:,(ii-1)/numBinAvg+1,:) = mean(tempFiltData(:,ii:end,:),2);
  36. end
  37. end
  38. newBlockData{i} = tempFiltData2;
  39. end
  40. timeLength = size(tempFiltData2,2);
  41. % ====== Reshape to units x gloms ======
  42. allUnits = [];
  43. clusterAssignment = [];
  44. for i = 1:numOdor
  45. unitVector = newBlockData{i};
  46. tempUnits = reshape(permute(unitVector,[2 1 3]),[],size(unitVector,3));
  47. allUnits = [allUnits; tempUnits];
  48. clusterAssignment = [clusterAssignment; i*ones(size(tempUnits,1),1)];
  49. end
  50. numUnits = size(allUnits,1);
  51. % ====== Correlation distance with leave-trial-out block ======
  52. tempDistMatrix = squareform(pdist(allUnits,'correlation'));
  53. diagValue = ones(1,numUnits)*10;
  54. fullDistMatrix = tempDistMatrix + diag(diagValue);
  55. trialBlock = ones(timeLength)*10;
  56. trialBlockMatrix = kron(eye(numOdor*numTrial), trialBlock);
  57. fullDistMatrix = fullDistMatrix + trialBlockMatrix*10;
  58. % ====== kNN ======
  59. [~, idx] = sort(fullDistMatrix,2,"ascend");
  60. colIndices = idx(:,1:KNN);
  61. clusterTemp = zeros(size(colIndices));
  62. for nn = 1:KNN
  63. clusterTemp(:,nn) = clusterAssignment(colIndices(:,nn),1);
  64. end
  65. clusterAssignment(:,2) = mode(clusterTemp(:,1:KNN),2);
  66. clusterAssignment(:,3) = clusterAssignment(:,2) == clusterAssignment(:,1);
  67. perCorrectCluster = sum(clusterAssignment(:,3)) ./ size(clusterAssignment(:,1),1);
  68. disp(['Percentage correctly clustered = ', num2str(perCorrectCluster)]);
  69. % ====== Rearrange + percent ======
  70. clear resultMatrixTrialPerRow resultMatrix percentMatrix
  71. resultMatrix = zeros(numOdor,timeLength*numTrial);
  72. resultMatrixTrialPerRow = zeros(numOdor*numTrial,timeLength);
  73. percentMatrixAllTrial = zeros(numOdor,numTrial,timeLength);
  74. percentMatrix = zeros(numOdor+1,timeLength);
  75. for i = 1:numOdor
  76. blockIndex = find(clusterAssignment(:,1)==i);
  77. resultMatrix(i,1:length(blockIndex)) = clusterAssignment(blockIndex,2);
  78. resultMatrixTrialPerRow((i-1)*numTrial+1:i*numTrial,:) = reshape(resultMatrix(i,:),timeLength,numTrial)';
  79. tempPercent = clusterAssignment(blockIndex,3);
  80. percentMatrixTrialPerRow = reshape(tempPercent,timeLength,numTrial)';
  81. percentMatrixAllTrial(i,:,:) = percentMatrixTrialPerRow;
  82. percentMatrix(i,:) = mean(percentMatrixTrialPerRow,1);
  83. end
  84. percentMatrix(end,:) = mean(percentMatrix(1:numOdor,:),1);
  85. if fileIter == 1
  86. smooth = 3 + (OnOffIter==2);
  87. else
  88. smooth = 2 + 2*(OnOffIter==2);
  89. end
  90. percentMatrix(1:end-1,:) = filtfilt(ones(1,smooth)/smooth,1,percentMatrix(1:end-1,:)')';
  91. percentMatrix(end,:) = filtfilt(ones(1,smooth)/smooth,1,percentMatrix(end,:));
  92. % ====== Plot Matrix ======
  93. figure; color = ColorScheme(numOdor);
  94. try
  95. imagesc(resultMatrixTrialPerRow,[0.5 length(odorNames)+0.5]); colormap(color); cbar = colorbar;
  96. catch
  97. imagesc(resultMatrixTrialPerRow,[0.5 length(odorList)-0.5]); colormap(color); cbar = colorbar;
  98. end
  99. xlim([0.5 timeLength]); xticks(linspace(0.5,timeLength,9));
  100. if OnOffIter == 1
  101. xticklabels(linspace(0,onDuration,9));
  102. xlabel 'Mouse ON, time after odor onset(s)'; title 'ON Prediction';
  103. else
  104. xticklabels(linspace(0,afterDuration,9));
  105. xlabel 'Mouse ITI, time after odor offset (s)'; title 'ITI Prediction';
  106. end
  107. yticks(5:10:numOdor*numTrial);
  108. cbar.Ticks = 1:numOdor;
  109. if fileIter == 1
  110. yticklabels({'ace';'eug';'cin';'mpz';'2ep';'ieg';'tcm';'als';'met';'ani';'asa';});
  111. cbar.TickLabels={'ace';'eug';'cin';'mpz';'2ep';'ieg';'tcm';'als';'met';'ani';'asa';};
  112. else
  113. yticklabels({'ace';'msc';'eug';'als'})
  114. cbar.TickLabels={'ace';'msc';'eug';'als'};
  115. end
  116. cbar.Direction = "reverse";
  117. ylabel('True Label'); axis square;
  118. % ====== Plot Percentage ======
  119. if OnOffIter == 1
  120. timeAxis = preDuration+1/fps : 1/fps : (preDuration+onDuration);
  121. else
  122. timeAxis = (preDuration+onDuration)+1/fps : 1/fps : (preDuration+onDuration+afterDuration);
  123. end
  124. timeAxis = timeAxis - timeAxis(1);
  125. timeAxis = timeAxis(1:numBinAvg:end);
  126. figure; color = ColorScheme(numOdor,0.2); hold on;
  127. for i = 1:size(percentMatrix,1)-1
  128. plot(timeAxis(1:size(percentMatrix,2)), percentMatrix(i,:)*100, 'LineStyle','-','LineWidth',2,'Color',color(i,:));
  129. end
  130. plot(timeAxis(1:size(percentMatrix,2)), percentMatrix(end,:)*100, 'Color','k','LineWidth',5);
  131. line([0 (range(end)-range(1))/fps], [1/numOdor*100 1/numOdor*100],'LineStyle',':');
  132. xlim([0 (range(end)-range(1))/fps]);
  133. xticks(linspace(0,(range(end)-range(1))/fps,9));
  134. if OnOffIter == 1
  135. xticklabels(linspace(0,onDuration,9));
  136. xlabel 'Mouse ON, time after odor onset (s)'; title 'ON Prediction';
  137. else
  138. xticklabels(linspace(0,afterDuration,9));
  139. xlabel 'Mouse ITI, time after odor offset (s)'; title 'ITI Prediction';
  140. end
  141. ylabel 'Classification rate (%)'; axis square;
  142. end
  143. end
  144. % -------- Average Across Sessions for A & B--------------
  145. fileNameKNN = {
  146. 'm149_1_2_dataset';
  147. 'm148_2_1_dataset';
  148. 'm149_3_1_dataset';
  149. 'm141_8_1_dataset';
  150. 'm141_5_1_dataset';
  151. 'm141_1_1_dataset';
  152. };
  153. KNN = 10;
  154. numTrial = 10;
  155. totalPercentMatrixON = nan(length(fileNameKNN), 200);
  156. totalPercentMatrixOFF = nan(length(fileNameKNN), 500);
  157. commonTimeON = linspace(0, 5, 200);
  158. commonTimeOFF = linspace(0, 50, 500);
  159. for fileIter = 1:length(fileNameKNN)
  160. clearvars -except fileNameKNN fileIter KNN numTrial totalPercentMatrixON totalPercentMatrixOFF commonTimeON commonTimeOFF
  161. load([fileNameKNN{fileIter},'\filtData2.mat']);
  162. numOdor = length(fieldnames(filtData));
  163. names = fieldnames(filtData);
  164. for OnOffIter = 1:2
  165. if OnOffIter == 1
  166. range = preDuration*fps+1:(preDuration+onDuration)*fps;
  167. else
  168. range = (preDuration+onDuration)*fps+1:(afterDuration+preDuration+onDuration)*fps;
  169. end
  170. numBinAvg = 1 + 3*(OnOffIter==2);
  171. newBlockData = cell(numOdor,1);
  172. clear tempFiltData2
  173. for i = 1:numOdor
  174. eval(strcat('tempData = filtData.', names{i},';'));
  175. tempFiltData = tempData(1:numTrial,range,:);
  176. iter = ceil(length(range)/numBinAvg);
  177. tempFiltData2 = zeros(numTrial, iter, size(tempFiltData,3));
  178. for ii = 1:numBinAvg:length(range)
  179. try
  180. tempFiltData2(:,(ii-1)/numBinAvg+1,:) = mean(tempFiltData(:,ii:ii+numBinAvg-1,:),2);
  181. catch
  182. tempFiltData2(:,(ii-1)/numBinAvg+1,:) = mean(tempFiltData(:,ii:end,:),2);
  183. end
  184. end
  185. newBlockData{i} = tempFiltData2;
  186. end
  187. timeLength = size(tempFiltData2,2);
  188. allUnits = [];
  189. clusterAssignment = [];
  190. for i = 1:numOdor
  191. unitVector = newBlockData{i};
  192. tempUnits = reshape(permute(unitVector,[2 1 3]),[],size(unitVector,3));
  193. allUnits = [allUnits; tempUnits];
  194. clusterAssignment = [clusterAssignment; i*ones(size(tempUnits,1),1)];
  195. end
  196. numUnits = size(allUnits,1);
  197. tempDistMatrix = squareform(pdist(allUnits,'correlation'));
  198. diagValue = ones(1,numUnits)*10;
  199. fullDistMatrix = tempDistMatrix + diag(diagValue);
  200. trialBlock = ones(timeLength)*10;
  201. trialBlockMatrix = kron(eye(numOdor*numTrial), trialBlock);
  202. fullDistMatrix = fullDistMatrix + trialBlockMatrix*10;
  203. [~, idx] = sort(fullDistMatrix,2,"ascend");
  204. colIndices = idx(:,1:KNN);
  205. clusterTemp = zeros(size(colIndices));
  206. for nn = 1:KNN
  207. clusterTemp(:,nn) = clusterAssignment(colIndices(:,nn),1);
  208. end
  209. clusterAssignment(:,2) = mode(clusterTemp(:,1:KNN),2);
  210. clusterAssignment(:,3) = clusterAssignment(:,2) == clusterAssignment(:,1);
  211. perCorrectCluster = sum(clusterAssignment(:,3)) ./ size(clusterAssignment(:,1),1);
  212. disp(['Percentage correctly clustered = ', num2str(perCorrectCluster)]);
  213. clear resultMatrixTrialPerRow resultMatrix percentMatrix
  214. resultMatrix = zeros(numOdor,timeLength*numTrial);
  215. resultMatrixTrialPerRow = zeros(numOdor*numTrial,timeLength);
  216. percentMatrixAllTrial = zeros(numOdor,numTrial,timeLength);
  217. percentMatrix = zeros(numOdor+1,timeLength);
  218. for i = 1:numOdor
  219. blockIndex = find(clusterAssignment(:,1)==i);
  220. resultMatrix(i,1:length(blockIndex)) = clusterAssignment(blockIndex,2);
  221. resultMatrixTrialPerRow((i-1)*numTrial+1:i*numTrial,:) = reshape(resultMatrix(i,:),timeLength,numTrial)';
  222. tempPercent = clusterAssignment(blockIndex,3);
  223. percentMatrixTrialPerRow = reshape(tempPercent,timeLength,numTrial)';
  224. percentMatrixAllTrial(i,:,:) = percentMatrixTrialPerRow;
  225. percentMatrix(i,:) = mean(percentMatrixTrialPerRow,1);
  226. end
  227. percentMatrix(end,:) = mean(percentMatrix(1:numOdor,:),1);
  228. if OnOffIter == 1, smooth = 2; else, smooth = 4; end
  229. percentMatrix(1:end-1,:) = filtfilt(ones(1,smooth)/smooth,1,percentMatrix(1:end-1,:)')';
  230. percentMatrix(end,:) = filtfilt(ones(1,smooth)/smooth,1,percentMatrix(end,:));
  231. if OnOffIter == 1
  232. origTimeON = linspace(0,onDuration,length(percentMatrix(end,:)));
  233. interpCurveON = interp1(origTimeON, percentMatrix(end,:), commonTimeON,'linear');
  234. totalPercentMatrixON(fileIter,:) = interpCurveON;
  235. else
  236. origTimeOFF = linspace(0,afterDuration,length(percentMatrix(end,:)));
  237. interpCurveOFF = interp1(origTimeOFF, percentMatrix(end,:), commonTimeOFF,'linear');
  238. totalPercentMatrixOFF(fileIter,:) = interpCurveOFF;
  239. end
  240. end
  241. end
  242. totalPercentMatrixON(end+1,:) = mean(totalPercentMatrixON, 1,'omitnan');
  243. totalPercentMatrixOFF(end+1,:) = mean(totalPercentMatrixOFF, 1,'omitnan');
  244. timeAxisON = linspace(0,onDuration,size(totalPercentMatrixON,2));
  245. figure; hold on;
  246. for r = 1:fileIter
  247. plot(timeAxisON, totalPercentMatrixON(r,:)*100,'Color',[0.7 0.7 0.7],'LineWidth',2);
  248. end
  249. plot(timeAxisON, totalPercentMatrixON(end,:)*100,'Color','k','LineWidth',4);
  250. chanceLevel = 100/numOdor; yline(chanceLevel,':','Chance','LabelHorizontalAlignment','left');
  251. xlim([0 onDuration]); xticks(linspace(0,onDuration,9)); xticklabels(linspace(0,onDuration,9));
  252. xlabel('Mouse ON, time after odor onset (s)'); ylabel('Classification rate (%)'); title('ON Prediction (n = 6)'); axis square;
  253. timeAxisOFF = linspace(0,afterDuration,size(totalPercentMatrixOFF,2));
  254. figure; hold on;
  255. for r = 1:fileIter
  256. plot(timeAxisOFF, totalPercentMatrixOFF(r,:)*100,'Color',[0.7 0.7 0.7],'LineWidth',2);
  257. end
  258. plot(timeAxisOFF, totalPercentMatrixOFF(end,:)*100,'Color','k','LineWidth',4);
  259. chanceLevel = 100/numOdor; yline(chanceLevel,':','Chance','LabelHorizontalAlignment','left');
  260. xlim([0 afterDuration]); xticks(linspace(0,afterDuration,9)); xticklabels(linspace(0,afterDuration,9));
  261. xlabel('Mouse ITI, time after odor offset (s)'); ylabel('Classification rate (%)'); title('ITI Prediction (n = 6)'); axis square;
  262. % ---------- Average Across Session for H -------------
  263. fileNameKNN = {
  264. 'm129_1_2_dataset';
  265. 'm122_4_4_dataset';
  266. 'm125_1_1_dataset';
  267. 'm126_1_1_dataset';
  268. 'm129_3_1_dataset';
  269. 'm129_4_2_dataset';
  270. 'm134_6_1_dataset';
  271. 'm1716_1_1_dataset';
  272. };
  273. KNN = 10;
  274. numTrial = 10;
  275. totalPercentMatrixON = nan(length(fileNameKNN), 25);
  276. totalPercentMatrixOFF = nan(length(fileNameKNN), 600);
  277. commonTimeON = linspace(0, 1, 25);
  278. commonTimeOFF = linspace(0, 17, 600);
  279. for fileIter = 1:length(fileNameKNN)
  280. clearvars -except fileNameKNN fileIter KNN numTrial commonTimeOFF commonTimeON totalPercentMatrixON totalPercentMatrixOFF
  281. load([fileNameKNN{fileIter},'\filtData2.mat']);
  282. numOdor = length(fieldnames(filtData)) - 1;
  283. names = fieldnames(filtData);
  284. for OnOffIter = 1:2
  285. if OnOffIter == 1
  286. range = preDuration*fps+1:(preDuration+onDuration)*fps;
  287. else
  288. range = (preDuration+onDuration)*fps+1:(afterDuration+preDuration+onDuration)*fps;
  289. end
  290. numBinAvg = 1 + 3*(OnOffIter==2);
  291. newBlockData = cell(numOdor,1);
  292. clear tempFiltData2
  293. for i = 1:numOdor
  294. eval(strcat('tempData = filtData.', names{i+1},';'));
  295. tempFiltData = tempData(1:numTrial,range,:);
  296. iter = ceil(length(range)/numBinAvg);
  297. tempFiltData2 = zeros(numTrial, iter, size(tempFiltData,3));
  298. for ii = 1:numBinAvg:length(range)
  299. try
  300. tempFiltData2(:,(ii-1)/numBinAvg+1,:) = mean(tempFiltData(:,ii:ii+numBinAvg-1,:),2);
  301. catch
  302. tempFiltData2(:,(ii-1)/numBinAvg+1,:) = mean(tempFiltData(:,ii:end,:),2);
  303. end
  304. end
  305. newBlockData{i} = tempFiltData2;
  306. end
  307. timeLength = size(tempFiltData2,2);
  308. allUnits = [];
  309. clusterAssignment = [];
  310. for i = 1:numOdor
  311. unitVector = newBlockData{i};
  312. tempUnits = reshape(permute(unitVector,[2 1 3]),[],size(unitVector,3));
  313. allUnits = [allUnits; tempUnits];
  314. clusterAssignment = [clusterAssignment; i*ones(size(tempUnits,1),1)];
  315. end
  316. numUnits = size(allUnits,1);
  317. tempDistMatrix = squareform(pdist(allUnits,'correlation'));
  318. diagValue = ones(1,numUnits)*10;
  319. fullDistMatrix = tempDistMatrix + diag(diagValue);
  320. trialBlock = ones(timeLength)*10;
  321. trialBlockMatrix = kron(eye(numOdor*numTrial), trialBlock);
  322. fullDistMatrix = fullDistMatrix + trialBlockMatrix*10;
  323. [~, idx] = sort(fullDistMatrix,2,"ascend");
  324. colIndices = idx(:,1:KNN);
  325. clusterTemp = zeros(size(colIndices));
  326. for nn = 1:KNN
  327. clusterTemp(:,nn) = clusterAssignment(colIndices(:,nn),1);
  328. end
  329. clusterAssignment(:,2) = mode(clusterTemp(:,1:KNN),2);
  330. clusterAssignment(:,3) = clusterAssignment(:,2) == clusterAssignment(:,1);
  331. perCorrectCluster = sum(clusterAssignment(:,3)) ./ size(clusterAssignment(:,1),1);
  332. disp(['Percentage correctly clustered = ', num2str(perCorrectCluster)]);
  333. clear resultMatrixTrialPerRow resultMatrix percentMatrix
  334. resultMatrix = zeros(numOdor,timeLength*numTrial);
  335. resultMatrixTrialPerRow = zeros(numOdor*numTrial,timeLength);
  336. percentMatrixAllTrial = zeros(numOdor,numTrial,timeLength);
  337. percentMatrix = zeros(numOdor+1,timeLength);
  338. for i = 1:numOdor
  339. blockIndex = find(clusterAssignment(:,1)==i);
  340. resultMatrix(i,1:length(blockIndex)) = clusterAssignment(blockIndex,2);
  341. resultMatrixTrialPerRow((i-1)*numTrial+1:i*numTrial,:) = reshape(resultMatrix(i,:),timeLength,numTrial)';
  342. tempPercent = clusterAssignment(blockIndex,3);
  343. percentMatrixTrialPerRow = reshape(tempPercent,timeLength,numTrial)';
  344. percentMatrixAllTrial(i,:,:) = percentMatrixTrialPerRow;
  345. percentMatrix(i,:) = mean(percentMatrixTrialPerRow,1);
  346. end
  347. percentMatrix(end,:) = mean(percentMatrix(1:numOdor,:),1);
  348. if OnOffIter == 1, smooth = 3; else, smooth = 4; end
  349. percentMatrix(1:end-1,:) = filtfilt(ones(1,smooth)/smooth,1,percentMatrix(1:end-1,:)')';
  350. percentMatrix(end,:) = filtfilt(ones(1,smooth)/smooth,1,percentMatrix(end,:));
  351. if OnOffIter == 1
  352. origTimeON = linspace(0,onDuration,length(percentMatrix(end,:)));
  353. interpCurveON = interp1(origTimeON, percentMatrix(end,:), commonTimeON,'linear');
  354. totalPercentMatrixON(fileIter,:) = interpCurveON;
  355. else
  356. origTimeOFF = linspace(0,afterDuration,length(percentMatrix(end,:)));
  357. interpCurveOFF = interp1(origTimeOFF, percentMatrix(end,:), commonTimeOFF,'linear');
  358. totalPercentMatrixOFF(fileIter,:) = interpCurveOFF;
  359. end
  360. end
  361. end
  362. totalPercentMatrixON(end+1,:) = mean(totalPercentMatrixON, 1,'omitnan');
  363. totalPercentMatrixOFF(end+1,:) = mean(totalPercentMatrixOFF, 1,'omitnan');
  364. timeAxisON = linspace(0,onDuration,size(totalPercentMatrixON,2));
  365. figure; hold on;
  366. for r = 1:fileIter
  367. plot(timeAxisON, totalPercentMatrixON(r,:)*100,'Color',[0.7 0.7 0.7],'LineWidth',2);
  368. end
  369. plot(timeAxisON, totalPercentMatrixON(end,:)*100,'Color','k','LineWidth',4);
  370. chanceLevel = 100/numOdor; yline(chanceLevel,':','Chance','LabelHorizontalAlignment','left');
  371. xlim([0 onDuration]); xticks(linspace(0,onDuration,5)); xticklabels(linspace(0,onDuration,5));
  372. xlabel('Mouse ON, time after odor onset (s)'); ylabel('Classification rate (%)'); title('ON Prediction (n = 8)'); axis square;
  373. timeAxisOFF = linspace(0,afterDuration,size(totalPercentMatrixOFF,2));
  374. figure; hold on;
  375. for r = 1:fileIter
  376. plot(timeAxisOFF, totalPercentMatrixOFF(r,:)*100,'Color',[0.7 0.7 0.7],'LineWidth',2);
  377. end
  378. plot(timeAxisOFF, totalPercentMatrixOFF(end,:)*100,'Color','k','LineWidth',4);
  379. chanceLevel = 100/numOdor; yline(chanceLevel,':','Chance','LabelHorizontalAlignment','left');
  380. xlim([0 afterDuration]); xticks(linspace(0,afterDuration,9)); xticklabels(linspace(0,afterDuration,9));
  381. xlabel('Mouse ITI, time after odor offset (s)'); ylabel('Classification rate (%)'); title('ITI Prediction (n = 8)'); axis square;
  382. %% Fig6c: PCA analysis and ITI classification
  383. close all; clc; clear
  384. fileName = 'm149_3_1_dataset\';
  385. load(strcat(fileName, '\filtData3.mat'));
  386. odorAbbrev = {'met', 'ace', 'als', 'eug'};
  387. odorOrder = [2 1 4 3];
  388. solBlocks = 4:7;
  389. nOdors = 4;
  390. nTrials = 10;
  391. nGlom = size(blockData.block4,3);
  392. onDuration = 5; preDuration = 2;fps = 16;
  393. trials = 1:nTrials;
  394. nbins = nTrials;
  395. pcDim = 1:3;
  396. offDuration = 50;
  397. timeBins = (onDuration+preDuration)*fps+1:fps*(onDuration+preDuration+offDuration);
  398. [colorMap] = ColorScheme(nOdors);
  399. medRsp = [];
  400. for oo = 1:nOdors
  401. tempName = strcat('filtData.block', num2str(oo));
  402. currentOdor = eval(tempName);
  403. temp = squeeze(mean(currentOdor(trials, timeBins,:),2)); %single trial
  404. medRsp = [medRsp; temp];
  405. end
  406. allData = [medRsp];
  407. [~,data_pca,latent] = pca(allData);
  408. [v] = latent(pcDim)/sum(latent);
  409. v1 = round(v(1)*10000)/100;
  410. v2 = round(v(2)*10000)/100;
  411. v3 = round(v(3)*10000)/100;
  412. st1 = strcat('PC', string(pcDim(1)), '(', num2str(v1),'%)');
  413. st2 = strcat('PC', string(pcDim(2)), '(', num2str(v2),'%)');
  414. st3 = strcat('PC', string(pcDim(3)), '(', num2str(v3),'%)');
  415. fff = figure(1);
  416. set(fff, 'units','normalized','outerposition',[0 0 0.4 0.5])
  417. hold on; grid on;
  418. xlabel(st1,'fontsize', 12);
  419. ylabel(st2,'fontsize', 12);
  420. zlabel(st3,'fontsize', 12);
  421. for t = 1:nOdors
  422. data2 = data_pca(((t-1)*nTrials+1):(nTrials*t), pcDim);
  423. for tt = trials
  424. if tt == nTrials
  425. plot3(data2(tt,1), data2(tt,2), data2(tt,3),...
  426. 'o', 'Color', colorMap(t,:),...
  427. 'MarkerFaceColor', colorMap(t,:),...
  428. 'LineWidth', 2, 'DisplayName',strcat('ITI: ',odorAbbrev{t}),...
  429. 'LineStyle', 'none', 'MarkerSize',8);
  430. else
  431. plot3(data2(tt,1), data2(tt,2), data2(tt,3),...
  432. 'o', 'Color', colorMap(t,:),...
  433. 'MarkerFaceColor', colorMap(t,:),...
  434. 'LineWidth', 2, 'HandleVisibility','off',...
  435. 'LineStyle', 'none', 'MarkerSize',8);
  436. end
  437. hold on;
  438. end
  439. end
  440. legend('Location','eastoutside');
  441. title({'Filled marker = 55s ITI'});
  442. axis square;
  443. %% Fig6d kNN leave-one-out classification for ITI spont activity
  444. clc; close all;
  445. k = 20;
  446. labels = zeros(1, nOdors * nTrials);
  447. nTrials = 10;
  448. nPts = zeros(1, nOdors);
  449. labeltemp = cell(1, nOdors);
  450. for oo = 1:nOdors
  451. labels((oo-1)*nTrials+1:oo*nTrials) = oo;
  452. nPts(oo) = nTrials;
  453. labeltemp{oo} = strcat('ITI:', odorAbbrev{oo});
  454. end
  455. cosDist = squareform(pdist(data_pca(:,1:3),'cosine'));
  456. nPtsTot = size(data_pca,1);
  457. classAccur = [];
  458. pointAccur = [];
  459. classifClass = zeros(nOdors,nOdors);
  460. for dd = 1:nPtsTot
  461. [~, indd] = sort(cosDist(dd,:));
  462. closePt = indd(2:k+1); %k-number closest points
  463. closeLabel(dd,1:k) = labels(closePt); %label of closest points
  464. predictedLabel(dd) = mode(closeLabel(dd,1:k));
  465. queryLabel = labels(dd); %label of query pt
  466. compPt = (queryLabel == closeLabel(dd,:)); %accuracy
  467. pointAccur(dd) = sum(compPt)/length(compPt); %normalized accuracy
  468. classifClass(queryLabel,predictedLabel(dd)) = classifClass(queryLabel,predictedLabel(dd))+1;
  469. end
  470. normclassifClass = classifClass./nPts; % normalized classification
  471. for dd = 1:nOdors
  472. classAccur(dd) = mean(pointAccur((oo-1)*nPts+1:oo*nPts));
  473. end
  474. classRate = diag(normclassifClass)*100;
  475. classRateITI = mean(classRate(1:nOdors));
  476. % Confusion matrix for each increment
  477. figure;
  478. imagesc(normclassifClass); hold on;
  479. cc = colorbar; cc.Label.String = 'P(Classification)';
  480. % colormap(flipud(gray))
  481. caxis([0 1])
  482. cc.Ticks = 0:0.5:1;
  483. set(gca, 'XTick',1:4,'xticklabel',labeltemp,'YTick',1:4,'yticklabel',labeltemp)
  484. ylabel('Actual'); xlabel('Predicted')
  485. axis square
  486. title({strcat('k-NN Classification: k=',num2str(k)),...
  487. strcat('ITI Classification Rate:',num2str(classRateITI),'%')})
  488. %% Fig6d_control kNN leave-one-out classification with shuffled data label
  489. clc; close all;
  490. repeat = 1000;
  491. repeatNormClassifClass = zeros(nOdors,nOdors);
  492. repeatClassAccur = zeros(repeat,1);
  493. repeatClassRate = 0;
  494. for ri = 1 : repeat
  495. closeLabel_shuf = zeros(nPtsTot, k);
  496. predictedLabel_shuf = zeros(1, nPtsTot);
  497. pointAccur_shuf = zeros(1, nPtsTot);
  498. classifClass_shuf = zeros(nOdors, nOdors);
  499. shufLabels = labels(randperm(numel(labels))); % shuffled training labels
  500. for dd = 1:nPtsTot
  501. [~, indd] = sort(cosDist(dd,:));
  502. closePt = indd(2:k+1);
  503. % neighbors use SHUFFLED labels
  504. closeLabel_shuf(dd,1:k) = shufLabels(closePt);
  505. predictedLabel_shuf(dd) = mode(closeLabel_shuf(dd,1:k));
  506. queryLabel = labels(dd); % still true label
  507. compPt = (queryLabel == closeLabel_shuf(dd,:));
  508. pointAccur_shuf(dd) = mean(compPt);
  509. classifClass_shuf(queryLabel, predictedLabel_shuf(dd)) = ...
  510. classifClass_shuf(queryLabel, predictedLabel_shuf(dd)) + 1;
  511. end
  512. normclassifClass_shuf = classifClass_shuf ./ nTrials;
  513. repeatNormClassifClass = repeatNormClassifClass + normclassifClass_shuf;
  514. classRate_shuf = diag(normclassifClass_shuf) * 100;
  515. classRateITI_shuf = mean(classRate_shuf);
  516. repeatClassAccur(ri) = classRateITI_shuf;
  517. end
  518. repeatNormClassifClass = repeatNormClassifClass ./ repeat;
  519. repeatClassRate = mean(repeatClassAccur);
  520. repeatClassStd = std(repeatClassAccur);
  521. fprintf('Repeat %d times with Mean = %.2f%% and STD = %.2f \n', repeat, repeatClassRate , repeatClassStd);
  522. figure;
  523. imagesc(normclassifClass_shuf); hold on;
  524. cc = colorbar; cc.Label.String = 'P(Classification)';
  525. caxis([0 1])
  526. cc.Ticks = 0:0.5:1;
  527. set(gca, 'XTick',1:nOdors,'xticklabel',labeltemp, ...
  528. 'YTick',1:nOdors,'yticklabel',labeltemp);
  529. ylabel('Actual'); xlabel('Predicted');
  530. axis square
  531. title({sprintf('k-NN Shuffled Classification: k = %d', k), ...
  532. sprintf('ITI Shuffled Classification Rate (REAL): %.2f %%', classRateITI_shuf)});
  533. figure; imagesc(repeatNormClassifClass); hold on;
  534. cc = colorbar; cc.Label.String = 'P(Classification)';
  535. caxis([0 1])
  536. cc.Ticks = 0:0.5:1;
  537. set(gca, 'XTick',1:nOdors,'xticklabel',labeltemp,'YTick',1:nOdors,'yticklabel',labeltemp)
  538. ylabel('Actual'); xlabel('Predicted')
  539. axis square
  540. title({'Fig 6d-control', sprintf('Mean classification rate: %.2f%% (n = %d)', repeatClassRate, repeat)});
  541. %% ============= Fig6 a/b/h: accuracy and stats comparing datasets ===========================
  542. clear;
  543. % SETTINGS
  544. % datasetDir = 'm129_1_2_dataset'; % Uncomment for Fig6h
  545. datasetDir = 'm149_3_1_dataset'; % Uncomment for Fig6ab
  546. KNN = 10;
  547. numTrial = 10;
  548. nPerm = 1000;
  549. qFDR = 0.05;
  550. useFDR = true;
  551. doMaxT = true;
  552. % Load data
  553. S = load(fullfile(datasetDir,'filtData2.mat'));
  554. filtData = S.filtData;
  555. fps = S.fps; preDuration = S.preDuration; onDuration = S.onDuration; afterDuration = S.afterDuration;
  556. names = fieldnames(filtData);
  557. odorFields = names;
  558. numOdor = numel(odorFields);
  559. stats = struct([]);
  560. for OnOffIter = 1:2
  561. % Time window + binning
  562. if OnOffIter == 1
  563. range = preDuration*fps+1 : (preDuration+onDuration)*fps;
  564. numBinAvg = 1; smoothN = 2; durSec = onDuration;
  565. plotTitle = sprintf('ON Prediction | %s', datasetDir);
  566. xlab = 'Mouse ON, time after odor onset (s)';
  567. else
  568. range = (preDuration+onDuration)*fps+1 : (afterDuration+preDuration+onDuration)*fps;
  569. numBinAvg = 4; smoothN = 4; durSec = afterDuration;
  570. plotTitle = sprintf('ITI Prediction | %s', datasetDir);
  571. xlab = 'Mouse ITI, time after odor offset (s)';
  572. end
  573. % Build newBlockData (odor -> trials x timeBins x features)
  574. newBlockData = cell(numOdor,1);
  575. for i = 1:numOdor
  576. tempData = filtData.(odorFields{i});
  577. tempFiltData = tempData(1:numTrial, range, :);
  578. iter = ceil(length(range)/numBinAvg);
  579. tempFiltData2 = zeros(numTrial, iter, size(tempFiltData,3));
  580. for ii = 1:numBinAvg:length(range)
  581. b = (ii-1)/numBinAvg + 1;
  582. try
  583. tempFiltData2(:,b,:) = mean(tempFiltData(:,ii:ii+numBinAvg-1,:),2);
  584. catch
  585. tempFiltData2(:,b,:) = mean(tempFiltData(:,ii:end,:),2);
  586. end
  587. end
  588. newBlockData{i} = tempFiltData2;
  589. end
  590. timeLength = size(newBlockData{1},2);
  591. % Reshape into allUnits + trueLabels
  592. allUnits = [];
  593. trueLabels = [];
  594. for i = 1:numOdor
  595. unitVector = newBlockData{i};
  596. tempUnits = reshape(permute(unitVector,[2 1 3]), [], size(unitVector,3));
  597. allUnits = [allUnits; tempUnits];
  598. trueLabels = [trueLabels; i*ones(size(tempUnits,1),1)];
  599. end
  600. numUnits = size(allUnits,1);
  601. numGlom = size(allUnits,2)
  602. rowStd = std(allUnits,0,2);
  603. zidx = (rowStd == 0);
  604. if any(zidx)
  605. allUnits(zidx,:) = allUnits(zidx,:) + 1e-12*randn(sum(zidx), size(allUnits,2));
  606. end
  607. % Distance matrix + leave-trial-out blocks
  608. tempDistMatrix = squareform(pdist(allUnits,'correlation'));
  609. tempDistMatrix(isnan(tempDistMatrix)) = 1;
  610. diagValue = ones(1,numUnits)*10;
  611. fullDistMatrix = tempDistMatrix + diag(diagValue);
  612. trialBlock = ones(timeLength)*10;
  613. trialBlockMatrix = kron(eye(numOdor*numTrial), trialBlock);
  614. fullDistMatrix = fullDistMatrix + trialBlockMatrix*10;
  615. [~, idx] = sort(fullDistMatrix,2,'ascend');
  616. colIndices = idx(:,1:KNN);
  617. % Real decoding
  618. [real_black, real_perOdor] = compute_percent_per_odor( ...
  619. trueLabels, colIndices, trueLabels, numOdor, timeLength, numTrial, smoothN);
  620. % Shuffle null distribution
  621. shuf_black = zeros(nPerm, timeLength);
  622. for pi = 1:nPerm
  623. shufLabels = trueLabels(randperm(numUnits));
  624. shuf_black(pi,:) = compute_percent_per_odor( ...
  625. shufLabels, colIndices, trueLabels, numOdor, timeLength, numTrial, smoothN);
  626. end
  627. % p-values per time bin + FDR / maxT
  628. p_point = (sum(shuf_black >= real_black,1) + 1) / (nPerm + 1);
  629. if useFDR
  630. sig_fdr = mafdr(p_point,'BHFDR',true) < qFDR;
  631. else
  632. sig_fdr = p_point < 0.05/numel(p_point);
  633. end
  634. p_maxT = nan(size(p_point));
  635. sig_maxT = false(size(p_point));
  636. if doMaxT
  637. shuf_mu = mean(shuf_black,1);
  638. shuf_sd = std(shuf_black,0,1) + eps;
  639. real_z = (real_black - shuf_mu) ./ shuf_sd;
  640. maxZ = zeros(nPerm,1);
  641. for pi = 1:nPerm
  642. z_perm = (shuf_black(pi,:) - shuf_mu) ./ shuf_sd;
  643. maxZ(pi) = max(z_perm);
  644. end
  645. p_maxT = (sum(maxZ >= real_z,1) + 1) / (nPerm + 1);
  646. sig_maxT = p_maxT < 0.05;
  647. end
  648. % Plot
  649. timeAxis = linspace(0, durSec, timeLength);
  650. figure; hold on;
  651. lo = prctile(shuf_black,2.5,1);
  652. hi = prctile(shuf_black,97.5,1);
  653. fill([timeAxis fliplr(timeAxis)], [lo*100 fliplr(hi*100)], ...
  654. [0.7 0.7 0.7], 'FaceAlpha', 0.25, 'EdgeColor', 'none');
  655. plot(timeAxis, mean(shuf_black,1)*100, 'Color', [0.5 0.5 0.5], 'LineWidth', 2);
  656. odorColors = ColorScheme(numOdor,0.25);
  657. for oo = 1:numOdor
  658. plot(timeAxis, real_perOdor(oo,:)*100, 'LineWidth', 2, 'Color', odorColors(oo,:));
  659. end
  660. plot(timeAxis, real_black*100, 'k', 'LineWidth', 5);
  661. yline(100/numOdor, ':', 'Chance', 'LabelHorizontalAlignment','left');
  662. xlabel(xlab); ylabel('Classification rate (%)');
  663. title(sprintf('%s | nPerm=%d', plotTitle, nPerm));
  664. axis square; box on; xlim([0 durSec]);
  665. p_for_bar = p_point;
  666. p_for_bar(~sig_fdr) = 1;
  667. yl = ylim;
  668. yBar = yl(1) + 0.05*(yl(2)-yl(1));
  669. plot_sig_bars(timeAxis, p_for_bar, yBar, 8);
  670. fprintf('\n=== %s ===\n', plotTitle);
  671. fprintf('Min pointwise p: %.4g\n', min(p_point));
  672. fprintf('FDR(q=%.3f) sig bins: %d/%d\n', qFDR, sum(sig_fdr), numel(sig_fdr));
  673. if doMaxT
  674. fprintf('MaxT(FWER) sig bins: %d/%d | min p_maxT=%.4g\n', ...
  675. sum(sig_maxT), numel(sig_maxT), min(p_maxT));
  676. end
  677. % store
  678. stats(OnOffIter).timeAxis = timeAxis;
  679. stats(OnOffIter).real_perOdor = real_perOdor;
  680. stats(OnOffIter).real_black = real_black;
  681. stats(OnOffIter).shuf_black = shuf_black;
  682. stats(OnOffIter).p_point = p_point;
  683. stats(OnOffIter).sig_fdr = sig_fdr;
  684. stats(OnOffIter).p_maxT = p_maxT;
  685. stats(OnOffIter).sig_maxT = sig_maxT;
  686. stats(OnOffIter).odorFields = odorFields;
  687. end
  688. % ============= Fig6 e/f/i: accuracy and stats comparing datasets ===========================
  689. clear;
  690. % % Fig6ef list:
  691. % fileNameKNN = {
  692. % 'm149_1_2_dataset';
  693. % 'm148_2_1_dataset';
  694. % 'm141_8_1_dataset';
  695. % 'm141_1_1_dataset';
  696. % 'm149_3_1_dataset';
  697. % };
  698. % Fig6i list:
  699. fileNameKNN = {
  700. 'm122_4_4_dataset';
  701. 'm125_1_1_dataset';
  702. 'm126_1_1_dataset';
  703. 'm134_6_1_dataset';
  704. 'm129_1_2_dataset';
  705. 'm129_4_2_dataset';
  706. 'm129_3_1_dataset';
  707. 'm1716_1_1_dataset';
  708. };
  709. OnOffIter = 2; % 1=ON, 2=ITI/OFF, 3=ITI/OFF random structure
  710. KNN = 10;
  711. numTrial = 10;
  712. binSizePts = 5;
  713. qFDR = 0.05;
  714. sessColor = [0.55 0.80 0.82];
  715. semColor = [0.70 0.70 0.70];
  716. meanColor = [0 0 0];
  717. chanceColor = [0.55 0.55 0.55];
  718. pThresh = [0.05, 0.01, 0.001];
  719. pColors = {[0 0 0]; [0.85 0 0]; [0 0 0.85]};
  720. % Time for interpolation
  721. if OnOffIter == 1
  722. commonTime = linspace(0,5,200);
  723. xlab = 'Time (s)';
  724. elseif OnOffIter == 2
  725. commonTime = linspace(0,50,500);
  726. xlab = 'Time after odor offset (s)';
  727. else
  728. commonTime = linspace(0,17,500);
  729. xlab = 'Time after odor offset (s)';
  730. end
  731. % means across odors by session
  732. nSess = numel(fileNameKNN);
  733. nTime = numel(commonTime);
  734. accSess = nan(nSess, nTime);
  735. chanceFixed = [];
  736. for s = 1:nSess
  737. datasetDir = fileNameKNN{s};
  738. fprintf('Session %d/%d: %s\n', s, nSess, datasetDir);
  739. % Load + parse
  740. S = load(fullfile(datasetDir,'filtData2.mat'));
  741. filtData = S.filtData;
  742. fps = S.fps; preDuration = S.preDuration; onDuration = S.onDuration; afterDuration = S.afterDuration;
  743. names = fieldnames(filtData);
  744. odorFields = names;
  745. numOdor = numel(odorFields);
  746. if isempty(chanceFixed), chanceFixed = 1/numOdor; end
  747. % Window + binning
  748. if OnOffIter == 1
  749. range = preDuration*fps+1 : (preDuration+onDuration)*fps;
  750. numBinAvg = 1; smoothN = 2; durSec = onDuration;
  751. else
  752. range = (preDuration+onDuration)*fps+1 : (afterDuration+preDuration+onDuration)*fps;
  753. numBinAvg = 4; smoothN = 4; durSec = afterDuration;
  754. end
  755. [timeAxis_s, meanTrace_s] = decode_timecurve( ...
  756. filtData, odorFields, range, fps, numTrial, numBinAvg, KNN, smoothN, durSec);
  757. accSess(s,:) = interp1(timeAxis_s, meanTrace_s, commonTime, 'linear', 'extrap');
  758. end
  759. % STATS: one-sided t-test vs chance + BH-FDR
  760. [xStat, accStat] = bin_timecourses(commonTime, accSess, binSizePts);
  761. nBins = numel(xStat);
  762. p_raw = nan(nBins,1);
  763. for b = 1:nBins
  764. y = accStat(:,b);
  765. y = y(~isnan(y));
  766. if numel(y) >= 2
  767. [~, p_raw(b)] = ttest(y, chanceFixed, 'Tail','right');
  768. end
  769. end
  770. sig_fdr = bh_fdr_mask(p_raw, qFDR);
  771. % Plot
  772. figure; hold on;
  773. for s = 1:nSess
  774. plot(commonTime, accSess(s,:)*100, 'Color', sessColor, 'LineWidth', 2);
  775. end
  776. meanAcc = mean(accSess, 1, 'omitnan');
  777. semAcc = std(accSess, 0, 1, 'omitnan') ./ sqrt(sum(~isnan(accSess),1));
  778. fill([commonTime fliplr(commonTime)], ...
  779. [(meanAcc-semAcc)*100 fliplr((meanAcc+semAcc)*100)], ...
  780. semColor, 'FaceAlpha', 0.6, 'EdgeColor','none');
  781. plot(commonTime, meanAcc*100, 'Color', meanColor, 'LineWidth', 3.5);
  782. yline(chanceFixed*100, '--', 'Color', chanceColor, 'LineWidth', 1.3);
  783. xlabel(xlab);
  784. ylabel('Classification rate (%)');
  785. ylim([0 100]);
  786. xlim([commonTime(1) commonTime(end)]);
  787. box off;
  788. yl = ylim;
  789. yBar = yl(1) + 0.03*diff(yl);
  790. plot_sig_dots(xStat, p_raw, sig_fdr, yBar, pThresh, pColors, 7.5);
  791. % Functions for Figure 6 A, B, H
  792. function [blackLine, perOdor] = compute_percent_per_odor(labelVec, colIndices, trueLabels, ...
  793. numOdor, timeLength, numTrial, smoothN)
  794. numUnits = numel(trueLabels);
  795. KNN = size(colIndices,2);
  796. clusterTemp = zeros(numUnits, KNN);
  797. for nn = 1:KNN
  798. clusterTemp(:,nn) = labelVec(colIndices(:,nn));
  799. end
  800. predLabel = mode(clusterTemp,2);
  801. correct = (predLabel == trueLabels);
  802. perOdor = zeros(numOdor, timeLength);
  803. for oo = 1:numOdor
  804. idx = find(trueLabels == oo);
  805. tmpMat = reshape(correct(idx), timeLength, numTrial)';
  806. perOdor(oo,:) = mean(tmpMat,1);
  807. end
  808. if smoothN > 1
  809. for oo = 1:numOdor
  810. perOdor(oo,:) = filtfilt(ones(1,smoothN)/smoothN, 1, perOdor(oo,:));
  811. end
  812. end
  813. blackLine = mean(perOdor,1);
  814. end
  815. function plot_sig_bars(timeAxis, pvals, y, lw)
  816. if nargin < 4, lw = 6; end
  817. thr = [0.05, 0.01, 0.001];
  818. cols = {[0 0 0], [1 0 0], [0 0 1]};
  819. for k = 1:numel(thr)
  820. sig = pvals < thr(k);
  821. d = diff([false sig false]);
  822. starts = find(d==1);
  823. ends = find(d==-1) - 1;
  824. for s = 1:numel(starts)
  825. i1 = starts(s); i2 = ends(s);
  826. line([timeAxis(i1) timeAxis(i2)], [y y], ...
  827. 'Color', cols{k}, 'LineWidth', lw, 'Clipping', 'on');
  828. end
  829. end
  830. end
  831. % Functions for Figure 6 E, F, I
  832. function [timeAxis, meanTrace] = decode_timecurve( ...
  833. filtData, odorFields, range, fps, numTrial, numBinAvg, KNN, smoothN, durSec)
  834. numOdor = numel(odorFields);
  835. % bin within range for each odor: trials x timeBins x features
  836. blockData = cell(numOdor,1);
  837. for i = 1:numOdor
  838. tempData = filtData.(odorFields{i});
  839. temp = tempData(1:numTrial, range, :);
  840. iter = ceil(length(range)/numBinAvg);
  841. tempBinned = zeros(numTrial, iter, size(temp,3));
  842. for ii = 1:numBinAvg:length(range)
  843. b = (ii-1)/numBinAvg + 1;
  844. try
  845. tempBinned(:,b,:) = mean(temp(:,ii:ii+numBinAvg-1,:),2);
  846. catch
  847. tempBinned(:,b,:) = mean(temp(:,ii:end,:),2);
  848. end
  849. end
  850. blockData{i} = tempBinned;
  851. end
  852. timeLength = size(blockData{1},2);
  853. % units + labels
  854. allUnits = [];
  855. trueLabels = [];
  856. for i = 1:numOdor
  857. X = blockData{i};
  858. U = reshape(permute(X,[2 1 3]), [], size(X,3));
  859. allUnits = [allUnits; U];
  860. trueLabels = [trueLabels; i*ones(size(U,1),1)];
  861. end
  862. numUnits = size(allUnits,1);
  863. rowStd = std(allUnits,0,2);
  864. zidx = (rowStd==0);
  865. if any(zidx)
  866. allUnits(zidx,:) = allUnits(zidx,:) + 1e-12*randn(sum(zidx), size(allUnits,2));
  867. end
  868. % distance matrix
  869. distMat = squareform(pdist(allUnits,'correlation'));
  870. distMat(isnan(distMat)) = 1;
  871. distMat = distMat + diag(ones(numUnits,1)*10);
  872. % exclude within-odor same-trial neighbors
  873. trialBlock = ones(timeLength)*10;
  874. penalty = kron(eye(numOdor*numTrial), trialBlock);
  875. distMat = distMat + penalty*10;
  876. [~, idx] = sort(distMat,2,'ascend');
  877. nnIdx = idx(:,1:KNN);
  878. [meanTrace, ~] = compute_percent_per_odor_2( ...
  879. trueLabels, nnIdx, trueLabels, numOdor, timeLength, numTrial, smoothN);
  880. timeAxis = linspace(0, durSec, timeLength);
  881. end
  882. function [meanTrace, perOdor] = compute_percent_per_odor_2(labelVec, nnIdx, trueLabels, ...
  883. numOdor, timeLength, numTrial, smoothN)
  884. numUnits = numel(trueLabels);
  885. KNN = size(nnIdx,2);
  886. neigh = zeros(numUnits, KNN);
  887. for nn = 1:KNN
  888. neigh(:,nn) = labelVec(nnIdx(:,nn));
  889. end
  890. predLabel = mode(neigh,2);
  891. correct = (predLabel == trueLabels);
  892. perOdor = zeros(numOdor, timeLength);
  893. for oo = 1:numOdor
  894. idx = find(trueLabels == oo);
  895. tmpMat = reshape(correct(idx), timeLength, numTrial)';
  896. perOdor(oo,:) = mean(tmpMat,1);
  897. end
  898. if smoothN > 1
  899. for oo = 1:numOdor
  900. perOdor(oo,:) = filtfilt(ones(1,smoothN)/smoothN, 1, perOdor(oo,:));
  901. end
  902. end
  903. meanTrace = mean(perOdor,1);
  904. end
  905. function [xBin, accBin] = bin_timecourses(x, accSess, binSizePts)
  906. [nSess, nTime] = size(accSess);
  907. nBins = floor(nTime/binSizePts);
  908. xT = x(1:nBins*binSizePts);
  909. accT = accSess(:,1:nBins*binSizePts);
  910. accR = reshape(accT, nSess, binSizePts, nBins);
  911. accBin = squeeze(mean(accR,2,'omitnan'));
  912. xR = reshape(xT, binSizePts, nBins);
  913. xBin = mean(xR,1,'omitnan')';
  914. end
  915. function sig = bh_fdr_mask(p, q)
  916. sig = false(size(p));
  917. v = ~isnan(p);
  918. pv = p(v);
  919. if isempty(pv), return; end
  920. [ps, ord] = sort(pv);
  921. m = numel(ps);
  922. th = (1:m)'/m*q;
  923. pass = ps <= th;
  924. if any(pass)
  925. kmax = find(pass,1,'last');
  926. idxValid = find(v);
  927. sig(idxValid(ord(1:kmax))) = true;
  928. end
  929. end
  930. function plot_sig_dots(x, p_raw, sig_fdr, yBar, pThresh, pColors, markerSize)
  931. elig = sig_fdr & ~isnan(p_raw);
  932. for i = 1:numel(x)
  933. if ~elig(i), continue; end
  934. if p_raw(i) < pThresh(3)
  935. c = pColors{3};
  936. elseif p_raw(i) < pThresh(2)
  937. c = pColors{2};
  938. elseif p_raw(i) < pThresh(1)
  939. c = pColors{1};
  940. else
  941. continue
  942. end
  943. plot(x(i), yBar, 'o', ...
  944. 'MarkerFaceColor', c, ...
  945. 'MarkerEdgeColor', c, ...
  946. 'MarkerSize', markerSize);
  947. end
  948. end

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

Overview

Authors: Elizabeth H. Moss1, Doris Ling2, Cameron L. Smith3, Feiyang Deng2, Ryan Kroeger3, Jacob Reimer3, Baranidharan Raman2, Benjamin R. Arenkiel1
  1. Department of Molecular and Human Genetics, Baylor College of Medicine, Houston, TX 77030, USA
  2. Department of Biomedical Engineering, Washington University in St. Louis, St. Louis, MO 63105, USA
  3. Department of Neuroscience, Baylor College of Medicine, Houston, TX 77030, USA
Institutions: Baylor College of Medicine (United States); Washington University in St. Louis (United States)
Journal: iScience, volume 29, issue 4, article 115373
Dates: received 28 July 2025; accepted 12 March 2026; published online 16 March 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1016/j.isci.2026.115373 · PMID 41971993 · PMCID PMC13066794 · OpenAlex W7136786262
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: mouse (organism), other (organism)
Methods: Spectral & time-frequency, Statistics, Smoothing, state filtering, decompositions, Machine learning, fMRI & imaging, Single-unit activity, calcium imaging
Keywords: biological sciences, neuroscience, sensory neuroscience
Topic: Olfactory and Sensory Function Studies (Sensory Systems, Neuroscience), according to OpenAlex
Funding: NSF (1724218, 2021795); ONR (N00014-19-1-2049, N00014-21-1-2343); NIH (R00DC019505, UF1NS111692); McNair Medical Institute; NINDS (R01NS078294); NIDDK (R01DK109934); DOD (PR180451-PRMP)
Citations: not cited yet (Europe PMC); 63 references in the paper
Research resources: MATLAB RRID:SCR_001622, ScanImage RRID:SCR_014307, LabView RRID:SCR_014325

Abstract

Interpreting chemical information and translating it into ethologically relevant output is a shared challenge of olfactory systems across species, but are olfactory computations conserved across species to overcome these common challenges? To investigate this, we compared neural activity in the locust antennal lobe (AL) and mouse olfactory bulb (OB) both during and after odor presentations. We found that odors activated nearly mutually exclusive neural ensembles during odor presentations (“ON response”) and after the termination of the odor stimulus (“OFF response”). ON and OFF responses evoked by a single odor were anticorrelated with each other. Inverted OFF responses persisted long after odor termination in both AL and OB, and enhanced contrast between odors that were experienced close together in time. Together our results show how post-odor neural activity, relative to odor-evoked activity, is similar across two distinct species, revealing a conserved mechanism for enhancing contrast between odors at the neural level.

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

Repository

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

figshare 31435543

License: CC-BY-4.0
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Languages: MATLAB (13)
Size: 77 files, 13 scripts
Software Heritage: not checked
Found in: “Data and code availability”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 30 September 2026: the link answers (HTTP 200)
  • 30 September 2026: the link answers (HTTP 200)
8 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;
  • 8 scripts, each with its path and the digest of its content;
  • 1 match between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

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

Data

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

Data and code availability

• Locust electrophysiology and mouse calcium imaging data is publicly available at Figshare: https://doi.org/10.6084/m9.figshare.31435543. DOI is listed in the key resources table. • Original code used to analyze data and generate figures is publicly available at Figshare: https://doi.org/10.6084/m9.figshare.31435543. DOI is listed in the key resources table. • Any additional information required to reanalyze the data reported in this paper is available from the lead contact upon request.

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

Versions

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

Version 1, 30 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 8 authors, 3 keywords, 7 funders, 61 references, 3 RRIDs.

Cite

This paper

Moss, E. H., Ling, D., Smith, C. L., Deng, F., Kroeger, R., Reimer, J., Raman, B., & Arenkiel, B. R. (2026). Conserved post-odor dynamics in the olfactory systems of mice and locusts. iScience, 29(4), 115373. https://doi.org/10.1016/j.isci.2026.115373

BibTeX

@article{moss2026conserved,
author = {Moss, Elizabeth H. and Ling, Doris and Smith, Cameron L. and Deng, Feiyang and Kroeger, Ryan and Reimer, Jacob and Raman, Baranidharan and Arenkiel, Benjamin R.},
title = {{Conserved post-odor dynamics in the olfactory systems of mice and locusts}},
journal = {iScience},
year = {2026},
month = mar,
volume = {29},
number = {4},
pages = {115373},
publisher = {Elsevier},
issn = {2589-0042},
doi = {10.1016/j.isci.2026.115373},
url = {https://doi.org/10.1016/j.isci.2026.115373},
pmid = {41971993},
pmcid = {PMC13066794}
}

RIS

TY - JOUR
AU - Moss, Elizabeth H.
AU - Ling, Doris
AU - Smith, Cameron L.
AU - Deng, Feiyang
AU - Kroeger, Ryan
AU - Reimer, Jacob
AU - Raman, Baranidharan
AU - Arenkiel, Benjamin R.
TI - Conserved post-odor dynamics in the olfactory systems of mice and locusts
T2 - iScience
J2 - iScience
PY - 2026
DA - 2026/03/16
VL - 29
IS - 4
SP - 115373
SN - 2589-0042
PB - Elsevier
DO - 10.1016/j.isci.2026.115373
UR - https://doi.org/10.1016/j.isci.2026.115373
LA - en
ER -

CSL-JSON

{
"id": "10.1016/j.isci.2026.115373",
"type": "article-journal",
"title": "Conserved post-odor dynamics in the olfactory systems of mice and locusts",
"container-title": "iScience",
"author": [
{
"family": "Moss",
"given": "Elizabeth H."
},
{
"family": "Ling",
"given": "Doris"
},
{
"family": "Smith",
"given": "Cameron L."
},
{
"family": "Deng",
"given": "Feiyang"
},
{
"family": "Kroeger",
"given": "Ryan"
},
{
"family": "Reimer",
"given": "Jacob"
},
{
"family": "Raman",
"given": "Baranidharan"
},
{
"family": "Arenkiel",
"given": "Benjamin R."
}
],
"container-title-short": "iScience",
"volume": "29",
"issue": "4",
"page": "115373",
"DOI": "10.1016/j.isci.2026.115373",
"PMID": "41971993",
"PMCID": "PMC13066794",
"ISSN": "2589-0042",
"publisher": "Elsevier",
"URL": "https://doi.org/10.1016/j.isci.2026.115373",
"language": "en",
"issued": {
"date-parts": [
[
2026,
3,
16
]
]
}
}

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.7554/elife.107905 [code]
Adult-neurogenesis allows for representational stability and flexibility in early olfactory system.
Journal: eLife
In common: 7 references
[2] doi:10.1038/s41592-026-03023-y
Isotonic and minimally invasive optical clearing media for live cell imaging ex vivo and in vivo.
Journal: Nature methods
In common: mouse, 5 references
[3] doi:10.1126/sciadv.aee1002 [code]
Theta oscillations are an organizational unit of odor processing in the olfactory bulb.
Journal: Science advances
In common: Signal Processing Toolbox, Statistics and Machine Learning Toolbox, 2 references
[4] doi:10.1038/s41467-026-70356-9
A topographical organization in the primary olfactory cortex.
Journal: Nature communications
In common: mouse, 3 references
[5] doi:10.1016/j.isci.2026.115897 [code]
Experience and behavior modulate piriform cortex odor representation in freely moving mice.
Journal: iScience
In common: Signal Processing Toolbox, Statistics and Machine Learning Toolbox, mouse, 1 reference
[6] doi:10.1002/cne.70197
Anatomical Investigation Reveals a Second Olfactory System in Locusts.
Journal: The Journal of comparative neurology
In common: other, 2 references
[7] doi:10.1038/s41467-026-71742-z [code]
Hypercapnia dissociates neuronal and hemodynamic responses impairing neurovascular coupling and functional brain connectivity.
Journal: Nature communications
In common: Signal Processing Toolbox, Statistics and Machine Learning Toolbox, mouse, 1 reference
[8] doi:10.7554/elife.110685 [code]
Sensory adaptation and pupil-linked arousal support flexible evidence accumulation during perceptual decision making.
Journal: eLife
In common: Signal Processing Toolbox, Statistics and Machine Learning Toolbox, 1 reference
[9] doi:10.1038/s41467-026-71356-5 [code]
Generalization of fear learning is shaped by inhibitory sensory processing in mice.
Journal: Nature communications
In common: mouse, 2 references
[10] doi:10.1126/sciadv.aeg3535 [code]
Undoing of firing rate adaptation enables invariant population codes.
Journal: Science advances
In common: 2 references

Contribute

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

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

Request its removal

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

Discussion, reproductions, activity

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

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

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