Scheduled service maintenance on November 22


On Friday, November 22, 2024, between 06:00 CET and 18:00 CET, GIN services will undergo planned maintenance. Extended service interruptions should be expected. We will try to keep downtimes to a minimum, but recommend that users avoid critical tasks, large data uploads, or DOI requests during this time.

We apologize for any inconvenience.

openNEV.m 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033
  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 & 3.0.
  6. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  7. % Use OUTPUT = openNEV(fname, 'noread', 'report', 'noparse', 'nowarning',
  8. % 'nosave', 'nomat', 'uV', 'overwrite', 'direct').
  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. % 'direct': Use this if you are using a CerePlex Direct system
  70. % without the typical strobe mode. This will treat the 16th
  71. % bit of the digital input as a strobe signal and report
  72. % the remaining 15 bits as the digital input value.
  73. %
  74. % OUTPUT: Contains the NEV structure.
  75. %
  76. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  77. % USAGE EXAMPLE:
  78. %
  79. % openNEV('report','read');
  80. %
  81. % In the example above, the file dialogue will prompt for a file. A
  82. % report of the file contents will be shown. The digital data will not be
  83. % parsed. The data needs to be in the proper format (refer below). The
  84. % spike waveforms are in raw units and not in uV.
  85. %
  86. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  87. % DIGITAL PARAMETERS/MARKERS FORMAT:
  88. %
  89. % In order for this function to parse your experimental parameters they
  90. % need to be in the following format:
  91. %
  92. % *ParamLabel:Parameter1=value1;Parameter2=value2;Parameter3=value3;#
  93. %
  94. % TWO EXAMPLES:
  95. % *ExpParameter:Intensity=1.02;Duration=400;Trials=1;PageSegment=14;#
  96. %
  97. % *Stimulation:StimCount=5;Duration=10;#
  98. %
  99. % In the first example, the parameter is of type "ExpParameter". The
  100. % parameters are, "Intensity, Duration, Trials, and PageSement." The
  101. % values of those parameters are, "1.02, 400, 1, and 14," respectively.
  102. % The second example is of type "Stimulation". The name of the parameters
  103. % are "StimCount" and "Duration" and the values are "5" and "10"
  104. % respectively.
  105. % -----------------------------------------------------------------------
  106. % It can also read single value markers that follow the following format.
  107. %
  108. % *MarkerName=Value;#
  109. %
  110. % EXAMPLES: *WaitSeconds=10;# OR *JuiceStatus=ON;#
  111. %
  112. % The above line is a "Marker". The marker value is 10 in the first
  113. % and it's ON in the second example.
  114. % -----------------------------------------------------------------------
  115. % Moreover, the marker could be a single value:
  116. %
  117. % *MarkerValue#
  118. %
  119. % EXAMPLES: *JuiceOff# OR *HandsOnSwitches#
  120. % -----------------------------------------------------------------------
  121. % The label, parameter name, and values are flexible and can be anything.
  122. % The only required formatting is that the user needs to have a label
  123. % followed by a colon ':', followed by a field name 'MarkerVal', followed
  124. % by an equal sign '=', followed by the parameter value '10', and end
  125. % with a semi-colon ';'.
  126. %
  127. % NOTE:
  128. % Every line requires a pound-sign '#' at the very end.
  129. % Every line requires a star sign '*' at the very beginning. If you
  130. % use LabVIEW SendtoCerebus.vi by Kian Torab then there is no need for
  131. % a '*' in the beginning.
  132. %
  133. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  134. % Kian Torab
  135. % support@blackrockmicro.com
  136. % Blackrock Microsystems
  137. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  138. % Version History
  139. %
  140. % 4.4.0.0:
  141. % - Major performance boost in reading NEV files when tracking data is
  142. % stored in the file.
  143. %
  144. % 4.4.0.2:
  145. % - Updated documentation.
  146. %
  147. % 4.4.0.3: 5 January 2014
  148. % - Fixed the way DayOfWeek is read in MetaTags.
  149. % - Fixed 'noread' argument, so when passed, openNEV will not read the
  150. % spike waveforms.
  151. %
  152. % 4.4.1.0: 25 January 2014
  153. % - Fixed a bug that resulted from passing 'read' to openNEV.
  154. %
  155. % 4.4.2.0: 28 February 2014
  156. % - Fixed bug related to loading data with t:XX:XX argument.
  157. %
  158. % 4.4.3.0: 12 June 2014
  159. % - Fixed a typo in the help.
  160. %
  161. % 4.4.3.1: 13 June 2014
  162. % - Updated the version numbers in the help and in the function itself.
  163. %
  164. % 5.0.0.0: 02 December 2014
  165. % - Fixed a bug where Application name wasn't being read properly.
  166. % - Warnings now don't show up in more places when "nowarning" is used.
  167. % - Added field FileExt to MetaTags.
  168. % - Added 512 synchronized reading capability.
  169. % - Fixed the date in NSx.MetaTags.DateTime.
  170. %
  171. % 5.1.0.0: 28 March 2015
  172. % - Added the ability to read from networked drives in Windows.
  173. % - Fixed the DateTime variable in MetaTags.
  174. % - Fixed the date in NSx.MetaTags.DateTime (again).
  175. % - Fixed a bug related to >512-ch data loading.
  176. %
  177. % 5.1.1.0: 1 April 2015
  178. % - Fixed a bug with NeuroMotive when spike window is changed from the
  179. % original length.
  180. %
  181. % 5.1.2.0: June 30 2015
  182. % - Fixed a bug regarding the number of packages when 'no read' is used.
  183. %
  184. % 5.1.3.0: July 10 2015
  185. % - Fixed a bug with NeuroMotive data reading when both objects and
  186. % markers were being recorded.
  187. %
  188. % 5.2.0.0: June 11 2016
  189. % - Added support for CerePlex Direct strobe mode on digital input.
  190. % - Fixed a bug with reading NeuroMotive data that resulted in a crash.
  191. %
  192. % 5.3.0.0: June 13 2016
  193. % - Fixed a bug with reading NeuroMotive data that resulted in a crash.
  194. % - Improved and more detailed parsing of NeuroMotive events.
  195. % - Added parsing of comment start time and comment committ time (time
  196. % that a comment is entered.
  197. %
  198. % 5.3.1.0: September 1, 2017
  199. % - Fixed a bug with file path and whent this was passed to the function.
  200. %
  201. % 5.4.0.0: September 13, 2017
  202. % - Checks to see if there's a newer version of NPMK is available.
  203. % - Properly reads the comment colors.
  204. %
  205. % 5.4.0.1: January 10, 2018
  206. % - Fixed a NeuroMotive bug when AllMarkers was being recorded.
  207. %
  208. % 5.4.1.0: April 25, 2018
  209. % - Now all comments open in order.
  210. % - Fixed a bug with path of file if both NEV and MAT were moved to a new
  211. % location.
  212. %
  213. % 6.0.0.0: January 27, 2020
  214. % - Added support for 64-bit timestamps in NEV and NSx.
  215. % - Removed dependency on MATLAB R2016b by removing function 'contains'.
  216. %
  217. % 6.1.0.0: April 16, 2020
  218. % - Some bug fixes. (David Kluger)
  219. %
  220. % 6.2.0.0: April 29, 2020
  221. % - Added ability to read all types of recording event types.
  222. %
  223. % 6.2.1.0: April 20, 2021
  224. % - Fixed a bug related to file opening.
  225. %
  226. % 6.2.2.0: March 7, 2022
  227. % - Fixed a data offset error related to handling 64-bit timestamps in
  228. % spike data. (Spencer Kellis)
  229. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  230. %% Check for the latest version fo NPMK
  231. NPMKverChecker
  232. %% Defining structures
  233. NEV = struct('MetaTags',[], 'ElectrodesInfo', [], 'Data', []);
  234. NEV.MetaTags.openNEVver = '6.2.2.0';
  235. NEV.MetaTags = struct('Subject', [], 'Experimenter', [], 'DateTime', [],...
  236. 'SampleRes',[],'Comment',[],'FileTypeID',[],'Flags',[], 'openNEVver', [], ...
  237. 'DateTimeRaw', [], 'FileSpec', [], 'PacketBytes', [], 'HeaderOffset', [], ...
  238. 'DataDuration', [], 'DataDurationSec', [], 'PacketCount', [], ...
  239. 'TimeRes', [], 'Application', [], 'Filename', [], 'FilePath', []);
  240. NEV.Data = struct('SerialDigitalIO', [], 'Spikes', [], 'Comments', [], 'VideoSync', [], ...
  241. 'Tracking', [], 'TrackingEvents', [], 'PatientTrigger', [], 'Reconfig', []);
  242. NEV.Data.Spikes = struct('TimeStamp', [],'Electrode', [],...
  243. 'Unit', [],'Waveform', [], 'WaveformUnit', []);
  244. NEV.Data.SerialDigitalIO = struct('InputType', [], 'TimeStamp', [],...
  245. 'TimeStampSec', [], 'Type', [], 'Value', [], 'InsertionReason', [], 'UnparsedData', []);
  246. NEV.Data.VideoSync = struct('TimeStamp', [], 'FileNumber', [], 'FrameNumber', [], 'ElapsedTime', [], 'SourceID', []);
  247. NEV.Data.Comments = struct('TimeStampStarted', [], 'TimeStampStartedSec', [], 'TimeStamp', [], 'TimeStampSec', [], 'CharSet', [], 'Text', []);
  248. NEV.Data.Tracking = [];
  249. NEV.Data.TrackingEvents = struct('TimeStamp', [], 'TimeStampSec', [], 'ROIName', [], 'ROINum', [], 'Event', [], 'Frame', []);
  250. NEV.Data.PatientTrigger = struct('TimeStamp', [], 'TriggerType', []);
  251. NEV.Data.Reconfig = struct('TimeStamp', [], 'ChangeType', [], 'CompName', [], 'ConfigChanged', []);
  252. Flags = struct;
  253. %% Check for multiple versions of openNEV in path
  254. if size(which('openNEV', '-ALL'),1) > 1
  255. disp('WARNING: There are multiple openNEV functions in the path. Use which openNEV -ALL for more information.');
  256. end
  257. %% Validating input arguments
  258. for i=1:length(varargin)
  259. switch lower(varargin{i})
  260. case 'report'
  261. Flags.Report = varargin{i};
  262. case 'noread'
  263. Flags.ReadData = varargin{i};
  264. case 'nomultinsp'
  265. Flags.MultiNSP = varargin{i};
  266. case 'read'
  267. Flags.ReadData = varargin{i};
  268. case 'nosave'
  269. Flags.SaveFile = varargin{i};
  270. case 'nomat'
  271. Flags.NoMAT = varargin{i};
  272. case 'direct'
  273. Flags.Direct = varargin{i};
  274. case 'nowarning'
  275. Flags.WarningStat = varargin{i};
  276. case 'parse'
  277. Flags.ParseData = 'parse';
  278. case 'uv'
  279. Flags.waveformUnits = 'uV';
  280. case '8bits'
  281. Flags.digIOBits = '8bits';
  282. case 'overwrite'
  283. Flags.Overwrite = 'overwrite';
  284. case 'nooverwrite'
  285. Flags.Overwrite = 'nooverwrite';
  286. otherwise
  287. tempst = char(varargin{i});
  288. if length(tempst)>3 && ...
  289. (strcmpi(tempst(3),'\') || ...
  290. strcmpi(tempst(1),'/') || ...
  291. strcmpi(tempst(2),'/') || ...
  292. strcmpi(tempst(1:2), '\\') || ...
  293. strcmpi(tempst(end-3), '.'))
  294. fileFullPath = varargin{i};
  295. if exist(fileFullPath, 'file') ~= 2
  296. disp('The file does not exist.');
  297. varargout{1} = [];
  298. return;
  299. end
  300. elseif length(tempst)>3 && strcmpi(tempst(1:2),'t:') && ~strcmpi(tempst(3), '\') && ~strcmpi(tempst(3), '/')
  301. tempst(1:2) = [];
  302. tempst = str2num(tempst);
  303. if length(tempst) == 1
  304. fprintf('Only one timepoint (%0.0f) was passed to the function.\n', tempst);
  305. fprintf('The initial timepoint is set to 0, so data between 0 and %0.0f will be read.\n', tempst);
  306. tempst(2) = tempst;
  307. tempst(1) = 0;
  308. end
  309. readTime = [tempst(1), tempst(end)];
  310. Flags.SaveFile = 'nosave';
  311. Flags.NoMAT = 'nomat';
  312. elseif (strncmp(tempst, 'c:', 2) && tempst(3) ~= '\' && tempst(3) ~= '/')
  313. Flags.selChannels = str2num(tempst(3:end)); %#ok<ST2NM>
  314. else
  315. if ~isnumeric(varargin{i})
  316. disp(['Invalid argument ''' varargin{i} ''' .']);
  317. else
  318. disp(['Invalid argument ''' num2str(varargin{i}) ''' .']);
  319. end
  320. clear variables;
  321. if nargout
  322. varargout{1} = [];
  323. end
  324. return;
  325. end
  326. clear temp;
  327. end
  328. end; clear i;
  329. %% Defining and validating variables
  330. if ~exist('fileFullPath', 'var')
  331. if exist('getFile.m', 'file') == 2
  332. [fileName pathName] = getFile('*.nev*', 'Choose a NEV file...');
  333. else
  334. [fileName pathName] = uigetfile;
  335. end
  336. fileFullPath = [pathName fileName];
  337. if fileFullPath==0;
  338. clear variables;
  339. if nargout
  340. varargout{1} = [];
  341. end
  342. disp('No file was selected.');
  343. return
  344. end
  345. end
  346. [~, ~, fileExt] = fileparts(fileFullPath);
  347. %% Loading .x files for multiNSP configuration
  348. if strcmpi(fileExt(2:4), 'nev') && length(fileExt) == 5
  349. fileFullPath(1) = fileFullPath(end);
  350. fileFullPath(end) = [];
  351. end
  352. if ~isfield(Flags, 'Report'); Flags.Report = 'noreport'; end
  353. if ~isfield(Flags, 'WarningStat'); Flags.WarningStat = 'warning'; end;
  354. if ~isfield(Flags, 'ReadData'); Flags.ReadData = 'read'; end
  355. if ~isfield(Flags, 'ParseData'); Flags.ParseData = 'noparse'; end
  356. if ~isfield(Flags, 'SaveFile'); Flags.SaveFile = 'save'; end;
  357. if ~isfield(Flags, 'NoMAT'); Flags.NoMAT = 'yesmat'; end;
  358. if ~isfield(Flags, 'waveformUnits'); Flags.waveformUnits = 'raw'; end;
  359. if ~isfield(Flags, 'digIOBits'); Flags.digIOBits = '16bits'; end;
  360. if ~isfield(Flags, 'Overwrite'); Flags.Overwrite = 'nooverwrite'; end;
  361. if ~isfield(Flags, 'MultiNSP'); Flags.MultiNSP = 'multinsp'; end;
  362. if ~isfield(Flags, 'selChannels'); Flags.selChannels = 'all'; end;
  363. if ~isfield(Flags, 'Direct'); Flags.Direct = 'nodirect'; end;
  364. if strcmpi(Flags.Report, 'report')
  365. disp(['openNEV ' NEV.MetaTags.openNEVver]);
  366. end
  367. syncShift = 0;
  368. % Check to see if 512 setup and calculate offset
  369. if strcmpi(Flags.MultiNSP, 'multinsp')
  370. fiveTwelveFlag = regexp(fileFullPath, '-i[0123]-');
  371. if ~isempty(fiveTwelveFlag)
  372. syncShift = multiNSPSync(fileFullPath);
  373. else
  374. Flags.MultiNSP = 'no';
  375. end
  376. end
  377. %% Validating existance of parseCommand
  378. if strcmpi(Flags.ParseData, 'parse')
  379. if exist('parseCommand.m', 'file') ~= 2
  380. disp('This version of openNEV requires function parseCommand.m to be placed in path.');
  381. clear variables;
  382. if nargout
  383. varargout{1} = [];
  384. end
  385. return;
  386. end
  387. end
  388. tic;
  389. matPath = [fileFullPath(1:end-4) '.mat'];
  390. %% Check for a MAT file and load that instead of NEV
  391. if exist(matPath, 'file') == 2 && strcmpi(Flags.NoMAT, 'yesmat') && strcmpi(Flags.WarningStat, 'warning')
  392. disp('MAT file corresponding to selected NEV file already exists. Loading MAT instead...');
  393. load(matPath);
  394. NEV.MetaTags.FilePath = fileFullPath;
  395. if isempty(NEV.Data.Spikes.Waveform) && strcmpi(Flags.ReadData, 'read') && strcmpi(Flags.WarningStat, 'warning')
  396. disp('The MAT file does not waveforms. Loading NEV instead...');
  397. else
  398. NEV = killUnwantedChannels(NEV, Flags.selChannels);
  399. if ~nargout
  400. assignin('base', 'NEV', NEV);
  401. clear variables;
  402. else
  403. varargout{1} = NEV;
  404. end
  405. return;
  406. end
  407. end
  408. %% Reading BasicHeader information from file
  409. FID = fopen(fileFullPath, 'r', 'ieee-le');
  410. BasicHeader = fread(FID, 336, '*uint8');
  411. NEV.MetaTags.FileTypeID = char(BasicHeader(1:8)');
  412. NEV.MetaTags.FileSpec = [num2str(double(BasicHeader(9))) '.' num2str(double(BasicHeader(10)))];
  413. NEV.MetaTags.Flags = dec2bin(double(typecast(BasicHeader(11:12), 'uint16')),16);
  414. Trackers.fExtendedHeader = double(typecast(BasicHeader(13:16), 'uint32'));
  415. NEV.MetaTags.HeaderOffset = Trackers.fExtendedHeader;
  416. Trackers.countPacketBytes = double(typecast(BasicHeader(17:20), 'uint32'));
  417. NEV.MetaTags.PacketBytes = Trackers.countPacketBytes;
  418. NEV.MetaTags.TimeRes = double(typecast(BasicHeader(21:24), 'uint32'));
  419. NEV.MetaTags.SampleRes = typecast(BasicHeader(25:28), 'uint32');
  420. t = double(typecast(BasicHeader(29:44), 'uint16'));
  421. tempApp = BasicHeader(45:76)';
  422. tempApp(find(tempApp == 0):end) = [];
  423. NEV.MetaTags.Application = char(tempApp); clear tempApp;
  424. NEV.MetaTags.Comment = char(BasicHeader(77:332)');
  425. [NEV.MetaTags.FilePath, NEV.MetaTags.Filename, NEV.MetaTags.FileExt] = fileparts(fileFullPath);
  426. Trackers.countExtHeader = typecast(BasicHeader(333:336), 'uint32');
  427. clear BasicHeader;
  428. if or(strcmpi(NEV.MetaTags.FileTypeID, 'NEURALEV'), strcmpi(NEV.MetaTags.FileTypeID, 'BREVENTS'))
  429. if exist([fileFullPath(1:end-8) '.sif'], 'file') == 2
  430. METATAGS = textread([fileFullPath(1:end-8) '.sif'], '%s');
  431. NEV.MetaTags.Subject = METATAGS{3}(5:end-5);
  432. NEV.MetaTags.Experimenter = [METATAGS{5}(8:end-8) ' ' METATAGS{6}(7:end-7)];
  433. end
  434. end
  435. if ~any(strcmpi(NEV.MetaTags.FileSpec, {'2.1', '2.2', '2.3', '3.0'}))
  436. disp('Unknown filespec. Cannot open file.');
  437. fclose FID;
  438. clear variables;
  439. if nargout
  440. varargout{1} = [];
  441. end
  442. return;
  443. end
  444. clear fileFullPath;
  445. %% Parsing and validating FileSpec and DateTime variables
  446. NEV.MetaTags.DateTimeRaw = t.';
  447. NEV.MetaTags.DateTime = datestr(datenum(t(1), t(2), t(4), t(5), t(6), t(7)));
  448. clear t;
  449. %% Removing extra garbage characters from the Comment field.
  450. NEV.MetaTags.Comment(find(NEV.MetaTags.Comment==0,1):end) = 0;
  451. %% Recording after BasicHeader file position
  452. Trackers.fBasicHeader = ftell(FID); %#ok<NASGU>
  453. % Calculating the length of the data
  454. currentLocation = ftell(FID);
  455. fseek(FID, -Trackers.countPacketBytes, 'eof');
  456. NEV.MetaTags.DataDuration = fread(FID, 1, 'uint32=>double');
  457. NEV.MetaTags.DataDurationSec = double(NEV.MetaTags.DataDuration) / double(NEV.MetaTags.SampleRes);
  458. fseek(FID, currentLocation, 'bof');
  459. %% Reading ExtendedHeader information
  460. for ii=1:Trackers.countExtHeader
  461. ExtendedHeader = fread(FID, 32, '*uint8');
  462. PacketID = char(ExtendedHeader(1:8)');
  463. switch PacketID
  464. case 'ARRAYNME'
  465. NEV.ArrayInfo.ElectrodeName = char(ExtendedHeader(9:end));
  466. case 'ECOMMENT'
  467. NEV.ArrayInfo.ArrayComment = char(ExtendedHeader(9:end));
  468. case 'CCOMMENT'
  469. NEV.ArrayInfo.ArrayCommentCont = char(ExtendedHeader(9:end));
  470. case 'MAPFILE'
  471. NEV.ArrayInfo.MapFile = char(ExtendedHeader(9:end));
  472. case 'NEUEVWAV'
  473. ElectrodeID = typecast(ExtendedHeader(9:10), 'uint16');
  474. NEV.ElectrodesInfo(ElectrodeID).ElectrodeID = ElectrodeID;
  475. NEV.ElectrodesInfo(ElectrodeID).ConnectorBank = char(ExtendedHeader(11)+64);
  476. NEV.ElectrodesInfo(ElectrodeID).ConnectorPin = ExtendedHeader(12);
  477. df = typecast(ExtendedHeader(13:14),'int16');
  478. % This is a workaround for the DigitalFactor overflow in NEV
  479. % files. Remove once Central is updated
  480. if df == 21516
  481. NEV.ElectrodesInfo(ElectrodeID).DigitalFactor = 152592.547;
  482. else
  483. NEV.ElectrodesInfo(ElectrodeID).DigitalFactor = df;
  484. end
  485. % End of workaround
  486. NEV.ElectrodesInfo(ElectrodeID).EnergyThreshold = typecast(ExtendedHeader(15:16),'uint16');
  487. NEV.ElectrodesInfo(ElectrodeID).HighThreshold = typecast(ExtendedHeader(17:18),'int16');
  488. NEV.ElectrodesInfo(ElectrodeID).LowThreshold = typecast(ExtendedHeader(19:20),'int16');
  489. NEV.ElectrodesInfo(ElectrodeID).Units = ExtendedHeader(21);
  490. NEV.ElectrodesInfo(ElectrodeID).WaveformBytes = ExtendedHeader(22);
  491. clear ElectrodeID;
  492. case 'NEUEVLBL'
  493. ElectrodeID = typecast(ExtendedHeader(9:10), 'uint16');
  494. NEV.ElectrodesInfo(ElectrodeID).ElectrodeLabel = char(ExtendedHeader(11:26));
  495. clear ElectrodeID;
  496. case 'NEUEVFLT'
  497. ElectrodeID = typecast(ExtendedHeader(9:10), 'uint16');
  498. NEV.ElectrodesInfo(ElectrodeID).HighFreqCorner = typecast(ExtendedHeader(11:14),'uint32');
  499. NEV.ElectrodesInfo(ElectrodeID).HighFreqOrder = typecast(ExtendedHeader(15:18),'uint32');
  500. NEV.ElectrodesInfo(ElectrodeID).HighFilterType = typecast(ExtendedHeader(19:20),'uint16');
  501. NEV.ElectrodesInfo(ElectrodeID).LowFreqCorner = typecast(ExtendedHeader(21:24),'uint32');
  502. NEV.ElectrodesInfo(ElectrodeID).LowFreqOrder = typecast(ExtendedHeader(25:28),'uint32');
  503. NEV.ElectrodesInfo(ElectrodeID).LowFilterType = typecast(ExtendedHeader(29:30),'uint16');
  504. clear ElectrodeID;
  505. case 'DIGLABEL'
  506. Mode = ExtendedHeader(25);
  507. NEV.IOLabels{Mode+1} = char(ExtendedHeader(9:24).');
  508. clear Mode;
  509. case 'NSASEXEV' %% Not implemented in the Cerebus firmware.
  510. %% Needs to be updated once implemented into the
  511. %% firmware by Blackrock Microsystems.
  512. NEV.NSAS.Freq = typecast(ExtendedHeader(9:10),'uint16');
  513. NEV.NSAS.DigInputConf = char(ExtendedHeader(11));
  514. NEV.NSAS.AnalCh1Conf = char(ExtendedHeader(12));
  515. NEV.NSAS.AnalCh1Detect = typecast(ExtendedHeader(13:14),'uint16');
  516. NEV.NSAS.AnalCh2Conf = char(ExtendedHeader(15));
  517. NEV.NSAS.AnalCh2Detect = typecast(ExtendedHeader(16:17),'uint16');
  518. NEV.NSAS.AnalCh3Conf = char(ExtendedHeader(18));
  519. NEV.NSAS.AnalCh3Detect = typecast(ExtendedHeader(19:20),'uint16');
  520. NEV.NSAS.AnalCh4Conf = char(ExtendedHeader(21));
  521. NEV.NSAS.AnalCh4Detect = typecast(ExtendedHeader(22:23),'uint16');
  522. NEV.NSAS.AnalCh5Conf = char(ExtendedHeader(24));
  523. NEV.NSAS.AnalCh5Detect = typecast(ExtendedHeader(25:26),'uint16');
  524. case 'VIDEOSYN'
  525. cnt = 1;
  526. if (isfield(NEV, 'VideoSyncInfo'))
  527. cnt = size(NEV.VideoSyncInfo, 2) + 1;
  528. end
  529. NEV.VideoSyncInfo(cnt).SourceID = typecast(ExtendedHeader(9:10),'uint16');
  530. NEV.VideoSyncInfo(cnt).SourceName = char(ExtendedHeader(11:26))';
  531. NEV.VideoSyncInfo(cnt).FrameRateFPS = typecast(ExtendedHeader(27:30),'single')';
  532. clear cnt;
  533. case 'TRACKOBJ'
  534. cnt = 1;
  535. if (isfield(NEV, 'ObjTrackInfo'))
  536. cnt = size(NEV.ObjTrackInfo, 2) + 1;
  537. end
  538. NEV.ObjTrackInfo(cnt).TrackableType = typecast(ExtendedHeader(9:10),'uint16');
  539. NEV.ObjTrackInfo(cnt).TrackableID = typecast(ExtendedHeader(11:14), 'uint32');
  540. NEV.ObjTrackInfo(cnt).TrackableName = char(ExtendedHeader(15:30))';
  541. clear cnt;
  542. otherwise
  543. disp(['PacketID ' PacketID ' is invalid.']);
  544. disp('Please make sure this version of openNEV is compatible with your current NSP firmware.')
  545. fclose(FID);
  546. clear variables;
  547. if nargout
  548. varargout{1} = [];
  549. end
  550. return;
  551. end
  552. end
  553. NEV.MetaTags.ChannelID = [NEV.ElectrodesInfo.ElectrodeID];
  554. clear ExtendedHeader PacketID ii;
  555. %% Recording after ExtendedHeader file position and calculating Data Length
  556. % and number of data packets
  557. fseek(FID, 0, 'eof');
  558. Trackers.fData = ftell(FID);
  559. Trackers.countDataPacket = (Trackers.fData - Trackers.fExtendedHeader)/Trackers.countPacketBytes;
  560. NEV.MetaTags.PacketCount = Trackers.countDataPacket;
  561. %%
  562. Flags.UnparsedDigitalData = 0;
  563. %% Reading packet headers and digital values
  564. Timestamp = [];
  565. PacketIDs = [];
  566. tempClassOrReason = [];
  567. tempDigiVals = [];
  568. if NEV.MetaTags.PacketCount ~= 0
  569. fseek(FID, Trackers.fExtendedHeader, 'bof');
  570. if strcmpi(NEV.MetaTags.FileTypeID, 'NEURALEV')
  571. tRawData = fread(FID, [10 Trackers.countDataPacket], '10*uint8=>uint8', Trackers.countPacketBytes - 10);
  572. Timestamp = tRawData(1:4,:);
  573. Timestamp = typecast(Timestamp(:), 'uint32').' + syncShift;
  574. timeStampBytes = 4;
  575. elseif strcmpi(NEV.MetaTags.FileTypeID, 'BREVENTS')
  576. tRawData = fread(FID, [14 Trackers.countDataPacket], '14*uint8=>uint8', Trackers.countPacketBytes - 14);
  577. Timestamp = tRawData(1:8,:);
  578. Timestamp = typecast(Timestamp(:), 'uint64').' + syncShift;
  579. timeStampBytes = 8;
  580. end
  581. %% Calculate the number of packets that need to be read based on the time input parameters
  582. if ~exist('readTime', 'var')
  583. Trackers.readPackets = [1, length(Timestamp)];
  584. else
  585. [tmp,tempReadPackets] = find(Timestamp > readTime(1)*NEV.MetaTags.SampleRes,1,'first');
  586. if ~isempty(tempReadPackets)
  587. Trackers.readPackets(1) = tempReadPackets;
  588. else
  589. Trackers.readPackets(1) = NaN;
  590. end
  591. if isnan(Trackers.readPackets(1))
  592. fprintf('The file contains %0.2f seconds of data. The requested begining timestamp of %0.2f seconds is longer than the duration.\n', ...
  593. double(Timestamp(end))/double(NEV.MetaTags.SampleRes), ...
  594. readTime(1));
  595. clear variables;
  596. if nargout
  597. varargout{1} = [];
  598. end
  599. return;
  600. end
  601. [tmp,tempReadPackets] = find(Timestamp < readTime(2)*NEV.MetaTags.SampleRes,1,'last');
  602. if ~isempty(tempReadPackets)
  603. if readTime(2)*NEV.MetaTags.SampleRes > Timestamp(end)
  604. 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', ...
  605. double(Timestamp(end))/double(NEV.MetaTags.SampleRes), ...
  606. readTime(2),...
  607. double(Timestamp(end))/double(NEV.MetaTags.SampleRes));
  608. end
  609. Trackers.readPackets(2) = tempReadPackets;
  610. else
  611. Trackers.readPackets(2) = NaN;
  612. end
  613. clear tmp, tempReadPackets;
  614. end
  615. PacketIDs = tRawData(timeStampBytes+1:timeStampBytes+2,Trackers.readPackets(1):Trackers.readPackets(2));
  616. PacketIDs = typecast(PacketIDs(:), 'uint16').';
  617. tempClassOrReason = uint8(tRawData(timeStampBytes+3,Trackers.readPackets(1):Trackers.readPackets(2)));
  618. if strcmpi(Flags.digIOBits, '16bits')
  619. tempDigiVals = tRawData(timeStampBytes+5:timeStampBytes+6,Trackers.readPackets(1):Trackers.readPackets(2));
  620. tempDigiVals = typecast(tempDigiVals(:), 'uint16');
  621. else
  622. tempDigiVals = uint16(tRawData(timeStampBytes+5,Trackers.readPackets(1):Trackers.readPackets(2)));
  623. end
  624. clear tRawData;
  625. else
  626. Trackers.readPackets = zeros(1,2);
  627. end
  628. %% Defining PacketID constants
  629. digserPacketID = 0;
  630. neuralIndicesPacketIDBounds = [1, 16384];
  631. commentPacketID = 65535;
  632. videoSyncPacketID = 65534;
  633. trackingPacketID = 65533;
  634. patientTrigPacketID = 65532;
  635. logEventPacketID = 65531;
  636. reconfigPacketID = 65530;
  637. recEventPacketID = 65529;
  638. %% Parse read digital data. Please refer to help to learn about the proper
  639. % formatting if the data.
  640. digserIndices = find(PacketIDs == digserPacketID);
  641. neuralIndices = find(neuralIndicesPacketIDBounds(2) >= PacketIDs & PacketIDs >= neuralIndicesPacketIDBounds(1));
  642. commentIndices = find(PacketIDs == commentPacketID);
  643. videoSyncPacketIDIndices = find(PacketIDs == videoSyncPacketID);
  644. trackingPacketIDIndices = find(PacketIDs == trackingPacketID);
  645. patientTrigPacketIDIndices = find(PacketIDs == patientTrigPacketID);
  646. logEventPacketIDIndices = find(PacketIDs == logEventPacketID);
  647. reconfigPacketIDIndices = find(PacketIDs == reconfigPacketID);
  648. recEventPacketIDIndices = find(PacketIDs == recEventPacketID);
  649. clear digserPacketID neuralIndicesPacketIDBounds commentPacketID ...
  650. videoSyncPacketID trackingPacketID patientTrigPacketID reconfigPacketID;
  651. digserTimestamp = Timestamp(digserIndices);
  652. NEV.Data.Spikes.TimeStamp = Timestamp(neuralIndices);
  653. NEV.Data.Spikes.Electrode = PacketIDs(neuralIndices);
  654. clear PacketIDs;
  655. NEV.Data.Spikes.Unit = tempClassOrReason(neuralIndices);
  656. %clear neuralIndices;
  657. NEV.Data.SerialDigitalIO.InsertionReason = tempClassOrReason(digserIndices);
  658. clear tempClassOrReason;
  659. DigiValues = tempDigiVals(digserIndices);
  660. clear tempDigiVals;
  661. %% Reads the waveforms if 'read' is passed to the function
  662. if strcmpi(Flags.ReadData, 'read')
  663. allExtraDataPacketIndices = [commentIndices, ...
  664. videoSyncPacketIDIndices, ...
  665. trackingPacketIDIndices, ...
  666. patientTrigPacketIDIndices, ...
  667. logEventPacketIDIndices,...
  668. reconfigPacketIDIndices,...
  669. recEventPacketIDIndices];
  670. if ~isempty(allExtraDataPacketIndices) % if there is any extra packets
  671. fseek(FID, Trackers.fExtendedHeader, 'bof');
  672. fseek(FID, (Trackers.readPackets(1)-1) * Trackers.countPacketBytes, 'cof');
  673. tRawData = fread(FID, [Trackers.countPacketBytes Trackers.readPackets(2)], ...
  674. [num2str(Trackers.countPacketBytes) '*uint8=>uint8'], 0);
  675. if ~isempty(commentIndices)
  676. [NEV.Data.Comments.TimeStamp, orderOfTS] = sort(Timestamp(commentIndices));
  677. NEV.Data.Comments.TimeStampSec = double(NEV.Data.Comments.TimeStamp)/double(NEV.MetaTags.TimeRes);
  678. tempCharSet = tRawData(timeStampBytes+3, commentIndices);
  679. NEV.Data.Comments.CharSet = tempCharSet(orderOfTS); clear tempCharSet;
  680. colorFlag = tRawData(timeStampBytes+4, commentIndices);
  681. NEV.Data.Comments.TimeStampStarted = tRawData(timeStampBytes+5:timeStampBytes+8, commentIndices);
  682. tempTimeStampStarted = typecast(NEV.Data.Comments.TimeStampStarted(:), 'uint32').';
  683. NEV.Data.Comments.TimeStampStarted = tempTimeStampStarted(orderOfTS); clear tempTimeStampStarted;
  684. tempText = char(tRawData(timeStampBytes+9:Trackers.countPacketBytes, commentIndices).');
  685. NEV.Data.Comments.Text = tempText(orderOfTS,:); clear tempText;
  686. % Transferring NeuroMotive Events to its own structure
  687. neuroMotiveEvents = find(NEV.Data.Comments.CharSet == 255);
  688. NEV.Data.TrackingEvents.TimeStamp = NEV.Data.Comments.TimeStamp(neuroMotiveEvents);
  689. NEV.Data.TrackingEvents.TimeStampSec = double(NEV.Data.TrackingEvents.TimeStamp)/double(NEV.MetaTags.TimeRes);
  690. % Parsing NeuroMotive Events
  691. events = NEV.Data.Comments.Text(neuroMotiveEvents,:);
  692. for idx = 1:size(events,1)
  693. splitEvent = strsplit(events(idx,:), ':');
  694. NEV.Data.TrackingEvents.ROIName{idx} = splitEvent{1};
  695. NEV.Data.TrackingEvents.ROINum(idx) = str2double(splitEvent{2});
  696. NEV.Data.TrackingEvents.Event{idx} = splitEvent{3};
  697. NEV.Data.TrackingEvents.Frame(idx) = str2double(splitEvent{4});
  698. end
  699. NEV.Data.Comments.TimeStamp(neuroMotiveEvents) = [];
  700. NEV.Data.Comments.TimeStampSec(neuroMotiveEvents) = [];
  701. NEV.Data.Comments.CharSet(neuroMotiveEvents) = [];
  702. NEV.Data.Comments.TimeStampStarted(neuroMotiveEvents) = [];
  703. NEV.Data.Comments.TimeStampStartedSec = double(NEV.Data.Comments.TimeStampStarted)/double(NEV.MetaTags.TimeRes);
  704. NEV.Data.Comments.Text(neuroMotiveEvents,:) = [];
  705. colorFlag(neuroMotiveEvents) = [];
  706. % Figuring out the text color of the comments that had color
  707. NEV.Data.Comments.Color = dec2hex(NEV.Data.Comments.TimeStampStarted);
  708. NEV.Data.Comments.Color(colorFlag == 1,:) = repmat('0', size(NEV.Data.Comments.Color(colorFlag == 1,:)));
  709. NEV.Data.Comments.TimeStampStarted(colorFlag == 0) = NEV.Data.Comments.TimeStamp(colorFlag == 0);
  710. clear commentIndices;
  711. end
  712. if ~isempty(videoSyncPacketIDIndices)
  713. NEV.Data.VideoSync.TimeStamp = Timestamp(videoSyncPacketIDIndices);
  714. NEV.Data.VideoSync.FileNumber = tRawData(timeStampBytes+3:timeStampBytes+4, videoSyncPacketIDIndices);
  715. NEV.Data.VideoSync.FileNumber = typecast(NEV.Data.VideoSync.FileNumber(:), 'uint16').';
  716. NEV.Data.VideoSync.FrameNumber = tRawData(timeStampBytes+5:timeStampBytes+8, videoSyncPacketIDIndices);
  717. NEV.Data.VideoSync.FrameNumber = typecast(NEV.Data.VideoSync.FrameNumber(:), 'uint32').';
  718. NEV.Data.VideoSync.ElapsedTime = tRawData(timeStampBytes+9:timeStampBytes+12, videoSyncPacketIDIndices);
  719. NEV.Data.VideoSync.ElapsedTime = typecast(NEV.Data.VideoSync.ElapsedTime(:), 'uint32').';
  720. NEV.Data.VideoSync.SourceID = tRawData(timeStampBytes+13:timeStampBytes+16, videoSyncPacketIDIndices);
  721. NEV.Data.VideoSync.SourceID = typecast(NEV.Data.VideoSync.SourceID(:), 'uint32').';
  722. clear videoSyncPacketIDIndices;
  723. end
  724. if ~isempty(trackingPacketIDIndices)
  725. tmp.TimeStamp = Timestamp(trackingPacketIDIndices);
  726. tmp.TimeStampSec = double(tmp.TimeStamp)/30000;
  727. % This portion is commented out because it does not contain any
  728. % information as of yet.
  729. tmp.ParentID = tRawData(timeStampBytes+3:timeStampBytes+4, trackingPacketIDIndices);
  730. tmp.ParentID = typecast(tmp.ParentID(:), 'uint16').';
  731. tmp.NodeID = tRawData(timeStampBytes+5:timeStampBytes+6, trackingPacketIDIndices);
  732. tmp.NodeID = typecast(tmp.NodeID(:), 'uint16').';
  733. tmp.NodeCount = tRawData(timeStampBytes+7:timeStampBytes+8, trackingPacketIDIndices);
  734. tmp.NodeCount = typecast(tmp.NodeCount(:), 'uint16').';
  735. tmp.MarkerCount = tRawData(timeStampBytes+9:timeStampBytes+10, trackingPacketIDIndices);
  736. tmp.MarkerCount = typecast(tmp.MarkerCount(:), 'uint16').';
  737. tmp.rigidBodyPoints = tRawData(timeStampBytes+11:NEV.MetaTags.PacketBytes, trackingPacketIDIndices);
  738. tmp.rigidBodyPoints = reshape(typecast(tmp.rigidBodyPoints(:), 'uint16'), size(tmp.rigidBodyPoints, 1)/2, size(tmp.rigidBodyPoints, 2));
  739. if (isfield(NEV, 'ObjTrackInfo'))
  740. for IDX = 1:size(NEV.ObjTrackInfo,2)
  741. emptyChar = find(NEV.ObjTrackInfo(IDX).TrackableName == 0, 1);
  742. NEV.ObjTrackInfo(IDX).TrackableName(emptyChar:end) = [];
  743. if ~(~isempty(strfind(NEV.ObjTrackInfo(IDX).TrackableName, '1')) || ...
  744. ~isempty(strfind(NEV.ObjTrackInfo(IDX).TrackableName, '2')) || ...
  745. ~isempty(strfind(NEV.ObjTrackInfo(IDX).TrackableName, '3')) || ...
  746. ~isempty(strfind(NEV.ObjTrackInfo(IDX).TrackableName, '4')))
  747. nameLength = min(length(NEV.ObjTrackInfo(IDX-1).TrackableName(1:end-1)), length(NEV.ObjTrackInfo(IDX).TrackableName(1:end-1)));
  748. if ~strcmpi(NEV.ObjTrackInfo(IDX-1).TrackableName(1:nameLength-1), NEV.ObjTrackInfo(IDX).TrackableName(1:nameLength-1))
  749. objectIndex = 1;
  750. else
  751. objectIndex = objectIndex + 1;
  752. end
  753. NEV.ObjTrackInfo(IDX).TrackableName(emptyChar) = num2str(objectIndex);
  754. end
  755. indicesOfEvent = find(tmp.NodeID == IDX-1);
  756. if ~isempty(indicesOfEvent)
  757. NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).TimeStamp = tmp.TimeStamp(indicesOfEvent);
  758. NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).TimeStampSec = tmp.TimeStampSec(indicesOfEvent);
  759. NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).ParentID = tmp.ParentID(indicesOfEvent);
  760. NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).NodeCount = tmp.NodeCount(indicesOfEvent);
  761. NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).MarkerCount = tmp.MarkerCount(indicesOfEvent);
  762. NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).MarkerCoordinates(size(NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).TimeStamp,2)).X = [];
  763. NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).MarkerCoordinates(size(NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).TimeStamp,2)).X = [];
  764. for xyIDX = 1:size(NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).TimeStamp,2)
  765. NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).MarkerCoordinates(xyIDX).X = ...
  766. tmp.rigidBodyPoints(1:2:NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).MarkerCount(xyIDX)*2, indicesOfEvent(xyIDX));
  767. NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).MarkerCoordinates(xyIDX).Y = ...
  768. tmp.rigidBodyPoints(2:2:NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).MarkerCount(xyIDX)*2, indicesOfEvent(xyIDX));
  769. end
  770. end
  771. end
  772. end
  773. clear trackingPacketIDIndices tmp;
  774. end
  775. if ~isempty(patientTrigPacketIDIndices)
  776. NEV.Data.PatientTrigger.TimeStamp = Timestamp(patientTrigPacketIDIndices);
  777. NEV.Data.PatientTrigger.TriggerType = tRawData(timeStampBytes+3:timeStampBytes+4, patientTrigPacketIDIndices);
  778. NEV.Data.PatientTrigger.TriggerType = typecast(NEV.Data.PatientTrigger.TriggerType(:), 'uint16').';
  779. clear patientTrigPacketIDIndices;
  780. end
  781. if ~isempty(reconfigPacketIDIndices)
  782. NEV.Data.Reconfig.TimeStamp = Timestamp(reconfigPacketIDIndices);
  783. NEV.Data.Reconfig.ChangeType = tRawData(timeStampBytes+3:timeStampBytes+4, reconfigPacketIDIndices);
  784. NEV.Data.Reconfig.ChangeType = typecast(NEV.Data.Reconfig.ChangeType(:), 'uint16').';
  785. NEV.Data.Reconfig.CompName = char(tRawData(timeStampBytes+5:timeStampBytes+20, reconfigPacketIDIndices));
  786. NEV.Data.Reconfig.ConfigChanged = char(tRawData(timeStampBytes+21:Trackers.countPacketBytes, reconfigPacketIDIndices));
  787. clear reconfigPacketIDIndices;
  788. end
  789. if ~isempty(logEventPacketIDIndices)
  790. NEV.Data.LogEvent.TimeStamp = Timestamp(logEventPacketIDIndices);
  791. tmp.Mode = tRawData(timeStampBytes+3:timeStampBytes+4, logEventPacketIDIndices);
  792. NEV.Data.LogEvent.Mode = typecast(tmp.Mode(:), 'uint16').';
  793. NEV.Data.LogEvent.Application = char(tRawData(timeStampBytes+5:timeStampBytes+20, logEventPacketIDIndices).');
  794. end
  795. if ~isempty(recEventPacketIDIndices)
  796. NEV.Data.RecordingEvents.TimeStamp = Timestamp(recEventPacketIDIndices);
  797. tmp.EventCode = tRawData(timeStampBytes+3:timeStampBytes+4, recEventPacketIDIndices);
  798. NEV.Data.RecordingEvents.EventCode = typecast(tmp.EventCode(:), 'uint16').';
  799. end
  800. end % end if ~isempty(allExtraDataPacketIndices)
  801. clear Timestamp tRawData count idx;
  802. % now read waveform
  803. if strcmpi(NEV.MetaTags.FileTypeID, 'NEURALEV')
  804. hOffset = 8;
  805. elseif strcmpi(NEV.MetaTags.FileTypeID, 'BREVENTS')
  806. hOffset = 12;
  807. end
  808. fseek(FID, Trackers.fExtendedHeader + hOffset, 'bof'); % Seek to location of spikes
  809. fseek(FID, (Trackers.readPackets(1)-1) * Trackers.countPacketBytes, 'cof');
  810. NEV.Data.Spikes.WaveformUnit = Flags.waveformUnits;
  811. NEV.Data.Spikes.Waveform = fread(FID, [(Trackers.countPacketBytes-hOffset)/2 Trackers.readPackets(2)], ...
  812. [num2str((Trackers.countPacketBytes-hOffset)/2) '*int16=>int16'], hOffset);
  813. NEV.Data.Spikes.Waveform(:, [digserIndices allExtraDataPacketIndices]) = [];
  814. clear allExtraDataPacketIndices;
  815. if strcmpi(Flags.waveformUnits, 'uv')
  816. elecDigiFactors = double(1000./[NEV.ElectrodesInfo(NEV.Data.Spikes.Electrode).DigitalFactor]);
  817. NEV.Data.Spikes.Waveform = bsxfun(@rdivide, double(NEV.Data.Spikes.Waveform), elecDigiFactors);
  818. if strcmpi(Flags.WarningStat, 'warning')
  819. fprintf(1,'\nThe spike waveforms are in unit of uV.\n');
  820. fprintf(2,'WARNING: This conversion may lead to loss of information.');
  821. fprintf(1,'\nRefer to help for more information.\n');
  822. end
  823. end
  824. end
  825. clear digserIndices;
  826. %% Parse digital data if requested
  827. if ~isempty(DigiValues)
  828. if strcmpi(Flags.ParseData, 'parse')
  829. try
  830. DigiValues = char(DigiValues);
  831. Inputs = {'Digital'; 'AnCh1'; 'AnCh2'; 'AnCh3'; 'AnCh4'; 'AnCh5'; 'PerSamp'; 'Serial'};
  832. AsteriskIndices = find(DigiValues == '*');
  833. DataBegTimestamp = digserTimestamp(AsteriskIndices);
  834. splitDigiValues = regexp(DigiValues(2:end), '*', 'split')';
  835. for idx = 1:length(splitDigiValues)
  836. try
  837. if isempty(find(splitDigiValues{idx} == ':', 1))
  838. splitDigiValues{idx}(find(splitDigiValues{idx} == '#')) = [];
  839. NEV.Data.SerialDigitalIO(idx).Value = splitDigiValues{idx};
  840. NEV.Data.SerialDigitalIO(idx).Type = 'Marker';
  841. else
  842. [tempParsedCommand error] = parseCommand(splitDigiValues{idx});
  843. if ~error
  844. pcFields = fields(tempParsedCommand);
  845. NEV.Data.SerialDigitalIO(idx).Value = splitDigiValues{idx};
  846. for fidx = 1:length(pcFields)
  847. NEV.Data.SerialDigitalIO(idx).(pcFields{fidx}) = tempParsedCommand.(pcFields{fidx});
  848. end
  849. else
  850. NEV.Data.SerialDigitalIO(idx).Value = splitDigiValues{idx};
  851. NEV.Data.SerialDigitalIO(idx).Type = 'UnparsedData';
  852. Flags.UnparsedDigitalData = 1;
  853. end
  854. end
  855. catch
  856. disp(['Error parsing: ' splitDigiValues{idx}]);
  857. disp('Please refer to the help for more information on how to properly format the digital data for parsing.');
  858. end
  859. end
  860. % Populate the NEV structure with Timestamp and inputtypes for the
  861. % digital data
  862. if ~isempty(DataBegTimestamp)
  863. c = num2cell(DataBegTimestamp); [NEV.Data.SerialDigitalIO(1:length(NEV.Data.SerialDigitalIO)).TimeStamp] = deal(c{1:end});
  864. c = num2cell(DataBegTimestamp/NEV.MetaTags.SampleRes); [NEV.Data.SerialDigitalIO.TimeStampSec] = deal(c{1:end});
  865. c = {Inputs{NEV.Data.SerialDigitalIO.InsertionReason(AsteriskIndices)}}; [NEV.Data.SerialDigitalIO.InputType] = deal(c{1:end});
  866. end
  867. clear Inputs DigiValues digserTimestamp;
  868. catch
  869. disp(lasterr);
  870. disp('An error occured during reading digital data. This is due to a problem with formatting digital data.');
  871. disp('Refer to help ''help openNEV'' for more information on how to properly format the digital data.');
  872. disp('Try using openNEV with ''noparse'', i.e. openNEV(''noparse'').');
  873. end
  874. else
  875. NEV.Data.SerialDigitalIO.TimeStamp = digserTimestamp;
  876. NEV.Data.SerialDigitalIO.TimeStampSec = double(digserTimestamp)/30000;
  877. NEV.Data.SerialDigitalIO.UnparsedData = DigiValues;
  878. if strcmpi(Flags.Direct, 'direct')
  879. % Finding the members that have bit 16 as the strobe high
  880. DShighs = find(NEV.Data.SerialDigitalIO.UnparsedData >= bin2dec('1000000000000000'));
  881. uniqueDShighs = DShighs([1; find(diff(DShighs)>1)+1]);
  882. DShighUniqueBin = dec2bin(NEV.Data.SerialDigitalIO.UnparsedData(uniqueDShighs));
  883. DShighUniqueDec = bin2dec(DShighUniqueBin(:,2:16));
  884. % Removing the non-strobed-high values from SerialDigitalIO
  885. extraMembers = setxor(uniqueDShighs, 1:length(NEV.Data.SerialDigitalIO.UnparsedData));
  886. NEV.Data.SerialDigitalIO.TimeStamp(extraMembers) = [];
  887. NEV.Data.SerialDigitalIO.TimeStampSec(extraMembers) = [];
  888. NEV.Data.SerialDigitalIO.UnparsedData = DShighUniqueDec;
  889. clear DShighs DShighUniqueBin DShighUniqueDec extraMembers;
  890. end
  891. clear digserTimestamp DigiValues
  892. end
  893. else
  894. if strcmpi(Flags.ReadData, 'read')
  895. if strcmpi(Flags.Report, 'report')
  896. disp('No digital data to read.');
  897. end
  898. end
  899. end
  900. if strcmpi(Flags.ParseData, 'parse')
  901. if Flags.UnparsedDigitalData && strcmpi(Flags.WarningStat, 'warning')
  902. fprintf(2, 'WARNING: The NEV file contains unparsed digital data.\n');
  903. end
  904. end
  905. %% Show a report if 'report' is passed as an argument
  906. if strcmpi(Flags.Report, 'report')
  907. % Displaying report
  908. disp( '*** FILE INFO **************************');
  909. disp(['File Name = ' NEV.MetaTags.Filename]);
  910. disp(['Filespec = ' NEV.MetaTags.FileSpec]);
  911. disp(['Data Duration (min) = ' num2str(round(NEV.MetaTags.DataDuration/NEV.MetaTags.SampleRes/60))]);
  912. disp(['Packet Counts = ' num2str(Trackers.countDataPacket)]);
  913. disp(' ');
  914. disp( '*** BASIC HEADER ***********************');
  915. disp(['Sample Resolution = ' num2str(NEV.MetaTags.SampleRes)]);
  916. disp(['Date and Time = ' NEV.MetaTags.DateTime]);
  917. disp(['Comment = ' NEV.MetaTags.Comment(1:64) ]);
  918. disp([' ' NEV.MetaTags.Comment(65:128) ]);
  919. disp([' ' NEV.MetaTags.Comment(129:192)]);
  920. disp([' ' NEV.MetaTags.Comment(193:256)]);
  921. disp(['The load time was for NEV file was ' num2str(toc, '%0.1f') ' seconds.']);
  922. end
  923. %% Saving the NEV structure as a MAT file for easy access
  924. if strcmpi(Flags.SaveFile, 'save')
  925. if exist(matPath, 'file') == 2 && strcmpi(Flags.Overwrite, 'nooverwrite')
  926. if strcmpi(Flags.WarningStat, 'warning')
  927. disp(['File ' matPath ' already exists.']);
  928. overWrite = input('Would you like to overwrite (Y/N)? ', 's');
  929. else
  930. overWrite = 'n';
  931. end
  932. if strcmpi(overWrite, 'y')
  933. if strcmpi(Flags.WarningStat, 'warning')
  934. disp('Saving MAT file. This may take a few seconds...');
  935. end
  936. save(matPath, 'NEV', '-v7.3');
  937. else
  938. if strcmpi(Flags.WarningStat, 'warning')
  939. disp('File was not overwritten.');
  940. end
  941. end
  942. elseif exist(matPath, 'file') == 2 && strcmpi(Flags.Overwrite, 'overwrite')
  943. if strcmpi(Flags.WarningStat, 'warning')
  944. disp(['File ' matPath ' already exists.']);
  945. disp('Overwriting the old MAT file. This may take a few seconds...');
  946. end
  947. save(matPath, 'NEV', '-v7.3');
  948. else
  949. if strcmpi(Flags.WarningStat, 'warning')
  950. disp('Saving MAT file. This may take a few seconds...');
  951. end
  952. save(matPath, 'NEV', '-v7.3');
  953. end
  954. clear overWrite;
  955. end
  956. NEV = killUnwantedChannels(NEV, Flags.selChannels);
  957. if ~nargout
  958. assignin('base', 'NEV', NEV);
  959. else
  960. varargout{1} = NEV;
  961. end
  962. fclose(FID);
  963. clear Flags Trackers FID matPath;
  964. function NEV = killUnwantedChannels(NEV, selectedChannels)
  965. if ~strcmpi(selectedChannels, 'all')
  966. if any(selectedChannels < 1)
  967. disp('Invalid channel. Channels cannot be 0 or negative values. Channel selection ignored.');
  968. else
  969. uniqueChannels = unique(NEV.Data.Spikes.Electrode);
  970. if ~isempty(setdiff(selectedChannels, uniqueChannels))
  971. disp('Some of the selected channels in c:xxx command are not in the data file. These will not be loaded.')
  972. end
  973. unWantedChannels = setdiff(uniqueChannels, selectedChannels);
  974. for idx = 1:length(unWantedChannels)
  975. NEV.Data.Spikes.Waveform(:, NEV.Data.Spikes.Electrode == unWantedChannels(idx)) = [];
  976. NEV.Data.Spikes.Unit(NEV.Data.Spikes.Electrode == unWantedChannels(idx)) = [];
  977. NEV.Data.Spikes.TimeStamp(NEV.Data.Spikes.Electrode == unWantedChannels(idx)) = [];
  978. NEV.Data.Spikes.Electrode(NEV.Data.Spikes.Electrode == unWantedChannels(idx)) = [];
  979. end
  980. end
  981. end