PageRenderTime 27ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/MatlabCode/branches/Greg's Branch/Analysis/nex2stro/nex2stro.m

http://horwitzlab.googlecode.com/
MATLAB | 481 lines | 316 code | 71 blank | 94 comment | 41 complexity | 7981a80205a1c6b8faf587e93a7cad7d MD5 | raw file
Possible License(s): GPL-2.0, AGPL-1.0
  1. function stro = nex2stro(fileName)
  2. %
  3. % EXAMPLE: stro = nex2stro(fileName);
  4. %
  5. %This engine will take nex files and return a (S)ummary
  6. %(T)rials (R)aster (O)ther (stro) structure. 'filename' should be the
  7. %complete path to the nex file.
  8. %
  9. % cah 1/21/08 started development...
  10. % gdlh/cah 2/14/08 replaced global variable architecture with nested
  11. % functions.
  12. %start the wait dialog and, initialize all the appropriate structures,
  13. %then open a .nex file if no input argument was given.
  14. waitdlg = waitbar(0, 'Opening a .nex file');
  15. [stro, ecodes, spikes, anlg, waves, events] = setupStructures();
  16. if (nargin == 0 || isempty(fileName))
  17. [fname, pathname] = uigetfile('*.nex', 'Select a NeuroExplorer file');
  18. stro.sum.fileName = strcat(pathname, fname);
  19. else
  20. stro.sum.fileName = fileName;
  21. end
  22. % Unpack the .nex file. No arguments are needed b/c all the local
  23. %variables are accessible to the nested functions.
  24. unpackNexFile();
  25. cleanupAnalogSignals(); %determine if the start times are identical for each channel
  26. %determine which paradigm file to use, then eval the appropriate header
  27. %file. Be sure to return all the appropriate variables (for scoping
  28. %purposes). the feval statement will crash if one of the outputs is
  29. %undefined in paradigmFile
  30. paradigmID = dat2num(ecodes, 8999, 4000, 'int', 0);
  31. stro.sum.paradigmID = paradigmID{1}(1);
  32. paradigmFile = paradigmLibrary(stro.sum.paradigmID);
  33. [C, trialParamsHeader, exptParamsHeader, additionalRasterFields, badLogic, otherInstructions] = feval(paradigmFile);
  34. %Fill up some of the stro.sum contents
  35. getExperimentalParams; %iterates over the exptParamsHeader reconstructing those elements
  36. stro.sum.rasterCells = makeRasterHeader(additionalRasterFields); %defined in the 'paradigmFile'.
  37. stro.sum.trialFields = trialParamsHeader; %defined in the 'paradigmFile'
  38. stro.sum.otherFields = otherInstructions;
  39. stro.sum.analog.sigid = anlg.names;
  40. stro.sum.analog.storeRates = anlg.ADFrequency;
  41. stro.sum.analog.ADtoMV = anlg.ADtoMV;
  42. stro.sum.waves.wf_id = waves.names;
  43. stro.sum.waves.storeRates = waves.WFrequency;
  44. %iterate by trials filling up the .raster and .trial fields of stro.
  45. nTrials = length(events.start);
  46. stro.ras = cell(nTrials, length(stro.sum.rasterCells));
  47. stro.trial = nan(nTrials, size(trialParamsHeader, 2));
  48. stro.sum.absTrialNum = nan(nTrials,1);
  49. goodTrial = 0; %the counter of good trials
  50. for b = 1:nTrials;
  51. trialCodes = checkTrial(events.start(b), events.stop(b), badLogic);
  52. if trialCodes;
  53. goodTrial = goodTrial + 1;
  54. rasterByTrial(events.start(b), events.stop(b), goodTrial, b, additionalRasterFields, trialCodes);
  55. indexByTrial(trialCodes, trialParamsHeader, goodTrial, C.VALOFFSET);
  56. otherByTrial(trialCodes, otherInstructions, goodTrial, C);
  57. stro.sum.absTrialNum(goodTrial) = b; %a mapping b/w actual and good trial nums
  58. end
  59. if ~(rem(b, 3)) %update the waitdlg every three trials
  60. waitbar(b./nTrials, waitdlg, ' ...Processing trials');
  61. end
  62. end
  63. %now cleanup the bad trials (empty rows) of stro.trial, stro.ras, and
  64. %stro.sum.absTrialNum
  65. waitbar(1, waitdlg, ' ... cleaning up');
  66. stro.ras((goodTrial+1):end, :) = [];
  67. stro.trial((goodTrial+1):end, :) = [];
  68. stro.sum.absTrialNum(goodTrial+1:end) = [];
  69. %close up the wait dialog
  70. close(waitdlg);
  71. %****************************************%
  72. % END OF MAIN LOOP. SUBFUNCTIONS BELOW %
  73. %****************************************%
  74. %%
  75. function [stro, ecodes, spikes, anlg, waves, events] = setupStructures();
  76. %this function is necessary because later on we'll test the length of
  77. %spikes.names and anlg.names to iterate over neuronal and anlg data. If
  78. %we decide not to record from an anlg channel, setting the field
  79. %anlg.name to an empty vector (i.e., []) will allow this program to
  80. %function properly. ditto for spike channels. In addition, these
  81. %variables are global, so previous definitions of the variables could
  82. %intrude here.
  83. ecodes = [];
  84. spikes = struct('names', {{}}, ...
  85. 'timestamps', {{}});
  86. anlg = struct('names', {{}}, ...
  87. 'ADFrequency', {{}}, ...
  88. 'ADtoMV', {{}}, ...
  89. 'fragStarts', {{}}, ...
  90. 'nPtsPerFrag', {{}}, ...
  91. 'data', {{}});
  92. waves = struct('names', {{}}, ...
  93. 'WFrequency', {{}}, ...
  94. 'timestamps', {{}}, ...
  95. 'waveforms', {{}});
  96. events = struct();
  97. stro = struct('sum', struct(...
  98. 'fileName', [], ...
  99. 'date', date, ...
  100. 'paradigmID', [], ...
  101. 'waves', struct(...
  102. 'wf_id', [], ...
  103. 'storeRates', []), ...
  104. 'analog', struct( ...
  105. 'sigid', [], ...
  106. 'storeRates', [], ...
  107. 'ADtoMV', []),...
  108. 'trialFields', {{}}, ...
  109. 'rasterCells', {{}}, ...
  110. 'otherFields', {{}}, ...
  111. 'exptParams', struct(), ...
  112. 'absTrialNum', [], ...
  113. 'concat', []), ...
  114. 'trial', {{}}, ...
  115. 'ras', {{}}, ...
  116. 'other', {{}});
  117. end
  118. %%
  119. function unpackNexFile()
  120. %open differently for mac and pc:
  121. if isunix
  122. fid = fopen(stro.sum.fileName, 'r', 'l');
  123. elseif ispc
  124. fid = fopen(stro.sum.fileName, 'r');
  125. end
  126. if(fid == -1)
  127. error('Unable to open file')
  128. return
  129. end
  130. %open the file and make sure that it's a neuroexplorer file
  131. magic = fread(fid, 1, 'int32');
  132. if magic ~= 827868494
  133. error 'The file is not a valid .nex file'
  134. end
  135. %read in the header info
  136. version = fread(fid, 1, 'int32');
  137. comment = deblank(char(fread(fid, 256, 'char')'));
  138. freq = fread(fid, 1, 'double'); %acquisition rate for things other than continuous variables
  139. tbeg = fread(fid, 1, 'int32') ./ freq;
  140. tend = fread(fid, 1, 'int32') ./ freq;
  141. nvar = fread(fid, 1, 'int32');
  142. fseek(fid, 260, 'cof'); % skip location of next header and padding
  143. %now read the file putting the appropriate 'type' of Nex info in either
  144. %the ecodes, spikes, or analog vectors. For now ignore the other 'types'
  145. neuronCount = 0;
  146. contSigCount = 0;
  147. waveCount = 0;
  148. for i=1:nvar
  149. type = fread(fid, 1, 'int32');
  150. varVersion = fread(fid, 1, 'int32');
  151. name = deblank(char(fread(fid, [1 64], 'char')));
  152. offset = fread(fid, 1, 'int32');
  153. n = fread(fid, 1, 'int32');
  154. dummy = fread(fid, 32, 'char'); %sikps what's commented out below
  155. %WireNumber = fread(fid, 1, 'int32');
  156. %UnitNumber = fread(fid, 1, 'int32');
  157. %Gain = fread(fid, 1, 'int32');
  158. %Filter = fread(fid, 1, 'int32');
  159. %XPos = fread(fid, 1, 'double');
  160. %YPos = fread(fid, 1, 'double');
  161. WFrequency = fread(fid, 1, 'double'); % wf sampling fr.
  162. ADtoMV = fread(fid, 1, 'double'); % coeff to convert from AD values to Millivolts.
  163. NPointsWave = fread(fid, 1, 'int32'); % number of points in each wave
  164. NMarkers = fread(fid, 1, 'int32'); % how many values are associated with each marker
  165. MarkerLength = fread(fid, 1, 'int32'); % how many characters are in each marker value
  166. MVOfffset = fread(fid, 1, 'double'); % coeff to shift AD values in Millivolts: mv = raw*ADtoMV+MVOfffset
  167. filePosition = ftell(fid);
  168. switch type
  169. case 0 % neuron
  170. waitbar(0, waitdlg,' ...adding spike data');
  171. neuronCount = neuronCount+1;
  172. spikes.names(neuronCount) = {name};
  173. fseek(fid, offset, 'bof');
  174. spikes.timestamps{neuronCount} = fread(fid, [n 1], 'int32')./freq;
  175. fseek(fid, filePosition, 'bof');
  176. case 1 % event (start/stop)
  177. waitbar(0, waitdlg, sprintf(' ...adding %s times', name));
  178. fseek(fid, offset, 'bof');
  179. ts = fread(fid, [n 1], 'int32')./freq;
  180. events = setfield(events, lower(name), ts);
  181. fseek(fid, filePosition, 'bof');
  182. case 2 % interval
  183. %ignore this type for now
  184. fprintf('Ignoring the ''interval'' type \n');
  185. case 3 % waveform
  186. waveCount = waveCount+1;
  187. waves.names(waveCount) = {name};
  188. waves.WFrequency{waveCount} = WFrequency;
  189. fseek(fid, offset, 'bof');
  190. waves.timestamps{waveCount} = fread(fid, [n 1], 'int32')./freq;
  191. waves.waveforms{waveCount} = (fread(fid, [NPointsWave n], 'int16').*ADtoMV + MVOfffset)'; %transpose to make trials go down columns and time accross rows
  192. fseek(fid, filePosition, 'bof');
  193. case 4 % population vector
  194. %ignore this type for now
  195. fprintf('Ignoring the ''population vector'' type \n');
  196. case 5 % continuous variable (i.e. eye signals)
  197. waitbar(0, waitdlg, sprintf(' ...adding analog data: %s', name));
  198. contSigCount = contSigCount+1;
  199. anlg.names(contSigCount) = {name};
  200. anlg.ADtoMV(contSigCount) = {ADtoMV};
  201. anlg.ADFrequency{contSigCount} = WFrequency;
  202. fseek(fid, offset, 'bof');
  203. % get the start times of each analog signal snippet
  204. anlg.fragStarts{contSigCount} = fread(fid, [n 1], 'int32')./freq;
  205. %get the number of points sammpled during each of the snippets
  206. nFrag = fread(fid, [n 1], 'int32');
  207. nFrag(n+1) = NPointsWave;
  208. anlg.nPtsPerFrag{contSigCount} = nFrag;
  209. %now bring in the AtoD data for the entire recording
  210. anlg.data{contSigCount} = fread(fid, [NPointsWave 1], 'int16').*ADtoMV + MVOfffset;
  211. fseek(fid, filePosition, 'bof');
  212. case 6 % marker (i.e. ecodes)
  213. waitbar(0, waitdlg, ' ...adding ecodes');
  214. fseek(fid, offset, 'bof');
  215. timeStamps = fread(fid, [n 1], 'int32') ./ freq;
  216. %check to make sure that there are actually markers to
  217. %retreive before trying
  218. if (NMarkers ~= 1)
  219. error('bad number of markers in file');
  220. end
  221. %check to make sure that you're about to read in strobed
  222. %codes
  223. mname = char(fread(fid, [1 64], 'char'));
  224. if ~strncmp('DIO', mname, 3)
  225. error('unknown marker name')
  226. end
  227. %now collect the marker (ecode) data. convert them to
  228. %numeric representations and return them along with timestamps
  229. markers = deblank(fscanf(fid, '%c', [MarkerLength n])'); %transpose, then take off the trailing blank
  230. markers = markers-48; %convert to numeric
  231. powersOfTen = 10.^[(MarkerLength-2) : -1 : 0]';
  232. ecodes = [ecodes; [timeStamps, markers*powersOfTen]];
  233. %return to where you started in the file!
  234. fseek(fid, filePosition, 'bof');
  235. otherwise
  236. fprintf('unknown variable type <%s> \n', type);
  237. end
  238. dummy = fread(fid, 60, 'char'); %skip some junk to get to the next block of data
  239. end
  240. fclose(fid);
  241. end
  242. %%
  243. function trialCodes = checkTrial(trialStart, trialStop, badLogic)
  244. %pull out the relavent ecodes
  245. ind = (ecodes(:,1) >= trialStart) & (ecodes(:,1) <= trialStop);
  246. trialCodes = ecodes(ind,:);
  247. %test the ecodes to determine if the trial is good or bad. 'badLogic'
  248. %should be defined in the paradigm header file.
  249. bad = eval(badLogic);
  250. %return an empty vector if it's a bad trial
  251. if bad
  252. trialCodes = [];
  253. end
  254. end
  255. %%
  256. function rasterByTrial(start, stop, gtCounter, totTrialCounter, additionalRasterFields, trialCodes)
  257. %fill up the rows of stro.ras trial by trial. treat sike data, anlg
  258. %sigs, and other independently. Format is:
  259. % <neuron 1> <neuron 2> ... <waveForm1>... <anlg 1> <anlg 2> ... <atime> <other>
  260. %each neuron's spikes will go into it's own column:
  261. if ~isempty(spikes.names)
  262. for ind = 1:length(spikes.names);
  263. stro.ras{gtCounter, ind} = spikes.timestamps{ind}((spikes.timestamps{ind} >= start) & (spikes.timestamps{ind} <= stop));
  264. end
  265. else
  266. ind = 0;
  267. end
  268. %iterate over the waveform data (if available):
  269. for a = 1:length(waves.names);
  270. ind = ind + 1;
  271. whichSpikes = ((waves.timestamps{a} >= start) & (waves.timestamps{a} <= stop));
  272. stro.ras{gtCounter, ind} = waves.waveforms{a}(whichSpikes, :);
  273. end
  274. %iterate over anlg channels
  275. for a = 1:length(anlg.names);
  276. ind = ind + 1;
  277. startInd = anlg.nPtsPerFrag{a}(totTrialCounter) + 1;
  278. stopInd = anlg.nPtsPerFrag{a}(totTrialCounter+1);
  279. stro.ras{gtCounter, ind} = anlg.data{a}(startInd:stopInd);
  280. end
  281. %explicitly add the anlg start time. All the analog channels should
  282. %have the same number of start times, so indexing this way is o.k.
  283. if ~isempty(anlg.fragStarts)
  284. ind = ind + 1;
  285. stro.ras{gtCounter, ind} = anlg.fragStarts{1}(totTrialCounter);
  286. end
  287. %now look for additional elements as specified by
  288. %additionalRasterFields (which is defined in the 'paradigmFile'
  289. for a = 1:size(additionalRasterFields, 2);
  290. ind = ind + 1;
  291. nums = dat2num(trialCodes(:,2), [additionalRasterFields{4, a}], C.VALOFFSET, additionalRasterFields{2, a}, additionalRasterFields{3, a});
  292. stro.ras{gtCounter, ind} = nums{1};
  293. end
  294. end
  295. %%
  296. function indexByTrial(trialCodes, trialParamsHeader, goodTrial, offset)
  297. %deal with the ints, longs, and doubles
  298. types = {'int', 'long', 'float', 'double'};
  299. for a = 1:length(types);
  300. typeInd = strmatch(types{a}, strvcat(trialParamsHeader{2,:}));
  301. if typeInd
  302. nums = dat2num(trialCodes(:,2), [trialParamsHeader{4, typeInd}], offset, types{a}, 0);
  303. stro.trial(goodTrial, typeInd) = deal([nums{:}]);
  304. end
  305. end
  306. %now deal with the 'time' variables
  307. timeInd = strmatch('time', strvcat(trialParamsHeader{2,:}));
  308. timeInd_code = [trialParamsHeader{4, timeInd}];
  309. for a = 1:length(timeInd)
  310. time = trialCodes((trialCodes(:,2) == timeInd_code(a)), 1); %there will be an error if numel(time) > 1
  311. if ~isempty(time)
  312. stro.trial(goodTrial, timeInd(a)) = time;
  313. end
  314. end
  315. end
  316. %%
  317. function otherByTrial(trialCodes, otherInstructions, gtCounter, codeStruct)
  318. ind = 0;
  319. for a = 1:length(otherInstructions);
  320. ind = ind+1;
  321. stro.other{gtCounter, a} = feval(otherInstructions{2,a}, trialCodes, codeStruct);
  322. end
  323. end
  324. %%
  325. function rasterCells = makeRasterHeader(additionalRasterFields)
  326. %start with the spike data then add the anlg data. this means that
  327. %stro.raster will be composed of columns:
  328. % <neuron 1> <neuron 2> ... <neuron 3> <anlg 1> <anlg 2> ... <anlg 3> <atime> <other>
  329. rasterCells = {};
  330. if ~isempty(spikes.names)
  331. for ind = 1:length(spikes.names)
  332. rasterCells{ind} = spikes.names{ind};
  333. end
  334. else
  335. ind = 0; %incase there isn't any spike data
  336. end
  337. %iterate over waveforms (if they exist)
  338. for a = 1:length(waves.names);
  339. ind = ind + 1;
  340. rasterCells{ind} = waves.names{a};
  341. end
  342. %now add the analog channels
  343. for a = 1:length(anlg.names)
  344. ind = ind + 1;
  345. rasterCells{ind} = anlg.names{a};
  346. end
  347. %explictly add the 'anlgStart' field
  348. if ~isempty(anlg.fragStarts)
  349. ind = ind + 1;
  350. rasterCells{ind} = 'anlgStartTime';
  351. end
  352. %now add the additional raster headers (from the paradigm header file)
  353. for a = 1:size(additionalRasterFields, 2)
  354. ind = ind + 1;
  355. rasterCells{ind} = additionalRasterFields{1,a};
  356. end
  357. end
  358. %%
  359. function getExperimentalParams()
  360. %deal with the entries into the stro.sum.exptParams one by one.
  361. for a = 1:size(exptParamsHeader, 2);
  362. nums = dat2num(ecodes, [exptParamsHeader{4, a}], C.VALOFFSET, exptParamsHeader{2, a}, exptParamsHeader{3, a});
  363. stro.sum.exptParams = setfield(stro.sum.exptParams, exptParamsHeader{1, a}, nums{1});
  364. end
  365. end
  366. %%
  367. function cleanupAnalogSignals()
  368. %check to make sure that the number of analog fragments is the same for
  369. %each channel. ditto for the number of AtoD points per fragment
  370. %Ensure an equal number of starts and stops (i.e. check to
  371. %make sure that data acquisition was not arrested part way through a
  372. %trial)
  373. excessTrial = (length(events.start) - length(events.stop));
  374. if (excessTrial == 1)
  375. events.start(end) = [];
  376. fprintf(' Unequal number of STARTs and STOPs. Deleted the terminal START.\n');
  377. elseif (excessTrial == -1 && (events.stop(1) < events.start(1)))
  378. events.stop(1) = [];
  379. fprintf(' Unequal number of STARTs and STOPs. Deleted the initial STOP.\n');
  380. elseif (excessTrial ~= 0)
  381. error(' Unequal number of STARTS and STOPS.');
  382. end
  383. %now determine if there are the same number of trial starts/stops
  384. %as there are anlg starts/stops. delete any anlg data that occurs
  385. %before the first trial.
  386. if isempty(anlg.fragStarts)
  387. return
  388. end
  389. err = abs(events.start(1) - anlg.fragStarts{1});
  390. startIdx = find(err == min(err));
  391. for a = 1:length(anlg.names)
  392. anlg.fragStarts{a}(1:(startIdx-1)) = [];
  393. anlg.nPtsPerFrag{a}(1:(startIdx-1)) = [];
  394. end
  395. if numel(anlg.names) > 1
  396. if ~(isequal(anlg.fragStarts{1:end}))
  397. error('frag starts are unequal')
  398. end
  399. if ~(isequal(anlg.nPtsPerFrag{1:end}))
  400. error('points per frag are unequal')
  401. end
  402. end
  403. end
  404. end