technical_validation.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. % Note: This script is meant to be run as a whole;
  2. % assigning the directories only works if the script is run, not if
  3. % individual lines are executed.
  4. % If you want to run it line by line, set the directories manually
  5. % (e.g. by setting codeDir = pwd if you are currently in the directory
  6. % that contains this file).
  7. % On a late 2013 iMac with a 3.4 GHz Quad-Core Intel Core i5 this
  8. % takes around 1.5 minutes to run.
  9. clc;
  10. % Specify directories
  11. codeDir = fileparts(mfilename('fullpath'));
  12. mainDir = codeDir(1:find(codeDir==filesep,1,'last'));
  13. dataDir = [mainDir,'data/'];
  14. % Add all folders with code and data to the path
  15. addpath(genpath(mainDir));
  16. % Specify and initiate some variables
  17. experiments = {'MSTm', 'MSTt', 'MSTn'};
  18. param2read = {'/event_value', '/event_time'};
  19. nCells = 172;
  20. nExperiments = length(experiments);
  21. %% Questions to be answered
  22. % Are there negative spike times?
  23. negative_spiketime_counter = 0;
  24. % Are the number of timestamps for every event identical to the number of event values?
  25. unequeal_value_times_counter = 0;
  26. % Are trial end times are always later than trial start times, i.e., do all trials have a positive duration?
  27. trial_durations = cell(nCells,nExperiments);
  28. % What is the range and average of trial durations for hit trials (from trial start to trial outcome assignment?)
  29. hit_trial_durations = cell(nCells,nExperiments);
  30. % Does the average firing rate per trial differ between Mapping and Tuning?
  31. % What is the average per trial firing rate? (for all trials and for trials >500ms)
  32. avg_firing_rates = nan(nCells,nExperiments);
  33. avg_firing_rates_500 = nan(nCells,nExperiments);
  34. % What is the highest per trial firing rate? (for all trials >500ms)
  35. highest_firing_rate = 0;
  36. highest_firing_rate_trial = nan;
  37. highest_firing_rate_datafile = '';
  38. % How many trials lasting longer than 500ms have 0 spikes?
  39. zero_spike_trials = 0;
  40. trials_longer_500 = 0;
  41. % What is the longest interspike interval?
  42. ISIs = cell(nCells,nExperiments);
  43. %% Read in and loop through data
  44. % Read in meta data
  45. meta_data = readtable([dataDir,'meta_data.txt']);
  46. % Loop through all recordings
  47. % and all units within each recording for plausibility checks
  48. cell_idx = 0;
  49. for rec = 1:size(meta_data,1) % loop through all recordings
  50. for exp = 1:length(experiments) % loop through the three experiments
  51. % uncomment this line to get output of which file is currently
  52. % being loaded
  53. %fprintf("Loading experiment file %s\n", ['amm-',experiments{exp},'-',meta_data.monkey{rec},'-',strip(meta_data.session_number{rec},'both','"'),'-',meta_data.daily_count{rec},'-task.h5']);
  54. % get number of trials; will be 0 for nonexistant files
  55. numTrials = eval(['meta_data.Exp_',experiments{exp},'_tt(',num2str(rec),')']);
  56. % check if there is a file for this experiment in this recording
  57. if numTrials > 0
  58. % change into the directory of the respective experiment
  59. cd([dataDir,experiments{exp}]);
  60. % specify file name
  61. datafile_h5 = ['amm-',experiments{exp},'-',meta_data.monkey{rec},...
  62. '-',strip(meta_data.session_number{rec},'both','"'),'-',...
  63. meta_data.daily_count{rec},'-task.h5'];
  64. % get information about HDF5 file
  65. file_info = h5info(datafile_h5, '/event_value');
  66. % get names of events
  67. events2read = {file_info.Datasets.Name};
  68. % extract values and times of each event
  69. [event_value, event_time] = h5_extract(datafile_h5, param2read, events2read);
  70. % check that the number of trial starts, end, types, and
  71. % outcomes is the same
  72. if length(event_value.TRIAL_start) ~= length(event_value.TRIAL_end) || ...
  73. length(event_value.TRIAL_start) ~= length(event_value.TRIAL_type) || ...
  74. length(event_value.TRIAL_start) ~= length(event_value.TRIAL_outcome)
  75. warning("Different number of Trial starts, ends, types, and/or outomes in recordings %s",datafile_h5);
  76. end
  77. event_names = fieldnames(event_value);
  78. %% Validation of stimulus/behavioral data
  79. % check that number of event values == number of event times
  80. for fn = 1:length(event_names)
  81. if length(eval(['event_time.',event_names{fn}]))~=length(eval(['event_value.',event_names{fn}]))
  82. warning('Number of event times and event values did not agree for %s in file %s\n',event_names{fn},datafile_h5);
  83. unequeal_value_times_counter = unequeal_value_times_counter+1;
  84. end
  85. end
  86. % get trial durations for this recording and this experiment
  87. trial_durations{rec,exp} = (event_time.TRIAL_end - event_time.TRIAL_start)/1000; % in milliseconds
  88. % get time from trial start until reward for hit trials
  89. for tr = 1:numTrials
  90. if strcmp(event_value.TRIAL_outcome{tr},'hit')
  91. tmp = (event_time.TRIAL_outcome(tr)-event_time.TRIAL_start(tr))/1000; % in ms
  92. if tmp < 3000 || tmp > 4750
  93. warning("Short or long hit trial duration on trial %d of %s\n",tr,datafile_h5);
  94. end
  95. hit_trial_durations{rec,exp} = cat(2,hit_trial_durations{rec,exp},tmp);
  96. end
  97. end
  98. %% Validation of neural data
  99. for u = 1:3 % loop through up to 3 offline sorted units per recording
  100. unit = eval(['meta_data.offline_units_',num2str(u),'(',num2str(rec),')']);
  101. % this will be NaN if no units have been offline sorted
  102. spike_trials = str2num(strip(eval(['meta_data.Exp_',experiments{exp},'_st_',num2str(u),'{',num2str(rec),'}']),"'")); %
  103. if ~isnan(unit) && length(spike_trials) > 1% if there is a unit
  104. if exp==1
  105. cell_idx = cell_idx+1; % increase cell counter only if it's the first experiment
  106. end
  107. % get the times of all spikes that were sorted to the current unit
  108. spiketimes = event_time.SPIKE_channelUnit(event_value.SPIKE_channelUnit==unit);
  109. % Check for negative spike times
  110. if sum(spiketimes<0)>0
  111. fprintf("Negative spike times in datafile %s", datafile_h5);
  112. negative_spiketime_counter = negative_spiketime_counter + sum(spiketimes<0);
  113. end
  114. % get per trial firing rates
  115. tmp_firing_rates = [];
  116. tmp_firing_rates_500 = [];
  117. tmp_ISIs = [];
  118. for tr = spike_trials
  119. trialStart = event_time.TRIAL_start(tr);
  120. trialEnd = event_time.TRIAL_end(tr);
  121. trialDuration = (trialEnd-trialStart)/1000000; % in seconds
  122. numSpikes = sum(spiketimes>trialStart & spiketimes < trialEnd);
  123. tmp_ISIs = cat(1,tmp_ISIs,diff(spiketimes(spiketimes>trialStart & spiketimes < trialEnd))/1000);
  124. if trialDuration > 0.5
  125. trials_longer_500 = trials_longer_500+1;
  126. if numSpikes == 0
  127. zero_spike_trials = zero_spike_trials+1;
  128. end
  129. if numSpikes/trialDuration > highest_firing_rate
  130. highest_firing_rate = numSpikes/trialDuration;
  131. highest_firing_rate_trial = tr;
  132. highest_firing_rate_datafile = [datafile_h5,'-',num2str(unit)];
  133. end
  134. end
  135. tmp_firing_rates = cat(2,tmp_firing_rates,numSpikes/trialDuration);
  136. if trialDuration > 0.5
  137. tmp_firing_rates_500 = cat(2,tmp_firing_rates_500,numSpikes/trialDuration);
  138. end
  139. end
  140. avg_firing_rates(rec,exp) = mean(tmp_firing_rates);
  141. avg_firing_rates_500(rec,exp) = mean(tmp_firing_rates_500);
  142. % interspike intervals
  143. ISIs{cell_idx, exp} = tmp_ISIs; % in ms
  144. end % end of ~isnan(unit)
  145. end % end of u-loop
  146. end % end of if statement that checks whether file exists
  147. end % end of exp-loop
  148. end % end of rec-loop
  149. % all_firing_rates = [trial_firing_rates{:}];
  150. all_trial_durations = vertcat(trial_durations{:});
  151. all_hit_trial_durations = horzcat(hit_trial_durations{:});
  152. %% Report results
  153. fprintf('\n\nOverall Results\n');
  154. fprintf('===============\n\n');
  155. fprintf('General Points\n');
  156. fprintf('---------------\n');
  157. if negative_spiketime_counter == 0
  158. fprintf('There are no negative spike times.\n');
  159. else
  160. fprintf('There are %d spikes with negative spiketimes.\n', negative_spiketime_counter);
  161. end
  162. if sum(all_trial_durations<0) == 0
  163. fprintf('Trial end times are always later than trial start times, i.e., all trials have a positive duration.\n')
  164. else
  165. fprintf('%d trials have a negative duration.\n', sum(all_trial_durations<0));
  166. end
  167. if unequeal_value_times_counter == 0
  168. fprintf('The number of timestamps for every event is identical to the number of event values.\n');
  169. else
  170. fprintf('For %d cases the number of event values did not agree with the number of timestamps.\n',unequeal_value_times_counter);
  171. end
  172. fprintf('\nTrial duration\n');
  173. fprintf('---------------\n');
  174. fprintf("For hit trials, the time from trial start to reward delivery ranged from %.2f to %.2f ms.\n", min(all_hit_trial_durations),max(all_hit_trial_durations));
  175. fprintf("The average time from trial start to reward delivery was %.2f ms (SD: %.2f ms)\n",mean(all_hit_trial_durations), std(all_hit_trial_durations))
  176. fprintf('\nFiring Rate\n');
  177. fprintf('------------\n')
  178. fprintf('The average firing rate in the Mapping experiment was %.2f, compared to %.2f for Tuning and %.2f for RC\n',mean(avg_firing_rates,'omitnan'));
  179. [H,P,CI,STATS] = ttest(avg_firing_rates(:,2), avg_firing_rates(:,1));
  180. if P < .05
  181. fprintf("The difference between Mapping and Tuning was significant, ");
  182. else
  183. fprintf("The difference between Mapping and Tuning was not significant, ");
  184. end
  185. fprintf("t(%d) = %.2f, p = %.4f\n", STATS.df, STATS.tstat, P);
  186. fprintf('Across all recordings and all experiments, %d trials (%.2f%%) that last at least 500ms have 0 spikes.\n', zero_spike_trials, 100*zero_spike_trials/trials_longer_500);
  187. fprintf('Across all units, all experiments, and all trials that last at least 500ms,\n');
  188. fprintf('the average firing rate (calculated per trial) is %.2f spikes/s (SD: %.2f),\n', mean(avg_firing_rates_500(:),'omitnan'), std(avg_firing_rates_500(:),'omitnan'));
  189. fprintf('and the highest firing rate calculated for one individual trial is %.2f spikes/s on trial %d of cell %s.\n',highest_firing_rate,highest_firing_rate_trial,highest_firing_rate_datafile);
  190. all_ISIs = vertcat(ISIs{:});
  191. fprintf('\nInterspike Intervals\n');
  192. fprintf('---------------------\n')
  193. fprintf('The longest interspike interval is %.0f milliseconds\n',max(all_ISIs));
  194. fprintf('Across all recordings and experiments, there were %d interspike intervals (%.4f%%) that were longer than 4s.\n',sum(all_ISIs>=4000), 100*sum(all_ISIs>=4000)/length(all_ISIs));
  195. fprintf('Across all recordings and experiments, there were %d interspike intervals (%.2f%%) that were longer than 2s.\n',sum(all_ISIs>2000), 100*sum(all_ISIs>2000)/length(all_ISIs));
  196. fprintf('%d interspike intervals (%.2f%%) were shorter than 1ms.\n',sum(all_ISIs<=1), 100*sum(all_ISIs<=1)/length(all_ISIs));