openNEVTracking.m 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819
  1. function varargout = openNEV(varargin)
  2. % openNEV
  3. %
  4. % Opens an .nev file for reading, returns all file information in a NEV
  5. % structure. Works with File Spec 2.1 & 2.2 & 2.3.
  6. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  7. % Use OUTPUT = openNEV(fname, 'noread', 'report', 'noparse', 'nowarning',
  8. % 'nosave', 'nomat', 'uV', 'overwrite').
  9. %
  10. % NOTE: All input arguments are optional. Input arguments may be in any order.
  11. %
  12. % fname: Name of the file to be opened. If the fname is omitted
  13. % the user will be prompted to select a file using an open
  14. % file user interface.
  15. % DEFAULT: Will open Open File UI.
  16. %
  17. % 'noread': Will not read the spike waveforms if user passes this argument.
  18. % DEFAULT: will read spike waveform.
  19. %
  20. % 'report': Will show a summary report if user passes this argument.
  21. % DEFAULT: will not show report.
  22. %
  23. % 'parse': The code will not parse the experimental parameters in digital I/O.
  24. % See below for guidelines on how to format your parameters.
  25. % DEFAULT: will not parse the parameters.
  26. %
  27. % 'nowarning': The code will not give a warning if there is an error in
  28. % parsing.
  29. % DEFAULT: will give warning message.
  30. %
  31. % 'nosave': The code will not save a copy of the NEV structure as a
  32. % MAT file. By default the code will save a copy in the same
  33. % folder as the NEV file for easy future access.
  34. % DEFAULT: will save the MAT file.
  35. %
  36. % 'nomat': Will not look for a MAT file. This option will force
  37. % openNEV to open a NEV file instead of any available MAT
  38. % files.
  39. % DEFAULT: will load the MAT file if available.
  40. %
  41. % 'uV': Will read the spike waveforms in unit of uV instead of
  42. % raw values. Note that this conversion may lead to loss of
  43. % information (e.g. 15/4 = 4) since the waveforms type will
  44. % stay in int16. It's recommended to read raw spike
  45. % waveforms and then perform the conversion at a later
  46. % time.
  47. % DEFAULT: will read waveform information in raw.
  48. %
  49. % '8bits': Indicates that 8 bits on the digital IO port was used
  50. % instead of 16 bits.
  51. % DEFAULT: will assumes that 16 bits of digital IO were used.
  52. %
  53. % 't:': Indicats the time window of the NEV file to be read. For
  54. % example, if t: is set to 2 (i.e. 't:0.6')
  55. % then only the first 2 seconds of the file is to be read. If set
  56. % to 2-50 (i.e. 't:2:50) then the time between 2 seconds
  57. % and 50 seconds will be read.
  58. % DEFAULT: the entire file will be read if 't:xx:xx' is not
  59. % passed to the function.
  60. %
  61. % 'overwrite': If MATLAB loads a NEV file using 'nomat' and a MAT file
  62. % already exists, by default it will prompt the user to
  63. % allow for overwriting the old MAT. Passing the
  64. % 'overwrite' flag will automatically overwrite the newly
  65. % opened NEV file ont the old MAT file.
  66. % DEFAULT: will ask the user whether to overwrite the old
  67. % MAT.
  68. %
  69. % OUTPUT: Contains the NEV structure.
  70. %
  71. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  72. % USAGE EXAMPLE:
  73. %
  74. % openNEV('report','read');
  75. %
  76. % In the example above, the file dialogue will prompt for a file. A
  77. % report of the file contents will be shown. The digital data will not be
  78. % parsed. The data needs to be in the proper format (refer below). The
  79. % spike waveforms are in raw units and not in uV.
  80. %
  81. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  82. % DIGITAL PARAMETERS/MARKERS FORMAT:
  83. %
  84. % In order for this function to parse your experimental parameters they
  85. % need to be in the following format:
  86. %
  87. % *ParamLabel:Parameter1=value1;Parameter2=value2;Parameter3=value3;#
  88. %
  89. % TWO EXAMPLES:
  90. % *ExpParameter:Intensity=1.02;Duration=400;Trials=1;PageSegment=14;#
  91. %
  92. % *Stimulation:StimCount=5;Duration=10;#
  93. %
  94. % In the first example, the parameter is of type "ExpParameter". The
  95. % parameters are, "Intensity, Duration, Trials, and PageSement." The
  96. % values of those parameters are, "1.02, 400, 1, and 14," respectively.
  97. % The second example is of type "Stimulation". The name of the parameters
  98. % are "StimCount" and "Duration" and the values are "5" and "10"
  99. % respectively.
  100. % -----------------------------------------------------------------------
  101. % It can also read single value markers that follow the following format.
  102. %
  103. % *MarkerName=Value;#
  104. %
  105. % EXAMPLES: *WaitSeconds=10;# OR *JuiceStatus=ON;#
  106. %
  107. % The above line is a "Marker". The marker value is 10 in the first
  108. % and it's ON in the second example.
  109. % -----------------------------------------------------------------------
  110. % Moreover, the marker could be a single value:
  111. %
  112. % *MarkerValue#
  113. %
  114. % EXAMPLES: *JuiceOff# OR *HandsOnSwitches#
  115. % -----------------------------------------------------------------------
  116. % The label, parameter name, and values are flexible and can be anything.
  117. % The only required formatting is that the user needs to have a label
  118. % followed by a colon ':', followed by a field name 'MarkerVal', followed
  119. % by an equal sign '=', followed by the parameter value '10', and end
  120. % with a semi-colon ';'.
  121. %
  122. % NOTE:
  123. % Every line requires a pound-sign '#' at the very end.
  124. % Every line requires a star sign '*' at the very beginning. If you
  125. % use LabVIEW SendtoCerebus.vi by Kian Torab then there is no need for
  126. % a '*' in the beginning.
  127. %
  128. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  129. % Kian Torab
  130. % kian@blackrockmicro.com
  131. % Blackrock Microsystems
  132. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  133. % Version History
  134. %
  135. % 4.4.0.0:
  136. % - Major performance boost in reading NEV files when tracking data is
  137. % stored in the file.
  138. %
  139. % 4.4.0.2:
  140. % - Updated documentation.
  141. %
  142. % 4.4.0.3: 5 January 2014
  143. % - Fixed the way DayOfWeek is read in MetaTags.
  144. % - Fixed 'noread' argument, so when passed, openNEV will not read the
  145. % spike waveforms.
  146. %
  147. % 4.4.1.0: 25 January 2014
  148. % - Fixed a bug that resulted from passing 'read' to openNEV.
  149. %
  150. % 4.4.2.0: 28 February 2014
  151. % - Fixed bug related to loading data with t:XX:XX argument.
  152. %
  153. % 4.4.3.0: 12 June 2014
  154. % - Fixed a typo in the help.
  155. %
  156. % 4.4.3.1: 13 June 2014
  157. % - Updated the version numbers in the help and in the function itself.
  158. %
  159. % 4.4.3.2: 21 June 2014
  160. % - Fixed a bug where Application name wasn't being read properly.
  161. %
  162. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  163. %% Defining structures
  164. NEV = struct('MetaTags',[], 'ElectrodesInfo', [], 'Data', []);
  165. NEV.MetaTags = struct('Subject', [], 'Experimenter', [], 'DateTime', [],...
  166. 'SampleRes',[],'Comment',[],'FileTypeID',[],'Flags',[], 'openNEVver', [], ...
  167. 'DateTimeRaw', [], 'FileSpec', [], 'PacketBytes', [], 'HeaderOffset', [], ...
  168. 'DataDuration', [], 'DataDurationSec', [], 'PacketCount', [], ...
  169. 'TimeRes', [], 'Application', [], 'Filename', [], 'FilePath', []);
  170. NEV.Data = struct('SerialDigitalIO', [], 'Spikes', [], 'Comments', [], 'VideoSync', [], ...
  171. 'Tracking', [], 'TrackingEvents', [], 'PatientTrigger', [], 'Reconfig', []);
  172. NEV.Data.Spikes = struct('TimeStamp', [],'Electrode', [],...
  173. 'Unit', [],'Waveform', [], 'WaveformUnit', []);
  174. NEV.Data.SerialDigitalIO = struct('InputType', [], 'TimeStamp', [],...
  175. 'TimeStampSec', [], 'Type', [], 'Value', [], 'InsertionReason', [], 'UnparsedData', []);
  176. NEV.Data.VideoSync = struct('TimeStamp', [], 'FileNumber', [], 'FrameNumber', [], 'ElapsedTime', [], 'SourceID', []);
  177. NEV.Data.Comments = struct('TimeStamp', [], 'TimeStampSec', [], 'CharSet', [], 'Color', [], 'Text', []);
  178. NEV.Data.Tracking = [];
  179. NEV.Data.TrackingEvents = struct('TimeStamp', [], 'TimeStampSec', [], 'Text', []);
  180. NEV.Data.PatientTrigger = struct('TimeStamp', [], 'TriggerType', []);
  181. NEV.Data.Reconfig = struct('TimeStamp', [], 'ChangeType', [], 'CompName', [], 'ConfigChanged', []);
  182. Flags = struct;
  183. NEV.MetaTags.openNEVver = '4.4.3.1';
  184. %% Check for multiple versions of openNEV in path
  185. if size(which('openNEV', '-ALL'),1) > 1
  186. disp('WARNING: There are multiple openNEV functions in the path. Use which openNEV -ALL for more information.');
  187. end
  188. %% Validating input arguments
  189. for i=1:length(varargin)
  190. switch lower(varargin{i})
  191. case 'report'
  192. Flags.Report = varargin{i};
  193. case 'noread'
  194. Flags.ReadData = varargin{i};
  195. case 'read'
  196. Flags.ReadData = varargin{i};
  197. case 'nosave'
  198. Flags.SaveFile = varargin{i};
  199. case 'nomat'
  200. Flags.NoMAT = varargin{i};
  201. case 'nowarning'
  202. Flags.WarningStat = varargin{i};
  203. case 'parse'
  204. Flags.ParseData = 'parse';
  205. case 'uv'
  206. Flags.waveformUnits = 'uV';
  207. case '8bits'
  208. Flags.digIOBits = '8bits';
  209. case 'overwrite'
  210. Flags.Overwrite = 'overwrite';
  211. otherwise
  212. temp = varargin{i};
  213. if length(temp)>3 && ...
  214. (strcmpi(temp(3),'\') || ...
  215. strcmpi(temp(1),'/') || ...
  216. strcmpi(temp(2),'/'))
  217. fileFullPath = varargin{i};
  218. if exist(fileFullPath, 'file') ~= 2
  219. disp('The file does not exist.');
  220. varargout{1} = [];
  221. return;
  222. end
  223. elseif length(temp)>3 && strcmpi(temp(1:2),'t:') && ~strcmpi(temp(3), '\') && ~strcmpi(temp(3), '/')
  224. temp(1:2) = [];
  225. temp = str2num(temp);
  226. if length(temp) == 1
  227. fprintf('Only one timepoint (%0.0f) was passed to the function.\n', temp);
  228. fprintf('The initial timepoint is set to 0, so data between 0 and %0.0f will be read.\n', temp);
  229. temp(2) = temp;
  230. temp(1) = 0;
  231. end
  232. readTime = [temp(1), temp(end)];
  233. Flags.SaveFile = 'nosave';
  234. Flags.NoMAT = 'nomat';
  235. else
  236. if ~isnumeric(varargin{i})
  237. disp(['Invalid argument ''' varargin{i} ''' .']);
  238. else
  239. disp(['Invalid argument ''' num2str(varargin{i}) ''' .']);
  240. end
  241. clear variables;
  242. if nargout
  243. varargout{1} = [];
  244. end
  245. return;
  246. end
  247. clear temp;
  248. end
  249. end; clear i;
  250. %% Defining and validating variables
  251. if ~exist('fileFullPath', 'var')
  252. if exist('getFile.m', 'file') == 2
  253. [fileName pathName] = getFile('*.nev', 'Choose a NEV file...');
  254. else
  255. [fileName pathName] = uigetfile;
  256. end
  257. fileFullPath = [pathName fileName];
  258. if fileFullPath==0;
  259. clear variables;
  260. if nargout
  261. varargout{1} = [];
  262. end
  263. disp('No file was selected.');
  264. return
  265. end
  266. end
  267. if ~isfield(Flags, 'Report'); Flags.Report = 'noreport'; end
  268. if ~isfield(Flags, 'WarningStat'); Flags.WarningStat = 'warning'; end;
  269. if ~isfield(Flags, 'ReadData'); Flags.ReadData = 'read'; end
  270. if ~isfield(Flags, 'ParseData'); Flags.ParseData = 'noparse'; end
  271. if ~isfield(Flags, 'SaveFile'); Flags.SaveFile = 'save'; end;
  272. if ~isfield(Flags, 'NoMAT'); Flags.NoMAT = 'yesmat'; end;
  273. if ~isfield(Flags, 'waveformUnits'); Flags.waveformUnits = 'raw'; end;
  274. if ~isfield(Flags, 'digIOBits'); Flags.digIOBits = '16bits'; end;
  275. if ~isfield(Flags, 'Overwrite'); Flags.Overwrite = 'noOverwrite'; end;
  276. if strcmpi(Flags.Report, 'report')
  277. disp(['openNEV ' NEV.MetaTags.openNEVver]);
  278. end
  279. %% Validating existance of parseCommand
  280. if strcmpi(Flags.ParseData, 'parse')
  281. if exist('parseCommand.m', 'file') ~= 2
  282. disp('This version of openNEV requires function parseCommand.m to be placed in path.');
  283. clear variables;
  284. if nargout
  285. varargout{1} = [];
  286. end
  287. return;
  288. end
  289. end
  290. tic;
  291. matPath = [fileFullPath(1:end-4) '.mat'];
  292. %% Check for a MAT file and load that instead of NEV
  293. if exist(matPath, 'file') == 2 && strcmpi(Flags.NoMAT, 'yesmat')
  294. disp('MAT file corresponding to selected NEV file already exists. Loading MAT instead...');
  295. load(matPath);
  296. if isempty(NEV.Data.Spikes.Waveform) && strcmpi(Flags.ReadData, 'read')
  297. disp('The MAT file does not contain waveforms. Loading NEV instead...');
  298. else
  299. if ~nargout
  300. assignin('base', 'NEV', NEV);
  301. clear variables;
  302. else
  303. varargout{1} = NEV;
  304. end
  305. return;
  306. end
  307. end
  308. %% Reading BasicHeader information from file
  309. FID = fopen(fileFullPath, 'r', 'ieee-le');
  310. BasicHeader = fread(FID, 336, '*uint8');
  311. NEV.MetaTags.FileTypeID = char(BasicHeader(1:8)');
  312. NEV.MetaTags.FileSpec = [num2str(double(BasicHeader(9))) '.' num2str(double(BasicHeader(10)))];
  313. NEV.MetaTags.Flags = dec2bin(double(typecast(BasicHeader(11:12), 'uint16')),16);
  314. Trackers.fExtendedHeader = double(typecast(BasicHeader(13:16), 'uint32'));
  315. NEV.MetaTags.HeaderOffset = Trackers.fExtendedHeader;
  316. Trackers.countPacketBytes = double(typecast(BasicHeader(17:20), 'uint32'));
  317. NEV.MetaTags.PacketBytes = Trackers.countPacketBytes;
  318. NEV.MetaTags.TimeRes = double(typecast(BasicHeader(21:24), 'uint32'));
  319. NEV.MetaTags.SampleRes = typecast(BasicHeader(25:28), 'uint32');
  320. t = double(typecast(BasicHeader(29:44), 'uint16'));
  321. tempApp = BasicHeader(45:76)';
  322. tempApp(find(tempApp == 0):end) = [];
  323. NEV.MetaTags.Application = char(tempApp); clear tempApp;
  324. NEV.MetaTags.Comment = char(BasicHeader(77:332)');
  325. [NEV.MetaTags.FilePath, NEV.MetaTags.Filename] = fileparts(fileFullPath);
  326. Trackers.countExtHeader = typecast(BasicHeader(333:336), 'uint32');
  327. clear BasicHeader;
  328. if strcmpi(NEV.MetaTags.FileTypeID, 'NEURALEV')
  329. if exist([fileFullPath(1:end-8) '.sif'], 'file') == 2
  330. METATAGS = textread([fileFullPath(1:end-8) '.sif'], '%s');
  331. NEV.MetaTags.Subject = METATAGS{3}(5:end-5);
  332. NEV.MetaTags.Experimenter = [METATAGS{5}(8:end-8) ' ' METATAGS{6}(7:end-7)];
  333. end
  334. end
  335. if ~any(strcmpi(NEV.MetaTags.FileSpec, {'2.1', '2.2', '2.3'}))
  336. disp('Unknown filespec. Cannot open file.');
  337. fclose FID;
  338. clear variables;
  339. if nargout
  340. varargout{1} = [];
  341. end
  342. return;
  343. end
  344. clear fileFullPath;
  345. %% Parsing and validating FileSpec and DateTime variables
  346. NEV.MetaTags.DateTimeRaw = t.';
  347. NEV.MetaTags.DateTime = [num2str(t(2)) '/' num2str(t(4)+2) '/' num2str(t(1))...
  348. ' ' datestr(t(3), 'dddd') ' ' num2str(t(5)) ':' ...
  349. num2str(t(6)) ':' num2str(t(7)) '.' num2str(t(8))] ;
  350. clear t;
  351. %% Removing extra garbage characters from the Comment field.
  352. NEV.MetaTags.Comment(find(NEV.MetaTags.Comment==0,1):end) = 0;
  353. %% Recording after BasicHeader file position
  354. Trackers.fBasicHeader = ftell(FID); %#ok<NASGU>
  355. % Calculating the length of the data
  356. currentLocation = ftell(FID);
  357. fseek(FID, -Trackers.countPacketBytes, 'eof');
  358. NEV.MetaTags.DataDuration = fread(FID, 1, 'uint32=>double');
  359. NEV.MetaTags.DataDurationSec = double(NEV.MetaTags.DataDuration) / double(NEV.MetaTags.SampleRes);
  360. fseek(FID, currentLocation, 'bof');
  361. %% Reading ExtendedHeader information
  362. for ii=1:Trackers.countExtHeader
  363. ExtendedHeader = fread(FID, 32, '*uint8');
  364. PacketID = char(ExtendedHeader(1:8)');
  365. switch PacketID
  366. case 'ARRAYNME'
  367. NEV.ArrayInfo.ElectrodeName = char(ExtendedHeader(9:end));
  368. case 'ECOMMENT'
  369. NEV.ArrayInfo.ArrayComment = char(ExtendedHeader(9:end));
  370. case 'CCOMMENT'
  371. NEV.ArrayInfo.ArrayCommentCont = char(ExtendedHeader(9:end));
  372. case 'MAPFILE'
  373. NEV.ArrayInfo.MapFile = char(ExtendedHeader(9:end));
  374. case 'NEUEVWAV'
  375. ElectrodeID = typecast(ExtendedHeader(9:10), 'uint16');
  376. NEV.ElectrodesInfo(ElectrodeID).ElectrodeID = ElectrodeID;
  377. NEV.ElectrodesInfo(ElectrodeID).ConnectorBank = char(ExtendedHeader(11)+64);
  378. NEV.ElectrodesInfo(ElectrodeID).ConnectorPin = ExtendedHeader(12);
  379. df = typecast(ExtendedHeader(13:14),'int16');
  380. % This is a workaround for the DigitalFactor overflow in NEV
  381. % files. Remove once Central is updated
  382. if df == 21516
  383. NEV.ElectrodesInfo(ElectrodeID).DigitalFactor = 152592.547;
  384. else
  385. NEV.ElectrodesInfo(ElectrodeID).DigitalFactor = df;
  386. end
  387. % End of workaround
  388. NEV.ElectrodesInfo(ElectrodeID).EnergyThreshold = typecast(ExtendedHeader(15:16),'uint16');
  389. NEV.ElectrodesInfo(ElectrodeID).HighThreshold = typecast(ExtendedHeader(17:18),'int16');
  390. NEV.ElectrodesInfo(ElectrodeID).LowThreshold = typecast(ExtendedHeader(19:20),'int16');
  391. NEV.ElectrodesInfo(ElectrodeID).Units = ExtendedHeader(21);
  392. NEV.ElectrodesInfo(ElectrodeID).WaveformBytes = ExtendedHeader(22);
  393. clear ElectrodeID;
  394. case 'NEUEVLBL'
  395. ElectrodeID = typecast(ExtendedHeader(9:10), 'uint16');
  396. NEV.ElectrodesInfo(ElectrodeID).ElectrodeLabel = char(ExtendedHeader(11:26));
  397. clear ElectrodeID;
  398. case 'NEUEVFLT'
  399. ElectrodeID = typecast(ExtendedHeader(9:10), 'uint16');
  400. NEV.ElectrodesInfo(ElectrodeID).HighFreqCorner = typecast(ExtendedHeader(11:14),'uint32');
  401. NEV.ElectrodesInfo(ElectrodeID).HighFreqOrder = typecast(ExtendedHeader(15:18),'uint32');
  402. NEV.ElectrodesInfo(ElectrodeID).HighFilterType = typecast(ExtendedHeader(19:20),'uint16');
  403. NEV.ElectrodesInfo(ElectrodeID).LowFreqCorner = typecast(ExtendedHeader(21:24),'uint32');
  404. NEV.ElectrodesInfo(ElectrodeID).LowFreqOrder = typecast(ExtendedHeader(25:28),'uint32');
  405. NEV.ElectrodesInfo(ElectrodeID).LowFilterType = typecast(ExtendedHeader(29:30),'uint16');
  406. clear ElectrodeID;
  407. case 'DIGLABEL'
  408. Mode = ExtendedHeader(25);
  409. NEV.IOLabels{Mode+1} = char(ExtendedHeader(9:24).');
  410. clear Mode;
  411. case 'NSASEXEV' %% Not implemented in the Cerebus firmware.
  412. %% Needs to be updated once implemented into the
  413. %% firmware by Blackrock Microsystems.
  414. NEV.NSAS.Freq = typecast(ExtendedHeader(9:10),'uint16');
  415. NEV.NSAS.DigInputConf = char(ExtendedHeader(11));
  416. NEV.NSAS.AnalCh1Conf = char(ExtendedHeader(12));
  417. NEV.NSAS.AnalCh1Detect = typecast(ExtendedHeader(13:14),'uint16');
  418. NEV.NSAS.AnalCh2Conf = char(ExtendedHeader(15));
  419. NEV.NSAS.AnalCh2Detect = typecast(ExtendedHeader(16:17),'uint16');
  420. NEV.NSAS.AnalCh3Conf = char(ExtendedHeader(18));
  421. NEV.NSAS.AnalCh3Detect = typecast(ExtendedHeader(19:20),'uint16');
  422. NEV.NSAS.AnalCh4Conf = char(ExtendedHeader(21));
  423. NEV.NSAS.AnalCh4Detect = typecast(ExtendedHeader(22:23),'uint16');
  424. NEV.NSAS.AnalCh5Conf = char(ExtendedHeader(24));
  425. NEV.NSAS.AnalCh5Detect = typecast(ExtendedHeader(25:26),'uint16');
  426. case 'VIDEOSYN'
  427. cnt = 1;
  428. if (isfield(NEV, 'VideoSyncInfo'))
  429. cnt = size(NEV.VideoSyncInfo, 2) + 1;
  430. end
  431. NEV.VideoSyncInfo(cnt).SourceID = typecast(ExtendedHeader(9:10),'uint16');
  432. NEV.VideoSyncInfo(cnt).SourceName = char(ExtendedHeader(11:26))';
  433. NEV.VideoSyncInfo(cnt).FrameRateFPS = typecast(ExtendedHeader(27:30),'single')';
  434. clear cnt;
  435. case 'TRACKOBJ'
  436. cnt = 1;
  437. if (isfield(NEV, 'ObjTrackInfo'))
  438. cnt = size(NEV.ObjTrackInfo, 2) + 1;
  439. end
  440. NEV.ObjTrackInfo(cnt).TrackableType = typecast(ExtendedHeader(9:10),'uint16');
  441. NEV.ObjTrackInfo(cnt).TrackableID = typecast(ExtendedHeader(11:14), 'uint32');
  442. NEV.ObjTrackInfo(cnt).TrackableName = char(ExtendedHeader(15:30))';
  443. clear cnt;
  444. otherwise
  445. disp(['PacketID ' PacketID ' is invalid.']);
  446. disp('Please make sure this version of openNEV is compatible with your current NSP firmware.')
  447. fclose(FID);
  448. clear variables;
  449. if nargout
  450. varargout{1} = [];
  451. end
  452. return;
  453. end
  454. end
  455. NEV.MetaTags.ChannelID = [NEV.ElectrodesInfo.ElectrodeID];
  456. clear ExtendedHeader PacketID ii;
  457. %% Recording after ExtendedHeader file position and calculating Data Length
  458. % and number of data packets
  459. fseek(FID, 0, 'eof');
  460. Trackers.fData = ftell(FID);
  461. Trackers.countDataPacket = (Trackers.fData - Trackers.fExtendedHeader)/Trackers.countPacketBytes;
  462. NEV.MetaTags.PacketCount = Trackers.countDataPacket;
  463. %%
  464. Flags.UnparsedDigitalData = 0;
  465. %% Reading packet headers and digital values
  466. Timestamp = [];
  467. PacketIDs = [];
  468. tempClassOrReason = [];
  469. tempDigiVals = [];
  470. if NEV.MetaTags.PacketCount ~= 0
  471. fseek(FID, Trackers.fExtendedHeader, 'bof');
  472. tRawData = fread(FID, [10 Trackers.countDataPacket], '10*uint8=>uint8', Trackers.countPacketBytes - 10);
  473. Timestamp = tRawData(1:4,:);
  474. Timestamp = typecast(Timestamp(:), 'uint32').';
  475. %% Calculate the number of packets that need to be read based on the time input parameters
  476. if ~exist('readTime', 'var')
  477. Trackers.readPackets = [1, length(Timestamp)];
  478. else
  479. [tmp,tempReadPackets] = find(Timestamp > readTime(1)*NEV.MetaTags.SampleRes,1,'first');
  480. if ~isempty(tempReadPackets)
  481. Trackers.readPackets(1) = tempReadPackets;
  482. else
  483. Trackers.readPackets(1) = NaN;
  484. end
  485. if isnan(Trackers.readPackets(1))
  486. fprintf('The file contains %0.2f seconds of data. The requested begining timestamp of %0.2f seconds is longer than the duration.\n', ...
  487. double(Timestamp(end))/double(NEV.MetaTags.SampleRes), ...
  488. readTime(1));
  489. clear variables;
  490. if nargout
  491. varargout{1} = [];
  492. end
  493. return;
  494. end
  495. [tmp,tempReadPackets] = find(Timestamp < readTime(2)*NEV.MetaTags.SampleRes,1,'last');
  496. if ~isempty(tempReadPackets)
  497. if readTime(2)*NEV.MetaTags.SampleRes > Timestamp(end)
  498. fprintf('The file contains %0.2f seconds of data. The requested end duration of %0.2f seconds will be adjusted to %0.2f seconds.\n', ...
  499. double(Timestamp(end))/double(NEV.MetaTags.SampleRes), ...
  500. readTime(2),...
  501. double(Timestamp(end))/double(NEV.MetaTags.SampleRes));
  502. end
  503. Trackers.readPackets(2) = tempReadPackets;
  504. else
  505. Trackers.readPackets(2) = NaN;
  506. end
  507. clear tmp, tempReadPackets;
  508. end
  509. PacketIDs = tRawData(5:6,Trackers.readPackets(1):Trackers.readPackets(2));
  510. PacketIDs = typecast(PacketIDs(:), 'uint16').';
  511. tempClassOrReason = uint8(tRawData(7,Trackers.readPackets(1):Trackers.readPackets(2)));
  512. if strcmpi(Flags.digIOBits, '16bits')
  513. tempDigiVals = tRawData(9:10,Trackers.readPackets(1):Trackers.readPackets(2));
  514. tempDigiVals = typecast(tempDigiVals(:), 'uint16');
  515. else
  516. tempDigiVals = uint16(tRawData(9,Trackers.readPackets(1):Trackers.readPackets(2)));
  517. end
  518. clear tRawData;
  519. else
  520. Trackers.readPackets = zeros(1,2);
  521. end
  522. %% Defining PacketID constants
  523. digserPacketID = 0;
  524. neuralIndicesPacketIDBounds = [1, 16384];
  525. commentPacketID = 65535;
  526. videoSyncPacketID = 65534;
  527. trackingPacketID = 65533;
  528. patientTrigPacketID = 65532;
  529. reconfigPacketID = 65531;
  530. %% Parse read digital data. Please refer to help to learn about the proper
  531. % formatting if the data.
  532. digserIndices = find(PacketIDs == digserPacketID);
  533. neuralIndices = find(neuralIndicesPacketIDBounds(2) >= PacketIDs & PacketIDs >= neuralIndicesPacketIDBounds(1));
  534. commentIndices = find(PacketIDs == commentPacketID);
  535. videoSyncPacketIDIndices = find(PacketIDs == videoSyncPacketID);
  536. trackingPacketIDIndices = find(PacketIDs == trackingPacketID);
  537. patientTrigPacketIDIndices = find(PacketIDs == patientTrigPacketID);
  538. reconfigPacketIDIndices = find(PacketIDs == reconfigPacketID);
  539. clear digserPacketID neuralIndicesPacketIDBounds commentPacketID ...
  540. videoSyncPacketID trackingPacketID patientTrigPacketID reconfigPacketID;
  541. digserTimestamp = Timestamp(digserIndices);
  542. NEV.Data.Spikes.TimeStamp = Timestamp(neuralIndices);
  543. NEV.Data.Spikes.Electrode = PacketIDs(neuralIndices);
  544. clear PacketIDs;
  545. NEV.Data.Spikes.Unit = tempClassOrReason(neuralIndices);
  546. %clear neuralIndices;
  547. NEV.Data.SerialDigitalIO.InsertionReason = tempClassOrReason(digserIndices);
  548. clear tempClassOrReason;
  549. DigiValues = tempDigiVals(digserIndices);
  550. clear tempDigiVals;
  551. %% Reads the waveforms if 'read' is passed to the function
  552. if strcmpi(Flags.ReadData, 'read')
  553. allExtraDataPacketIndices = [commentIndices, ...
  554. videoSyncPacketIDIndices, ...
  555. trackingPacketIDIndices, ...
  556. patientTrigPacketIDIndices, ...
  557. reconfigPacketIDIndices];
  558. if ~isempty(allExtraDataPacketIndices) % if there is any extra packets
  559. fseek(FID, Trackers.fExtendedHeader, 'bof');
  560. fseek(FID, (Trackers.readPackets(1)-1) * Trackers.countPacketBytes, 'cof');
  561. tRawData = fread(FID, [Trackers.countPacketBytes Trackers.readPackets(2)], ...
  562. [num2str(Trackers.countPacketBytes) '*uint8=>uint8'], 0);
  563. if ~isempty(commentIndices)
  564. NEV.Data.Comments.TimeStamp = Timestamp(commentIndices);
  565. NEV.Data.Comments.TimeStampSec = double(NEV.Data.Comments.TimeStamp)/double(NEV.MetaTags.TimeRes);
  566. NEV.Data.Comments.CharSet = tRawData(7, commentIndices);
  567. NEV.Data.Comments.Color = tRawData(9:12, commentIndices);
  568. NEV.Data.Comments.Color = typecast(NEV.Data.Comments.Color(:), 'uint32').';
  569. NEV.Data.Comments.Text = char(tRawData(13:Trackers.countPacketBytes, commentIndices).');
  570. % Transferring NeuroMotive Events to its own structure
  571. neuroMotiveEvents = find(NEV.Data.Comments.CharSet == 255);
  572. NEV.Data.TrackingEvents.TimeStamp = NEV.Data.Comments.TimeStamp(neuroMotiveEvents);
  573. NEV.Data.TrackingEvents.TimeStampSec = double(NEV.Data.TrackingEvents.TimeStamp)/double(NEV.MetaTags.TimeRes);
  574. NEV.Data.TrackingEvents.Text = NEV.Data.Comments.Text(neuroMotiveEvents,:);
  575. NEV.Data.Comments.TimeStamp(neuroMotiveEvents) = [];
  576. NEV.Data.Comments.TimeStampSec(neuroMotiveEvents) = [];
  577. NEV.Data.Comments.CharSet(neuroMotiveEvents) = [];
  578. NEV.Data.Comments.Color(neuroMotiveEvents) = [];
  579. NEV.Data.Comments.Text(neuroMotiveEvents) = [];
  580. clear commentIndices;
  581. end
  582. if ~isempty(videoSyncPacketIDIndices)
  583. NEV.Data.VideoSync.TimeStamp = Timestamp(videoSyncPacketIDIndices);
  584. NEV.Data.VideoSync.FileNumber = tRawData(7:8, videoSyncPacketIDIndices);
  585. NEV.Data.VideoSync.FileNumber = typecast(NEV.Data.VideoSync.FileNumber(:), 'uint16').';
  586. NEV.Data.VideoSync.FrameNumber = tRawData(9:12, videoSyncPacketIDIndices);
  587. NEV.Data.VideoSync.FrameNumber = typecast(NEV.Data.VideoSync.FrameNumber(:), 'uint32').';
  588. NEV.Data.VideoSync.ElapsedTime = tRawData(13:16, videoSyncPacketIDIndices);
  589. NEV.Data.VideoSync.ElapsedTime = typecast(NEV.Data.VideoSync.ElapsedTime(:), 'uint32').';
  590. NEV.Data.VideoSync.SourceID = tRawData(17:20, videoSyncPacketIDIndices);
  591. NEV.Data.VideoSync.SourceID = typecast(NEV.Data.VideoSync.SourceID(:), 'uint32').';
  592. clear videoSyncPacketIDIndices;
  593. end
  594. if ~isempty(trackingPacketIDIndices)
  595. tmp.TimeStamp = Timestamp(trackingPacketIDIndices);
  596. tmp.TimeStampSec = double(tmp.TimeStamp)/30000;
  597. % This portion is commented out because it does not contain any
  598. % information as of yet.
  599. tmp.ParentID = tRawData(7:8, trackingPacketIDIndices);
  600. tmp.ParentID = typecast(tmp.ParentID(:), 'uint16').';
  601. tmp.NodeID = tRawData(9:10, trackingPacketIDIndices);
  602. tmp.NodeID = typecast(tmp.NodeID(:), 'uint16').';
  603. tmp.NodeCount = tRawData(11:12, trackingPacketIDIndices);
  604. tmp.NodeCount = typecast(tmp.NodeCount(:), 'uint16').';
  605. tmp.MarkerCount = tRawData(13:14, trackingPacketIDIndices);
  606. tmp.MarkerCount = typecast(tmp.MarkerCount(:), 'uint16').';
  607. tmp.rigidBodyPoints = tRawData(15:102, trackingPacketIDIndices);
  608. tmp.rigidBodyPoints = reshape(typecast(tmp.rigidBodyPoints(:), 'uint16'), 44, length(tmp.ParentID));
  609. objectIndex = [0 '1' '2' '3' '4' '1' '2' '3' '4'];
  610. if (isfield(NEV, 'ObjTrackInfo'))
  611. for IDX = 1:size(NEV.ObjTrackInfo,2)
  612. emptyChar = find(NEV.ObjTrackInfo(IDX).TrackableName == 0, 1);
  613. NEV.ObjTrackInfo(IDX).TrackableName(emptyChar) = objectIndex(IDX);
  614. indicesOfEvent = find(tmp.NodeID == IDX-1);
  615. if ~isempty(indicesOfEvent)
  616. NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).TimeStamp = tmp.TimeStamp(indicesOfEvent);
  617. NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).TimeStampSec = tmp.TimeStampSec(indicesOfEvent);
  618. NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).ParentID = tmp.ParentID(indicesOfEvent);
  619. NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).NodeCount = tmp.NodeCount(indicesOfEvent);
  620. NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).MarkerCount = tmp.MarkerCount(indicesOfEvent);
  621. NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).MarkerCoordinates(size(NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).TimeStamp,2)).X = [];
  622. NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).MarkerCoordinates(size(NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).TimeStamp,2)).X = [];
  623. for xyIDX = 1:size(NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).TimeStamp,2)
  624. NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).MarkerCoordinates(xyIDX).X = ...
  625. tmp.rigidBodyPoints(1:2:NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).MarkerCount(xyIDX)*2, indicesOfEvent(xyIDX));
  626. NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).MarkerCoordinates(xyIDX).Y = ...
  627. tmp.rigidBodyPoints(2:2:NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).MarkerCount(xyIDX)*2, indicesOfEvent(xyIDX));
  628. end
  629. end
  630. end
  631. end
  632. clear trackingPacketIDIndices tmp;
  633. end
  634. if ~isempty(patientTrigPacketIDIndices)
  635. NEV.Data.PatientTrigger.TimeStamp = Timestamp(patientTrigPacketIDIndices);
  636. NEV.Data.PatientTrigger.TriggerType = tRawData(7:8, patientTrigPacketIDIndices);
  637. NEV.Data.PatientTrigger.TriggerType = typecast(NEV.Data.PatientTrigger.TriggerType(:), 'uint16').';
  638. clear patientTrigPacketIDIndices;
  639. end
  640. if ~isempty(reconfigPacketIDIndices)
  641. NEV.Data.Reconfig.TimeStamp = Timestamp(reconfigPacketIDIndices);
  642. NEV.Data.Reconfig.ChangeType = tRawData(7:8, reconfigPacketIDIndices);
  643. NEV.Data.Reconfig.ChangeType = typecast(NEV.Data.Reconfig.ChangeType(:), 'uint16').';
  644. NEV.Data.Reconfig.CompName = char(tRawData(9:24, reconfigPacketIDIndices));
  645. NEV.Data.Reconfig.ConfigChanged = char(tRawData(25:Trackers.countPacketBytes, reconfigPacketIDIndices));
  646. clear reconfigPacketIDIndices;
  647. end
  648. end % end if ~isempty(allExtraDataPacketIndices)
  649. clear Timestamp tRawData count idx;
  650. % now read waveform
  651. fseek(FID, Trackers.fExtendedHeader + 8, 'bof'); % Seek to location of spikes
  652. fseek(FID, (Trackers.readPackets(1)-1) * Trackers.countPacketBytes, 'cof');
  653. NEV.Data.Spikes.WaveformUnit = Flags.waveformUnits;
  654. NEV.Data.Spikes.Waveform = fread(FID, [(Trackers.countPacketBytes-8)/2 Trackers.readPackets(2)], ...
  655. [num2str((Trackers.countPacketBytes-8)/2) '*int16=>int16'], 8);
  656. NEV.Data.Spikes.Waveform(:, [digserIndices allExtraDataPacketIndices]) = [];
  657. clear allExtraDataPacketIndices;
  658. if strcmpi(Flags.waveformUnits, 'uv')
  659. elecDigiFactors = double(1000./[NEV.ElectrodesInfo(NEV.Data.Spikes.Electrode).DigitalFactor]);
  660. NEV.Data.Spikes.Waveform = bsxfun(@rdivide, double(NEV.Data.Spikes.Waveform), elecDigiFactors);
  661. if strcmpi(Flags.WarningStat, 'warning')
  662. fprintf(1,'\nThe spike waveforms are in unit of uV.\n');
  663. fprintf(2,'WARNING: This conversion may lead to loss of information.');
  664. fprintf(1,'\nRefer to help for more information.\n');
  665. end
  666. end
  667. end
  668. clear digserIndices;
  669. %% Parse digital data if requested
  670. if ~isempty(DigiValues)
  671. if strcmpi(Flags.ParseData, 'parse')
  672. try
  673. DigiValues = char(DigiValues);
  674. Inputs = {'Digital'; 'AnCh1'; 'AnCh2'; 'AnCh3'; 'AnCh4'; 'AnCh5'; 'PerSamp'; 'Serial'};
  675. AsteriskIndices = find(DigiValues == '*');
  676. DataBegTimestamp = digserTimestamp(AsteriskIndices);
  677. splitDigiValues = regexp(DigiValues(2:end), '*', 'split')';
  678. for idx = 1:length(splitDigiValues)
  679. try
  680. if isempty(find(splitDigiValues{idx} == ':', 1))
  681. splitDigiValues{idx}(find(splitDigiValues{idx} == '#')) = [];
  682. NEV.Data.SerialDigitalIO(idx).Value = splitDigiValues{idx};
  683. NEV.Data.SerialDigitalIO(idx).Type = 'Marker';
  684. else
  685. [tempParsedCommand error] = parseCommand(splitDigiValues{idx});
  686. if ~error
  687. pcFields = fields(tempParsedCommand);
  688. NEV.Data.SerialDigitalIO(idx).Value = splitDigiValues{idx};
  689. for fidx = 1:length(pcFields)
  690. NEV.Data.SerialDigitalIO(idx).(pcFields{fidx}) = tempParsedCommand.(pcFields{fidx});
  691. end
  692. else
  693. NEV.Data.SerialDigitalIO(idx).Value = splitDigiValues{idx};
  694. NEV.Data.SerialDigitalIO(idx).Type = 'UnparsedData';
  695. Flags.UnparsedDigitalData = 1;
  696. end
  697. end
  698. catch
  699. disp(['Error parsing: ' splitDigiValues{idx}]);
  700. disp('Please refer to the help for more information on how to properly format the digital data for parsing.');
  701. end
  702. end
  703. % Populate the NEV structure with Timestamp and inputtypes for the
  704. % digital data
  705. if ~isempty(DataBegTimestamp)
  706. c = num2cell(DataBegTimestamp); [NEV.Data.SerialDigitalIO(1:length(NEV.Data.SerialDigitalIO)).TimeStamp] = deal(c{1:end});
  707. c = num2cell(DataBegTimestamp/NEV.MetaTags.SampleRes); [NEV.Data.SerialDigitalIO.TimeStampSec] = deal(c{1:end});
  708. c = {Inputs{NEV.Data.SerialDigitalIO.InsertionReason(AsteriskIndices)}}; [NEV.Data.SerialDigitalIO.InputType] = deal(c{1:end});
  709. end
  710. clear Inputs DigiValues digserTimestamp;
  711. catch
  712. disp(lasterr);
  713. disp('An error occured during reading digital data. This is due to a problem with formatting digital data.');
  714. disp('Refer to help ''help openNEV'' for more information on how to properly format the digital data.');
  715. disp('Try using openNEV with ''noparse'', i.e. openNEV(''noparse'').');
  716. end
  717. else
  718. NEV.Data.SerialDigitalIO.TimeStamp = digserTimestamp;
  719. NEV.Data.SerialDigitalIO.TimeStampSec = double(digserTimestamp)/30000;
  720. clear digserTimestamp;
  721. NEV.Data.SerialDigitalIO.UnparsedData = DigiValues;
  722. clear DigiValues;
  723. end
  724. else
  725. if strcmpi(Flags.ReadData, 'read')
  726. if strcmpi(Flags.Report, 'report')
  727. disp('No digital data to read.');
  728. end
  729. end
  730. end
  731. if strcmpi(Flags.ParseData, 'parse')
  732. if Flags.UnparsedDigitalData && strcmpi(Flags.WarningStat, 'warning')
  733. fprintf(2, 'WARNING: The NEV file contains unparsed digital data.\n');
  734. end
  735. end
  736. %% Show a report if 'report' is passed as an argument
  737. if strcmpi(Flags.Report, 'report')
  738. % Displaying report
  739. disp( '*** FILE INFO **************************');
  740. disp(['File Name = ' NEV.MetaTags.Filename]);
  741. disp(['Filespec = ' NEV.MetaTags.FileSpec]);
  742. disp(['Data Duration (min) = ' num2str(round(NEV.MetaTags.DataDuration/NEV.MetaTags.SampleRes/60))]);
  743. disp(['Packet Counts = ' num2str(Trackers.countDataPacket)]);
  744. disp(' ');
  745. disp( '*** BASIC HEADER ***********************');
  746. disp(['Sample Resolution = ' num2str(NEV.MetaTags.SampleRes)]);
  747. disp(['Date and Time = ' NEV.MetaTags.DateTime]);
  748. disp(['Comment = ' NEV.MetaTags.Comment(1:64) ]);
  749. disp([' ' NEV.MetaTags.Comment(65:128) ]);
  750. disp([' ' NEV.MetaTags.Comment(129:192)]);
  751. disp([' ' NEV.MetaTags.Comment(193:256)]);
  752. disp(['The load time was for NEV file was ' num2str(toc, '%0.1f') ' seconds.']);
  753. end
  754. %% Saving the NEV structure as a MAT file for easy access
  755. if strcmpi(Flags.SaveFile, 'save')
  756. if exist(matPath, 'file') == 2 && strcmpi(Flags.Overwrite, 'nooverwrite')
  757. disp(['File ' matPath ' already exists.']);
  758. overWrite = input('Would you like to overwrite (Y/N)? ', 's');
  759. if strcmpi(overWrite, 'y')
  760. disp('Saving MAT file. This may take a few seconds...');
  761. save(matPath, 'NEV', '-v7.3');
  762. else
  763. disp('File was not overwritten.');
  764. end
  765. elseif exist(matPath, 'file') == 2 && strcmpi(Flags.Overwrite, 'overwrite')
  766. disp(['File ' matPath ' already exists.']);
  767. disp('Overwriting the old MAT file. This may take a few seconds...');
  768. save(matPath, 'NEV', '-v7.3');
  769. else
  770. disp('Saving MAT file. This may take a few seconds...');
  771. save(matPath, 'NEV', '-v7.3');
  772. end
  773. clear overWrite;
  774. end
  775. if ~nargout
  776. assignin('base', 'NEV', NEV);
  777. else
  778. varargout{1} = NEV;
  779. end
  780. fclose(FID);
  781. clear Flags Trackers FID matPath;