|
@@ -0,0 +1,819 @@
|
|
|
+function varargout = openNEV(varargin)
|
|
|
+
|
|
|
+% openNEV
|
|
|
+%
|
|
|
+% Opens an .nev file for reading, returns all file information in a NEV
|
|
|
+% structure. Works with File Spec 2.1 & 2.2 & 2.3.
|
|
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
|
+% Use OUTPUT = openNEV(fname, 'noread', 'report', 'noparse', 'nowarning',
|
|
|
+% 'nosave', 'nomat', 'uV', 'overwrite').
|
|
|
+%
|
|
|
+% NOTE: All input arguments are optional. Input arguments may be in any order.
|
|
|
+%
|
|
|
+% fname: Name of the file to be opened. If the fname is omitted
|
|
|
+% the user will be prompted to select a file using an open
|
|
|
+% file user interface.
|
|
|
+% DEFAULT: Will open Open File UI.
|
|
|
+%
|
|
|
+% 'noread': Will not read the spike waveforms if user passes this argument.
|
|
|
+% DEFAULT: will read spike waveform.
|
|
|
+%
|
|
|
+% 'report': Will show a summary report if user passes this argument.
|
|
|
+% DEFAULT: will not show report.
|
|
|
+%
|
|
|
+% 'parse': The code will not parse the experimental parameters in digital I/O.
|
|
|
+% See below for guidelines on how to format your parameters.
|
|
|
+% DEFAULT: will not parse the parameters.
|
|
|
+%
|
|
|
+% 'nowarning': The code will not give a warning if there is an error in
|
|
|
+% parsing.
|
|
|
+% DEFAULT: will give warning message.
|
|
|
+%
|
|
|
+% 'nosave': The code will not save a copy of the NEV structure as a
|
|
|
+% MAT file. By default the code will save a copy in the same
|
|
|
+% folder as the NEV file for easy future access.
|
|
|
+% DEFAULT: will save the MAT file.
|
|
|
+%
|
|
|
+% 'nomat': Will not look for a MAT file. This option will force
|
|
|
+% openNEV to open a NEV file instead of any available MAT
|
|
|
+% files.
|
|
|
+% DEFAULT: will load the MAT file if available.
|
|
|
+%
|
|
|
+% 'uV': Will read the spike waveforms in unit of uV instead of
|
|
|
+% raw values. Note that this conversion may lead to loss of
|
|
|
+% information (e.g. 15/4 = 4) since the waveforms type will
|
|
|
+% stay in int16. It's recommended to read raw spike
|
|
|
+% waveforms and then perform the conversion at a later
|
|
|
+% time.
|
|
|
+% DEFAULT: will read waveform information in raw.
|
|
|
+%
|
|
|
+% '8bits': Indicates that 8 bits on the digital IO port was used
|
|
|
+% instead of 16 bits.
|
|
|
+% DEFAULT: will assumes that 16 bits of digital IO were used.
|
|
|
+%
|
|
|
+% 't:': Indicats the time window of the NEV file to be read. For
|
|
|
+% example, if t: is set to 2 (i.e. 't:0.6')
|
|
|
+% then only the first 2 seconds of the file is to be read. If set
|
|
|
+% to 2-50 (i.e. 't:2:50) then the time between 2 seconds
|
|
|
+% and 50 seconds will be read.
|
|
|
+% DEFAULT: the entire file will be read if 't:xx:xx' is not
|
|
|
+% passed to the function.
|
|
|
+%
|
|
|
+% 'overwrite': If MATLAB loads a NEV file using 'nomat' and a MAT file
|
|
|
+% already exists, by default it will prompt the user to
|
|
|
+% allow for overwriting the old MAT. Passing the
|
|
|
+% 'overwrite' flag will automatically overwrite the newly
|
|
|
+% opened NEV file ont the old MAT file.
|
|
|
+% DEFAULT: will ask the user whether to overwrite the old
|
|
|
+% MAT.
|
|
|
+%
|
|
|
+% OUTPUT: Contains the NEV structure.
|
|
|
+%
|
|
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
|
+% USAGE EXAMPLE:
|
|
|
+%
|
|
|
+% openNEV('report','read');
|
|
|
+%
|
|
|
+% In the example above, the file dialogue will prompt for a file. A
|
|
|
+% report of the file contents will be shown. The digital data will not be
|
|
|
+% parsed. The data needs to be in the proper format (refer below). The
|
|
|
+% spike waveforms are in raw units and not in uV.
|
|
|
+%
|
|
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
|
+% DIGITAL PARAMETERS/MARKERS FORMAT:
|
|
|
+%
|
|
|
+% In order for this function to parse your experimental parameters they
|
|
|
+% need to be in the following format:
|
|
|
+%
|
|
|
+% *ParamLabel:Parameter1=value1;Parameter2=value2;Parameter3=value3;#
|
|
|
+%
|
|
|
+% TWO EXAMPLES:
|
|
|
+% *ExpParameter:Intensity=1.02;Duration=400;Trials=1;PageSegment=14;#
|
|
|
+%
|
|
|
+% *Stimulation:StimCount=5;Duration=10;#
|
|
|
+%
|
|
|
+% In the first example, the parameter is of type "ExpParameter". The
|
|
|
+% parameters are, "Intensity, Duration, Trials, and PageSement." The
|
|
|
+% values of those parameters are, "1.02, 400, 1, and 14," respectively.
|
|
|
+% The second example is of type "Stimulation". The name of the parameters
|
|
|
+% are "StimCount" and "Duration" and the values are "5" and "10"
|
|
|
+% respectively.
|
|
|
+% -----------------------------------------------------------------------
|
|
|
+% It can also read single value markers that follow the following format.
|
|
|
+%
|
|
|
+% *MarkerName=Value;#
|
|
|
+%
|
|
|
+% EXAMPLES: *WaitSeconds=10;# OR *JuiceStatus=ON;#
|
|
|
+%
|
|
|
+% The above line is a "Marker". The marker value is 10 in the first
|
|
|
+% and it's ON in the second example.
|
|
|
+% -----------------------------------------------------------------------
|
|
|
+% Moreover, the marker could be a single value:
|
|
|
+%
|
|
|
+% *MarkerValue#
|
|
|
+%
|
|
|
+% EXAMPLES: *JuiceOff# OR *HandsOnSwitches#
|
|
|
+% -----------------------------------------------------------------------
|
|
|
+% The label, parameter name, and values are flexible and can be anything.
|
|
|
+% The only required formatting is that the user needs to have a label
|
|
|
+% followed by a colon ':', followed by a field name 'MarkerVal', followed
|
|
|
+% by an equal sign '=', followed by the parameter value '10', and end
|
|
|
+% with a semi-colon ';'.
|
|
|
+%
|
|
|
+% NOTE:
|
|
|
+% Every line requires a pound-sign '#' at the very end.
|
|
|
+% Every line requires a star sign '*' at the very beginning. If you
|
|
|
+% use LabVIEW SendtoCerebus.vi by Kian Torab then there is no need for
|
|
|
+% a '*' in the beginning.
|
|
|
+%
|
|
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
|
+% Kian Torab
|
|
|
+% kian@blackrockmicro.com
|
|
|
+% Blackrock Microsystems
|
|
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
|
+% Version History
|
|
|
+%
|
|
|
+% 4.4.0.0:
|
|
|
+% - Major performance boost in reading NEV files when tracking data is
|
|
|
+% stored in the file.
|
|
|
+%
|
|
|
+% 4.4.0.2:
|
|
|
+% - Updated documentation.
|
|
|
+%
|
|
|
+% 4.4.0.3: 5 January 2014
|
|
|
+% - Fixed the way DayOfWeek is read in MetaTags.
|
|
|
+% - Fixed 'noread' argument, so when passed, openNEV will not read the
|
|
|
+% spike waveforms.
|
|
|
+%
|
|
|
+% 4.4.1.0: 25 January 2014
|
|
|
+% - Fixed a bug that resulted from passing 'read' to openNEV.
|
|
|
+%
|
|
|
+% 4.4.2.0: 28 February 2014
|
|
|
+% - Fixed bug related to loading data with t:XX:XX argument.
|
|
|
+%
|
|
|
+% 4.4.3.0: 12 June 2014
|
|
|
+% - Fixed a typo in the help.
|
|
|
+%
|
|
|
+% 4.4.3.1: 13 June 2014
|
|
|
+% - Updated the version numbers in the help and in the function itself.
|
|
|
+%
|
|
|
+% 4.4.3.2: 21 June 2014
|
|
|
+% - Fixed a bug where Application name wasn't being read properly.
|
|
|
+%
|
|
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
|
+
|
|
|
+%% Defining structures
|
|
|
+NEV = struct('MetaTags',[], 'ElectrodesInfo', [], 'Data', []);
|
|
|
+NEV.MetaTags = struct('Subject', [], 'Experimenter', [], 'DateTime', [],...
|
|
|
+ 'SampleRes',[],'Comment',[],'FileTypeID',[],'Flags',[], 'openNEVver', [], ...
|
|
|
+ 'DateTimeRaw', [], 'FileSpec', [], 'PacketBytes', [], 'HeaderOffset', [], ...
|
|
|
+ 'DataDuration', [], 'DataDurationSec', [], 'PacketCount', [], ...
|
|
|
+ 'TimeRes', [], 'Application', [], 'Filename', [], 'FilePath', []);
|
|
|
+NEV.Data = struct('SerialDigitalIO', [], 'Spikes', [], 'Comments', [], 'VideoSync', [], ...
|
|
|
+ 'Tracking', [], 'TrackingEvents', [], 'PatientTrigger', [], 'Reconfig', []);
|
|
|
+NEV.Data.Spikes = struct('TimeStamp', [],'Electrode', [],...
|
|
|
+ 'Unit', [],'Waveform', [], 'WaveformUnit', []);
|
|
|
+NEV.Data.SerialDigitalIO = struct('InputType', [], 'TimeStamp', [],...
|
|
|
+ 'TimeStampSec', [], 'Type', [], 'Value', [], 'InsertionReason', [], 'UnparsedData', []);
|
|
|
+NEV.Data.VideoSync = struct('TimeStamp', [], 'FileNumber', [], 'FrameNumber', [], 'ElapsedTime', [], 'SourceID', []);
|
|
|
+NEV.Data.Comments = struct('TimeStamp', [], 'TimeStampSec', [], 'CharSet', [], 'Color', [], 'Text', []);
|
|
|
+NEV.Data.Tracking = [];
|
|
|
+NEV.Data.TrackingEvents = struct('TimeStamp', [], 'TimeStampSec', [], 'Text', []);
|
|
|
+NEV.Data.PatientTrigger = struct('TimeStamp', [], 'TriggerType', []);
|
|
|
+NEV.Data.Reconfig = struct('TimeStamp', [], 'ChangeType', [], 'CompName', [], 'ConfigChanged', []);
|
|
|
+Flags = struct;
|
|
|
+
|
|
|
+NEV.MetaTags.openNEVver = '4.4.3.1';
|
|
|
+
|
|
|
+%% Check for multiple versions of openNEV in path
|
|
|
+if size(which('openNEV', '-ALL'),1) > 1
|
|
|
+ disp('WARNING: There are multiple openNEV functions in the path. Use which openNEV -ALL for more information.');
|
|
|
+end
|
|
|
+
|
|
|
+%% Validating input arguments
|
|
|
+for i=1:length(varargin)
|
|
|
+ switch lower(varargin{i})
|
|
|
+ case 'report'
|
|
|
+ Flags.Report = varargin{i};
|
|
|
+ case 'noread'
|
|
|
+ Flags.ReadData = varargin{i};
|
|
|
+ case 'read'
|
|
|
+ Flags.ReadData = varargin{i};
|
|
|
+ case 'nosave'
|
|
|
+ Flags.SaveFile = varargin{i};
|
|
|
+ case 'nomat'
|
|
|
+ Flags.NoMAT = varargin{i};
|
|
|
+ case 'nowarning'
|
|
|
+ Flags.WarningStat = varargin{i};
|
|
|
+ case 'parse'
|
|
|
+ Flags.ParseData = 'parse';
|
|
|
+ case 'uv'
|
|
|
+ Flags.waveformUnits = 'uV';
|
|
|
+ case '8bits'
|
|
|
+ Flags.digIOBits = '8bits';
|
|
|
+ case 'overwrite'
|
|
|
+ Flags.Overwrite = 'overwrite';
|
|
|
+ otherwise
|
|
|
+ temp = varargin{i};
|
|
|
+ if length(temp)>3 && ...
|
|
|
+ (strcmpi(temp(3),'\') || ...
|
|
|
+ strcmpi(temp(1),'/') || ...
|
|
|
+ strcmpi(temp(2),'/'))
|
|
|
+ fileFullPath = varargin{i};
|
|
|
+ if exist(fileFullPath, 'file') ~= 2
|
|
|
+ disp('The file does not exist.');
|
|
|
+ varargout{1} = [];
|
|
|
+ return;
|
|
|
+ end
|
|
|
+ elseif length(temp)>3 && strcmpi(temp(1:2),'t:') && ~strcmpi(temp(3), '\') && ~strcmpi(temp(3), '/')
|
|
|
+ temp(1:2) = [];
|
|
|
+ temp = str2num(temp);
|
|
|
+ if length(temp) == 1
|
|
|
+ fprintf('Only one timepoint (%0.0f) was passed to the function.\n', temp);
|
|
|
+ fprintf('The initial timepoint is set to 0, so data between 0 and %0.0f will be read.\n', temp);
|
|
|
+ temp(2) = temp;
|
|
|
+ temp(1) = 0;
|
|
|
+ end
|
|
|
+ readTime = [temp(1), temp(end)];
|
|
|
+ Flags.SaveFile = 'nosave';
|
|
|
+ Flags.NoMAT = 'nomat';
|
|
|
+ else
|
|
|
+ if ~isnumeric(varargin{i})
|
|
|
+ disp(['Invalid argument ''' varargin{i} ''' .']);
|
|
|
+ else
|
|
|
+ disp(['Invalid argument ''' num2str(varargin{i}) ''' .']);
|
|
|
+ end
|
|
|
+ clear variables;
|
|
|
+ if nargout
|
|
|
+ varargout{1} = [];
|
|
|
+ end
|
|
|
+ return;
|
|
|
+ end
|
|
|
+ clear temp;
|
|
|
+ end
|
|
|
+end; clear i;
|
|
|
+
|
|
|
+%% Defining and validating variables
|
|
|
+if ~exist('fileFullPath', 'var')
|
|
|
+ if exist('getFile.m', 'file') == 2
|
|
|
+ [fileName pathName] = getFile('*.nev', 'Choose a NEV file...');
|
|
|
+ else
|
|
|
+ [fileName pathName] = uigetfile;
|
|
|
+ end
|
|
|
+ fileFullPath = [pathName fileName];
|
|
|
+ if fileFullPath==0;
|
|
|
+ clear variables;
|
|
|
+ if nargout
|
|
|
+ varargout{1} = [];
|
|
|
+ end
|
|
|
+ disp('No file was selected.');
|
|
|
+ return
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+if ~isfield(Flags, 'Report'); Flags.Report = 'noreport'; end
|
|
|
+if ~isfield(Flags, 'WarningStat'); Flags.WarningStat = 'warning'; end;
|
|
|
+if ~isfield(Flags, 'ReadData'); Flags.ReadData = 'read'; end
|
|
|
+if ~isfield(Flags, 'ParseData'); Flags.ParseData = 'noparse'; end
|
|
|
+if ~isfield(Flags, 'SaveFile'); Flags.SaveFile = 'save'; end;
|
|
|
+if ~isfield(Flags, 'NoMAT'); Flags.NoMAT = 'yesmat'; end;
|
|
|
+if ~isfield(Flags, 'waveformUnits'); Flags.waveformUnits = 'raw'; end;
|
|
|
+if ~isfield(Flags, 'digIOBits'); Flags.digIOBits = '16bits'; end;
|
|
|
+if ~isfield(Flags, 'Overwrite'); Flags.Overwrite = 'noOverwrite'; end;
|
|
|
+if strcmpi(Flags.Report, 'report')
|
|
|
+ disp(['openNEV ' NEV.MetaTags.openNEVver]);
|
|
|
+end
|
|
|
+%% Validating existance of parseCommand
|
|
|
+if strcmpi(Flags.ParseData, 'parse')
|
|
|
+ if exist('parseCommand.m', 'file') ~= 2
|
|
|
+ disp('This version of openNEV requires function parseCommand.m to be placed in path.');
|
|
|
+ clear variables;
|
|
|
+ if nargout
|
|
|
+ varargout{1} = [];
|
|
|
+ end
|
|
|
+ return;
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+tic;
|
|
|
+matPath = [fileFullPath(1:end-4) '.mat'];
|
|
|
+
|
|
|
+%% Check for a MAT file and load that instead of NEV
|
|
|
+if exist(matPath, 'file') == 2 && strcmpi(Flags.NoMAT, 'yesmat')
|
|
|
+ disp('MAT file corresponding to selected NEV file already exists. Loading MAT instead...');
|
|
|
+ load(matPath);
|
|
|
+ if isempty(NEV.Data.Spikes.Waveform) && strcmpi(Flags.ReadData, 'read')
|
|
|
+ disp('The MAT file does not contain waveforms. Loading NEV instead...');
|
|
|
+ else
|
|
|
+ if ~nargout
|
|
|
+ assignin('base', 'NEV', NEV);
|
|
|
+ clear variables;
|
|
|
+ else
|
|
|
+ varargout{1} = NEV;
|
|
|
+ end
|
|
|
+ return;
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+%% Reading BasicHeader information from file
|
|
|
+FID = fopen(fileFullPath, 'r', 'ieee-le');
|
|
|
+BasicHeader = fread(FID, 336, '*uint8');
|
|
|
+NEV.MetaTags.FileTypeID = char(BasicHeader(1:8)');
|
|
|
+NEV.MetaTags.FileSpec = [num2str(double(BasicHeader(9))) '.' num2str(double(BasicHeader(10)))];
|
|
|
+NEV.MetaTags.Flags = dec2bin(double(typecast(BasicHeader(11:12), 'uint16')),16);
|
|
|
+Trackers.fExtendedHeader = double(typecast(BasicHeader(13:16), 'uint32'));
|
|
|
+NEV.MetaTags.HeaderOffset = Trackers.fExtendedHeader;
|
|
|
+Trackers.countPacketBytes = double(typecast(BasicHeader(17:20), 'uint32'));
|
|
|
+NEV.MetaTags.PacketBytes = Trackers.countPacketBytes;
|
|
|
+NEV.MetaTags.TimeRes = double(typecast(BasicHeader(21:24), 'uint32'));
|
|
|
+NEV.MetaTags.SampleRes = typecast(BasicHeader(25:28), 'uint32');
|
|
|
+t = double(typecast(BasicHeader(29:44), 'uint16'));
|
|
|
+tempApp = BasicHeader(45:76)';
|
|
|
+tempApp(find(tempApp == 0):end) = [];
|
|
|
+NEV.MetaTags.Application = char(tempApp); clear tempApp;
|
|
|
+NEV.MetaTags.Comment = char(BasicHeader(77:332)');
|
|
|
+[NEV.MetaTags.FilePath, NEV.MetaTags.Filename] = fileparts(fileFullPath);
|
|
|
+Trackers.countExtHeader = typecast(BasicHeader(333:336), 'uint32');
|
|
|
+clear BasicHeader;
|
|
|
+
|
|
|
+if strcmpi(NEV.MetaTags.FileTypeID, 'NEURALEV')
|
|
|
+ if exist([fileFullPath(1:end-8) '.sif'], 'file') == 2
|
|
|
+ METATAGS = textread([fileFullPath(1:end-8) '.sif'], '%s');
|
|
|
+ NEV.MetaTags.Subject = METATAGS{3}(5:end-5);
|
|
|
+ NEV.MetaTags.Experimenter = [METATAGS{5}(8:end-8) ' ' METATAGS{6}(7:end-7)];
|
|
|
+ end
|
|
|
+end
|
|
|
+if ~any(strcmpi(NEV.MetaTags.FileSpec, {'2.1', '2.2', '2.3'}))
|
|
|
+ disp('Unknown filespec. Cannot open file.');
|
|
|
+ fclose FID;
|
|
|
+ clear variables;
|
|
|
+ if nargout
|
|
|
+ varargout{1} = [];
|
|
|
+ end
|
|
|
+ return;
|
|
|
+end
|
|
|
+clear fileFullPath;
|
|
|
+
|
|
|
+%% Parsing and validating FileSpec and DateTime variables
|
|
|
+NEV.MetaTags.DateTimeRaw = t.';
|
|
|
+NEV.MetaTags.DateTime = [num2str(t(2)) '/' num2str(t(4)+2) '/' num2str(t(1))...
|
|
|
+ ' ' datestr(t(3), 'dddd') ' ' num2str(t(5)) ':' ...
|
|
|
+ num2str(t(6)) ':' num2str(t(7)) '.' num2str(t(8))] ;
|
|
|
+clear t;
|
|
|
+
|
|
|
+%% Removing extra garbage characters from the Comment field.
|
|
|
+NEV.MetaTags.Comment(find(NEV.MetaTags.Comment==0,1):end) = 0;
|
|
|
+
|
|
|
+%% Recording after BasicHeader file position
|
|
|
+Trackers.fBasicHeader = ftell(FID); %#ok<NASGU>
|
|
|
+
|
|
|
+% Calculating the length of the data
|
|
|
+currentLocation = ftell(FID);
|
|
|
+fseek(FID, -Trackers.countPacketBytes, 'eof');
|
|
|
+NEV.MetaTags.DataDuration = fread(FID, 1, 'uint32=>double');
|
|
|
+NEV.MetaTags.DataDurationSec = double(NEV.MetaTags.DataDuration) / double(NEV.MetaTags.SampleRes);
|
|
|
+fseek(FID, currentLocation, 'bof');
|
|
|
+
|
|
|
+%% Reading ExtendedHeader information
|
|
|
+for ii=1:Trackers.countExtHeader
|
|
|
+ ExtendedHeader = fread(FID, 32, '*uint8');
|
|
|
+ PacketID = char(ExtendedHeader(1:8)');
|
|
|
+ switch PacketID
|
|
|
+ case 'ARRAYNME'
|
|
|
+ NEV.ArrayInfo.ElectrodeName = char(ExtendedHeader(9:end));
|
|
|
+ case 'ECOMMENT'
|
|
|
+ NEV.ArrayInfo.ArrayComment = char(ExtendedHeader(9:end));
|
|
|
+ case 'CCOMMENT'
|
|
|
+ NEV.ArrayInfo.ArrayCommentCont = char(ExtendedHeader(9:end));
|
|
|
+ case 'MAPFILE'
|
|
|
+ NEV.ArrayInfo.MapFile = char(ExtendedHeader(9:end));
|
|
|
+ case 'NEUEVWAV'
|
|
|
+ ElectrodeID = typecast(ExtendedHeader(9:10), 'uint16');
|
|
|
+ NEV.ElectrodesInfo(ElectrodeID).ElectrodeID = ElectrodeID;
|
|
|
+ NEV.ElectrodesInfo(ElectrodeID).ConnectorBank = char(ExtendedHeader(11)+64);
|
|
|
+ NEV.ElectrodesInfo(ElectrodeID).ConnectorPin = ExtendedHeader(12);
|
|
|
+ df = typecast(ExtendedHeader(13:14),'int16');
|
|
|
+ % This is a workaround for the DigitalFactor overflow in NEV
|
|
|
+ % files. Remove once Central is updated
|
|
|
+ if df == 21516
|
|
|
+ NEV.ElectrodesInfo(ElectrodeID).DigitalFactor = 152592.547;
|
|
|
+ else
|
|
|
+ NEV.ElectrodesInfo(ElectrodeID).DigitalFactor = df;
|
|
|
+ end
|
|
|
+ % End of workaround
|
|
|
+ NEV.ElectrodesInfo(ElectrodeID).EnergyThreshold = typecast(ExtendedHeader(15:16),'uint16');
|
|
|
+ NEV.ElectrodesInfo(ElectrodeID).HighThreshold = typecast(ExtendedHeader(17:18),'int16');
|
|
|
+ NEV.ElectrodesInfo(ElectrodeID).LowThreshold = typecast(ExtendedHeader(19:20),'int16');
|
|
|
+ NEV.ElectrodesInfo(ElectrodeID).Units = ExtendedHeader(21);
|
|
|
+ NEV.ElectrodesInfo(ElectrodeID).WaveformBytes = ExtendedHeader(22);
|
|
|
+ clear ElectrodeID;
|
|
|
+ case 'NEUEVLBL'
|
|
|
+ ElectrodeID = typecast(ExtendedHeader(9:10), 'uint16');
|
|
|
+ NEV.ElectrodesInfo(ElectrodeID).ElectrodeLabel = char(ExtendedHeader(11:26));
|
|
|
+ clear ElectrodeID;
|
|
|
+ case 'NEUEVFLT'
|
|
|
+ ElectrodeID = typecast(ExtendedHeader(9:10), 'uint16');
|
|
|
+ NEV.ElectrodesInfo(ElectrodeID).HighFreqCorner = typecast(ExtendedHeader(11:14),'uint32');
|
|
|
+ NEV.ElectrodesInfo(ElectrodeID).HighFreqOrder = typecast(ExtendedHeader(15:18),'uint32');
|
|
|
+ NEV.ElectrodesInfo(ElectrodeID).HighFilterType = typecast(ExtendedHeader(19:20),'uint16');
|
|
|
+ NEV.ElectrodesInfo(ElectrodeID).LowFreqCorner = typecast(ExtendedHeader(21:24),'uint32');
|
|
|
+ NEV.ElectrodesInfo(ElectrodeID).LowFreqOrder = typecast(ExtendedHeader(25:28),'uint32');
|
|
|
+ NEV.ElectrodesInfo(ElectrodeID).LowFilterType = typecast(ExtendedHeader(29:30),'uint16');
|
|
|
+ clear ElectrodeID;
|
|
|
+ case 'DIGLABEL'
|
|
|
+ Mode = ExtendedHeader(25);
|
|
|
+ NEV.IOLabels{Mode+1} = char(ExtendedHeader(9:24).');
|
|
|
+ clear Mode;
|
|
|
+ case 'NSASEXEV' %% Not implemented in the Cerebus firmware.
|
|
|
+ %% Needs to be updated once implemented into the
|
|
|
+ %% firmware by Blackrock Microsystems.
|
|
|
+ NEV.NSAS.Freq = typecast(ExtendedHeader(9:10),'uint16');
|
|
|
+ NEV.NSAS.DigInputConf = char(ExtendedHeader(11));
|
|
|
+ NEV.NSAS.AnalCh1Conf = char(ExtendedHeader(12));
|
|
|
+ NEV.NSAS.AnalCh1Detect = typecast(ExtendedHeader(13:14),'uint16');
|
|
|
+ NEV.NSAS.AnalCh2Conf = char(ExtendedHeader(15));
|
|
|
+ NEV.NSAS.AnalCh2Detect = typecast(ExtendedHeader(16:17),'uint16');
|
|
|
+ NEV.NSAS.AnalCh3Conf = char(ExtendedHeader(18));
|
|
|
+ NEV.NSAS.AnalCh3Detect = typecast(ExtendedHeader(19:20),'uint16');
|
|
|
+ NEV.NSAS.AnalCh4Conf = char(ExtendedHeader(21));
|
|
|
+ NEV.NSAS.AnalCh4Detect = typecast(ExtendedHeader(22:23),'uint16');
|
|
|
+ NEV.NSAS.AnalCh5Conf = char(ExtendedHeader(24));
|
|
|
+ NEV.NSAS.AnalCh5Detect = typecast(ExtendedHeader(25:26),'uint16');
|
|
|
+ case 'VIDEOSYN'
|
|
|
+ cnt = 1;
|
|
|
+ if (isfield(NEV, 'VideoSyncInfo'))
|
|
|
+ cnt = size(NEV.VideoSyncInfo, 2) + 1;
|
|
|
+ end
|
|
|
+ NEV.VideoSyncInfo(cnt).SourceID = typecast(ExtendedHeader(9:10),'uint16');
|
|
|
+ NEV.VideoSyncInfo(cnt).SourceName = char(ExtendedHeader(11:26))';
|
|
|
+ NEV.VideoSyncInfo(cnt).FrameRateFPS = typecast(ExtendedHeader(27:30),'single')';
|
|
|
+ clear cnt;
|
|
|
+ case 'TRACKOBJ'
|
|
|
+ cnt = 1;
|
|
|
+ if (isfield(NEV, 'ObjTrackInfo'))
|
|
|
+ cnt = size(NEV.ObjTrackInfo, 2) + 1;
|
|
|
+ end
|
|
|
+ NEV.ObjTrackInfo(cnt).TrackableType = typecast(ExtendedHeader(9:10),'uint16');
|
|
|
+ NEV.ObjTrackInfo(cnt).TrackableID = typecast(ExtendedHeader(11:14), 'uint32');
|
|
|
+ NEV.ObjTrackInfo(cnt).TrackableName = char(ExtendedHeader(15:30))';
|
|
|
+ clear cnt;
|
|
|
+ otherwise
|
|
|
+ disp(['PacketID ' PacketID ' is invalid.']);
|
|
|
+ disp('Please make sure this version of openNEV is compatible with your current NSP firmware.')
|
|
|
+ fclose(FID);
|
|
|
+ clear variables;
|
|
|
+ if nargout
|
|
|
+ varargout{1} = [];
|
|
|
+ end
|
|
|
+ return;
|
|
|
+ end
|
|
|
+end
|
|
|
+NEV.MetaTags.ChannelID = [NEV.ElectrodesInfo.ElectrodeID];
|
|
|
+clear ExtendedHeader PacketID ii;
|
|
|
+
|
|
|
+%% Recording after ExtendedHeader file position and calculating Data Length
|
|
|
+% and number of data packets
|
|
|
+fseek(FID, 0, 'eof');
|
|
|
+Trackers.fData = ftell(FID);
|
|
|
+Trackers.countDataPacket = (Trackers.fData - Trackers.fExtendedHeader)/Trackers.countPacketBytes;
|
|
|
+NEV.MetaTags.PacketCount = Trackers.countDataPacket;
|
|
|
+
|
|
|
+%%
|
|
|
+Flags.UnparsedDigitalData = 0;
|
|
|
+
|
|
|
+%% Reading packet headers and digital values
|
|
|
+Timestamp = [];
|
|
|
+PacketIDs = [];
|
|
|
+tempClassOrReason = [];
|
|
|
+tempDigiVals = [];
|
|
|
+if NEV.MetaTags.PacketCount ~= 0
|
|
|
+ fseek(FID, Trackers.fExtendedHeader, 'bof');
|
|
|
+ tRawData = fread(FID, [10 Trackers.countDataPacket], '10*uint8=>uint8', Trackers.countPacketBytes - 10);
|
|
|
+ Timestamp = tRawData(1:4,:);
|
|
|
+ Timestamp = typecast(Timestamp(:), 'uint32').';
|
|
|
+
|
|
|
+ %% Calculate the number of packets that need to be read based on the time input parameters
|
|
|
+ if ~exist('readTime', 'var')
|
|
|
+ Trackers.readPackets = [1, length(Timestamp)];
|
|
|
+ else
|
|
|
+ [tmp,tempReadPackets] = find(Timestamp > readTime(1)*NEV.MetaTags.SampleRes,1,'first');
|
|
|
+ if ~isempty(tempReadPackets)
|
|
|
+ Trackers.readPackets(1) = tempReadPackets;
|
|
|
+ else
|
|
|
+ Trackers.readPackets(1) = NaN;
|
|
|
+ end
|
|
|
+ if isnan(Trackers.readPackets(1))
|
|
|
+ fprintf('The file contains %0.2f seconds of data. The requested begining timestamp of %0.2f seconds is longer than the duration.\n', ...
|
|
|
+ double(Timestamp(end))/double(NEV.MetaTags.SampleRes), ...
|
|
|
+ readTime(1));
|
|
|
+ clear variables;
|
|
|
+ if nargout
|
|
|
+ varargout{1} = [];
|
|
|
+ end
|
|
|
+ return;
|
|
|
+ end
|
|
|
+ [tmp,tempReadPackets] = find(Timestamp < readTime(2)*NEV.MetaTags.SampleRes,1,'last');
|
|
|
+ if ~isempty(tempReadPackets)
|
|
|
+ if readTime(2)*NEV.MetaTags.SampleRes > Timestamp(end)
|
|
|
+ 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', ...
|
|
|
+ double(Timestamp(end))/double(NEV.MetaTags.SampleRes), ...
|
|
|
+ readTime(2),...
|
|
|
+ double(Timestamp(end))/double(NEV.MetaTags.SampleRes));
|
|
|
+ end
|
|
|
+ Trackers.readPackets(2) = tempReadPackets;
|
|
|
+ else
|
|
|
+ Trackers.readPackets(2) = NaN;
|
|
|
+ end
|
|
|
+ clear tmp, tempReadPackets;
|
|
|
+ end
|
|
|
+
|
|
|
+ PacketIDs = tRawData(5:6,Trackers.readPackets(1):Trackers.readPackets(2));
|
|
|
+ PacketIDs = typecast(PacketIDs(:), 'uint16').';
|
|
|
+ tempClassOrReason = uint8(tRawData(7,Trackers.readPackets(1):Trackers.readPackets(2)));
|
|
|
+ if strcmpi(Flags.digIOBits, '16bits')
|
|
|
+ tempDigiVals = tRawData(9:10,Trackers.readPackets(1):Trackers.readPackets(2));
|
|
|
+ tempDigiVals = typecast(tempDigiVals(:), 'uint16');
|
|
|
+ else
|
|
|
+ tempDigiVals = uint16(tRawData(9,Trackers.readPackets(1):Trackers.readPackets(2)));
|
|
|
+ end
|
|
|
+ clear tRawData;
|
|
|
+else
|
|
|
+ Trackers.readPackets = zeros(1,2);
|
|
|
+end
|
|
|
+
|
|
|
+%% Defining PacketID constants
|
|
|
+digserPacketID = 0;
|
|
|
+neuralIndicesPacketIDBounds = [1, 16384];
|
|
|
+commentPacketID = 65535;
|
|
|
+videoSyncPacketID = 65534;
|
|
|
+trackingPacketID = 65533;
|
|
|
+patientTrigPacketID = 65532;
|
|
|
+reconfigPacketID = 65531;
|
|
|
+
|
|
|
+%% Parse read digital data. Please refer to help to learn about the proper
|
|
|
+% formatting if the data.
|
|
|
+digserIndices = find(PacketIDs == digserPacketID);
|
|
|
+neuralIndices = find(neuralIndicesPacketIDBounds(2) >= PacketIDs & PacketIDs >= neuralIndicesPacketIDBounds(1));
|
|
|
+commentIndices = find(PacketIDs == commentPacketID);
|
|
|
+videoSyncPacketIDIndices = find(PacketIDs == videoSyncPacketID);
|
|
|
+trackingPacketIDIndices = find(PacketIDs == trackingPacketID);
|
|
|
+patientTrigPacketIDIndices = find(PacketIDs == patientTrigPacketID);
|
|
|
+reconfigPacketIDIndices = find(PacketIDs == reconfigPacketID);
|
|
|
+clear digserPacketID neuralIndicesPacketIDBounds commentPacketID ...
|
|
|
+ videoSyncPacketID trackingPacketID patientTrigPacketID reconfigPacketID;
|
|
|
+digserTimestamp = Timestamp(digserIndices);
|
|
|
+NEV.Data.Spikes.TimeStamp = Timestamp(neuralIndices);
|
|
|
+NEV.Data.Spikes.Electrode = PacketIDs(neuralIndices);
|
|
|
+clear PacketIDs;
|
|
|
+NEV.Data.Spikes.Unit = tempClassOrReason(neuralIndices);
|
|
|
+%clear neuralIndices;
|
|
|
+NEV.Data.SerialDigitalIO.InsertionReason = tempClassOrReason(digserIndices);
|
|
|
+clear tempClassOrReason;
|
|
|
+DigiValues = tempDigiVals(digserIndices);
|
|
|
+clear tempDigiVals;
|
|
|
+
|
|
|
+%% Reads the waveforms if 'read' is passed to the function
|
|
|
+if strcmpi(Flags.ReadData, 'read')
|
|
|
+ allExtraDataPacketIndices = [commentIndices, ...
|
|
|
+ videoSyncPacketIDIndices, ...
|
|
|
+ trackingPacketIDIndices, ...
|
|
|
+ patientTrigPacketIDIndices, ...
|
|
|
+ reconfigPacketIDIndices];
|
|
|
+
|
|
|
+ if ~isempty(allExtraDataPacketIndices) % if there is any extra packets
|
|
|
+ fseek(FID, Trackers.fExtendedHeader, 'bof');
|
|
|
+ fseek(FID, (Trackers.readPackets(1)-1) * Trackers.countPacketBytes, 'cof');
|
|
|
+ tRawData = fread(FID, [Trackers.countPacketBytes Trackers.readPackets(2)], ...
|
|
|
+ [num2str(Trackers.countPacketBytes) '*uint8=>uint8'], 0);
|
|
|
+ if ~isempty(commentIndices)
|
|
|
+ NEV.Data.Comments.TimeStamp = Timestamp(commentIndices);
|
|
|
+ NEV.Data.Comments.TimeStampSec = double(NEV.Data.Comments.TimeStamp)/double(NEV.MetaTags.TimeRes);
|
|
|
+ NEV.Data.Comments.CharSet = tRawData(7, commentIndices);
|
|
|
+ NEV.Data.Comments.Color = tRawData(9:12, commentIndices);
|
|
|
+ NEV.Data.Comments.Color = typecast(NEV.Data.Comments.Color(:), 'uint32').';
|
|
|
+ NEV.Data.Comments.Text = char(tRawData(13:Trackers.countPacketBytes, commentIndices).');
|
|
|
+
|
|
|
+ % Transferring NeuroMotive Events to its own structure
|
|
|
+ neuroMotiveEvents = find(NEV.Data.Comments.CharSet == 255);
|
|
|
+ NEV.Data.TrackingEvents.TimeStamp = NEV.Data.Comments.TimeStamp(neuroMotiveEvents);
|
|
|
+ NEV.Data.TrackingEvents.TimeStampSec = double(NEV.Data.TrackingEvents.TimeStamp)/double(NEV.MetaTags.TimeRes);
|
|
|
+ NEV.Data.TrackingEvents.Text = NEV.Data.Comments.Text(neuroMotiveEvents,:);
|
|
|
+ NEV.Data.Comments.TimeStamp(neuroMotiveEvents) = [];
|
|
|
+ NEV.Data.Comments.TimeStampSec(neuroMotiveEvents) = [];
|
|
|
+ NEV.Data.Comments.CharSet(neuroMotiveEvents) = [];
|
|
|
+ NEV.Data.Comments.Color(neuroMotiveEvents) = [];
|
|
|
+ NEV.Data.Comments.Text(neuroMotiveEvents) = [];
|
|
|
+
|
|
|
+ clear commentIndices;
|
|
|
+ end
|
|
|
+ if ~isempty(videoSyncPacketIDIndices)
|
|
|
+ NEV.Data.VideoSync.TimeStamp = Timestamp(videoSyncPacketIDIndices);
|
|
|
+ NEV.Data.VideoSync.FileNumber = tRawData(7:8, videoSyncPacketIDIndices);
|
|
|
+ NEV.Data.VideoSync.FileNumber = typecast(NEV.Data.VideoSync.FileNumber(:), 'uint16').';
|
|
|
+ NEV.Data.VideoSync.FrameNumber = tRawData(9:12, videoSyncPacketIDIndices);
|
|
|
+ NEV.Data.VideoSync.FrameNumber = typecast(NEV.Data.VideoSync.FrameNumber(:), 'uint32').';
|
|
|
+ NEV.Data.VideoSync.ElapsedTime = tRawData(13:16, videoSyncPacketIDIndices);
|
|
|
+ NEV.Data.VideoSync.ElapsedTime = typecast(NEV.Data.VideoSync.ElapsedTime(:), 'uint32').';
|
|
|
+ NEV.Data.VideoSync.SourceID = tRawData(17:20, videoSyncPacketIDIndices);
|
|
|
+ NEV.Data.VideoSync.SourceID = typecast(NEV.Data.VideoSync.SourceID(:), 'uint32').';
|
|
|
+ clear videoSyncPacketIDIndices;
|
|
|
+ end
|
|
|
+ if ~isempty(trackingPacketIDIndices)
|
|
|
+ tmp.TimeStamp = Timestamp(trackingPacketIDIndices);
|
|
|
+ tmp.TimeStampSec = double(tmp.TimeStamp)/30000;
|
|
|
+ % This portion is commented out because it does not contain any
|
|
|
+ % information as of yet.
|
|
|
+ tmp.ParentID = tRawData(7:8, trackingPacketIDIndices);
|
|
|
+ tmp.ParentID = typecast(tmp.ParentID(:), 'uint16').';
|
|
|
+ tmp.NodeID = tRawData(9:10, trackingPacketIDIndices);
|
|
|
+ tmp.NodeID = typecast(tmp.NodeID(:), 'uint16').';
|
|
|
+ tmp.NodeCount = tRawData(11:12, trackingPacketIDIndices);
|
|
|
+ tmp.NodeCount = typecast(tmp.NodeCount(:), 'uint16').';
|
|
|
+ tmp.MarkerCount = tRawData(13:14, trackingPacketIDIndices);
|
|
|
+ tmp.MarkerCount = typecast(tmp.MarkerCount(:), 'uint16').';
|
|
|
+
|
|
|
+ tmp.rigidBodyPoints = tRawData(15:102, trackingPacketIDIndices);
|
|
|
+ tmp.rigidBodyPoints = reshape(typecast(tmp.rigidBodyPoints(:), 'uint16'), 44, length(tmp.ParentID));
|
|
|
+
|
|
|
+ objectIndex = [0 '1' '2' '3' '4' '1' '2' '3' '4'];
|
|
|
+ if (isfield(NEV, 'ObjTrackInfo'))
|
|
|
+ for IDX = 1:size(NEV.ObjTrackInfo,2)
|
|
|
+ emptyChar = find(NEV.ObjTrackInfo(IDX).TrackableName == 0, 1);
|
|
|
+ NEV.ObjTrackInfo(IDX).TrackableName(emptyChar) = objectIndex(IDX);
|
|
|
+ indicesOfEvent = find(tmp.NodeID == IDX-1);
|
|
|
+ if ~isempty(indicesOfEvent)
|
|
|
+ NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).TimeStamp = tmp.TimeStamp(indicesOfEvent);
|
|
|
+ NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).TimeStampSec = tmp.TimeStampSec(indicesOfEvent);
|
|
|
+ NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).ParentID = tmp.ParentID(indicesOfEvent);
|
|
|
+ NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).NodeCount = tmp.NodeCount(indicesOfEvent);
|
|
|
+ NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).MarkerCount = tmp.MarkerCount(indicesOfEvent);
|
|
|
+ NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).MarkerCoordinates(size(NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).TimeStamp,2)).X = [];
|
|
|
+ NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).MarkerCoordinates(size(NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).TimeStamp,2)).X = [];
|
|
|
+ for xyIDX = 1:size(NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).TimeStamp,2)
|
|
|
+ NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).MarkerCoordinates(xyIDX).X = ...
|
|
|
+ tmp.rigidBodyPoints(1:2:NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).MarkerCount(xyIDX)*2, indicesOfEvent(xyIDX));
|
|
|
+ NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).MarkerCoordinates(xyIDX).Y = ...
|
|
|
+ tmp.rigidBodyPoints(2:2:NEV.Data.Tracking.(NEV.ObjTrackInfo(IDX).TrackableName).MarkerCount(xyIDX)*2, indicesOfEvent(xyIDX));
|
|
|
+ end
|
|
|
+ end
|
|
|
+ end
|
|
|
+ end
|
|
|
+ clear trackingPacketIDIndices tmp;
|
|
|
+ end
|
|
|
+ if ~isempty(patientTrigPacketIDIndices)
|
|
|
+ NEV.Data.PatientTrigger.TimeStamp = Timestamp(patientTrigPacketIDIndices);
|
|
|
+ NEV.Data.PatientTrigger.TriggerType = tRawData(7:8, patientTrigPacketIDIndices);
|
|
|
+ NEV.Data.PatientTrigger.TriggerType = typecast(NEV.Data.PatientTrigger.TriggerType(:), 'uint16').';
|
|
|
+ clear patientTrigPacketIDIndices;
|
|
|
+ end
|
|
|
+ if ~isempty(reconfigPacketIDIndices)
|
|
|
+ NEV.Data.Reconfig.TimeStamp = Timestamp(reconfigPacketIDIndices);
|
|
|
+ NEV.Data.Reconfig.ChangeType = tRawData(7:8, reconfigPacketIDIndices);
|
|
|
+ NEV.Data.Reconfig.ChangeType = typecast(NEV.Data.Reconfig.ChangeType(:), 'uint16').';
|
|
|
+ NEV.Data.Reconfig.CompName = char(tRawData(9:24, reconfigPacketIDIndices));
|
|
|
+ NEV.Data.Reconfig.ConfigChanged = char(tRawData(25:Trackers.countPacketBytes, reconfigPacketIDIndices));
|
|
|
+ clear reconfigPacketIDIndices;
|
|
|
+ end
|
|
|
+ end % end if ~isempty(allExtraDataPacketIndices)
|
|
|
+
|
|
|
+ clear Timestamp tRawData count idx;
|
|
|
+
|
|
|
+ % now read waveform
|
|
|
+ fseek(FID, Trackers.fExtendedHeader + 8, 'bof'); % Seek to location of spikes
|
|
|
+ fseek(FID, (Trackers.readPackets(1)-1) * Trackers.countPacketBytes, 'cof');
|
|
|
+ NEV.Data.Spikes.WaveformUnit = Flags.waveformUnits;
|
|
|
+ NEV.Data.Spikes.Waveform = fread(FID, [(Trackers.countPacketBytes-8)/2 Trackers.readPackets(2)], ...
|
|
|
+ [num2str((Trackers.countPacketBytes-8)/2) '*int16=>int16'], 8);
|
|
|
+ NEV.Data.Spikes.Waveform(:, [digserIndices allExtraDataPacketIndices]) = [];
|
|
|
+
|
|
|
+ clear allExtraDataPacketIndices;
|
|
|
+ if strcmpi(Flags.waveformUnits, 'uv')
|
|
|
+ elecDigiFactors = double(1000./[NEV.ElectrodesInfo(NEV.Data.Spikes.Electrode).DigitalFactor]);
|
|
|
+ NEV.Data.Spikes.Waveform = bsxfun(@rdivide, double(NEV.Data.Spikes.Waveform), elecDigiFactors);
|
|
|
+ if strcmpi(Flags.WarningStat, 'warning')
|
|
|
+ fprintf(1,'\nThe spike waveforms are in unit of uV.\n');
|
|
|
+ fprintf(2,'WARNING: This conversion may lead to loss of information.');
|
|
|
+ fprintf(1,'\nRefer to help for more information.\n');
|
|
|
+ end
|
|
|
+ end
|
|
|
+end
|
|
|
+clear digserIndices;
|
|
|
+
|
|
|
+%% Parse digital data if requested
|
|
|
+if ~isempty(DigiValues)
|
|
|
+ if strcmpi(Flags.ParseData, 'parse')
|
|
|
+ try
|
|
|
+ DigiValues = char(DigiValues);
|
|
|
+ Inputs = {'Digital'; 'AnCh1'; 'AnCh2'; 'AnCh3'; 'AnCh4'; 'AnCh5'; 'PerSamp'; 'Serial'};
|
|
|
+ AsteriskIndices = find(DigiValues == '*');
|
|
|
+ DataBegTimestamp = digserTimestamp(AsteriskIndices);
|
|
|
+ splitDigiValues = regexp(DigiValues(2:end), '*', 'split')';
|
|
|
+ for idx = 1:length(splitDigiValues)
|
|
|
+ try
|
|
|
+ if isempty(find(splitDigiValues{idx} == ':', 1))
|
|
|
+ splitDigiValues{idx}(find(splitDigiValues{idx} == '#')) = [];
|
|
|
+ NEV.Data.SerialDigitalIO(idx).Value = splitDigiValues{idx};
|
|
|
+ NEV.Data.SerialDigitalIO(idx).Type = 'Marker';
|
|
|
+ else
|
|
|
+ [tempParsedCommand error] = parseCommand(splitDigiValues{idx});
|
|
|
+ if ~error
|
|
|
+ pcFields = fields(tempParsedCommand);
|
|
|
+ NEV.Data.SerialDigitalIO(idx).Value = splitDigiValues{idx};
|
|
|
+ for fidx = 1:length(pcFields)
|
|
|
+ NEV.Data.SerialDigitalIO(idx).(pcFields{fidx}) = tempParsedCommand.(pcFields{fidx});
|
|
|
+ end
|
|
|
+ else
|
|
|
+ NEV.Data.SerialDigitalIO(idx).Value = splitDigiValues{idx};
|
|
|
+ NEV.Data.SerialDigitalIO(idx).Type = 'UnparsedData';
|
|
|
+ Flags.UnparsedDigitalData = 1;
|
|
|
+ end
|
|
|
+ end
|
|
|
+ catch
|
|
|
+ disp(['Error parsing: ' splitDigiValues{idx}]);
|
|
|
+ disp('Please refer to the help for more information on how to properly format the digital data for parsing.');
|
|
|
+ end
|
|
|
+ end
|
|
|
+ % Populate the NEV structure with Timestamp and inputtypes for the
|
|
|
+ % digital data
|
|
|
+ if ~isempty(DataBegTimestamp)
|
|
|
+ c = num2cell(DataBegTimestamp); [NEV.Data.SerialDigitalIO(1:length(NEV.Data.SerialDigitalIO)).TimeStamp] = deal(c{1:end});
|
|
|
+ c = num2cell(DataBegTimestamp/NEV.MetaTags.SampleRes); [NEV.Data.SerialDigitalIO.TimeStampSec] = deal(c{1:end});
|
|
|
+ c = {Inputs{NEV.Data.SerialDigitalIO.InsertionReason(AsteriskIndices)}}; [NEV.Data.SerialDigitalIO.InputType] = deal(c{1:end});
|
|
|
+ end
|
|
|
+ clear Inputs DigiValues digserTimestamp;
|
|
|
+ catch
|
|
|
+ disp(lasterr);
|
|
|
+ disp('An error occured during reading digital data. This is due to a problem with formatting digital data.');
|
|
|
+ disp('Refer to help ''help openNEV'' for more information on how to properly format the digital data.');
|
|
|
+ disp('Try using openNEV with ''noparse'', i.e. openNEV(''noparse'').');
|
|
|
+ end
|
|
|
+ else
|
|
|
+ NEV.Data.SerialDigitalIO.TimeStamp = digserTimestamp;
|
|
|
+ NEV.Data.SerialDigitalIO.TimeStampSec = double(digserTimestamp)/30000;
|
|
|
+ clear digserTimestamp;
|
|
|
+ NEV.Data.SerialDigitalIO.UnparsedData = DigiValues;
|
|
|
+ clear DigiValues;
|
|
|
+ end
|
|
|
+else
|
|
|
+ if strcmpi(Flags.ReadData, 'read')
|
|
|
+ if strcmpi(Flags.Report, 'report')
|
|
|
+ disp('No digital data to read.');
|
|
|
+ end
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+if strcmpi(Flags.ParseData, 'parse')
|
|
|
+ if Flags.UnparsedDigitalData && strcmpi(Flags.WarningStat, 'warning')
|
|
|
+ fprintf(2, 'WARNING: The NEV file contains unparsed digital data.\n');
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+%% Show a report if 'report' is passed as an argument
|
|
|
+if strcmpi(Flags.Report, 'report')
|
|
|
+ % Displaying report
|
|
|
+ disp( '*** FILE INFO **************************');
|
|
|
+ disp(['File Name = ' NEV.MetaTags.Filename]);
|
|
|
+ disp(['Filespec = ' NEV.MetaTags.FileSpec]);
|
|
|
+ disp(['Data Duration (min) = ' num2str(round(NEV.MetaTags.DataDuration/NEV.MetaTags.SampleRes/60))]);
|
|
|
+ disp(['Packet Counts = ' num2str(Trackers.countDataPacket)]);
|
|
|
+ disp(' ');
|
|
|
+ disp( '*** BASIC HEADER ***********************');
|
|
|
+ disp(['Sample Resolution = ' num2str(NEV.MetaTags.SampleRes)]);
|
|
|
+ disp(['Date and Time = ' NEV.MetaTags.DateTime]);
|
|
|
+ disp(['Comment = ' NEV.MetaTags.Comment(1:64) ]);
|
|
|
+ disp([' ' NEV.MetaTags.Comment(65:128) ]);
|
|
|
+ disp([' ' NEV.MetaTags.Comment(129:192)]);
|
|
|
+ disp([' ' NEV.MetaTags.Comment(193:256)]);
|
|
|
+ disp(['The load time was for NEV file was ' num2str(toc, '%0.1f') ' seconds.']);
|
|
|
+end
|
|
|
+
|
|
|
+%% Saving the NEV structure as a MAT file for easy access
|
|
|
+if strcmpi(Flags.SaveFile, 'save')
|
|
|
+ if exist(matPath, 'file') == 2 && strcmpi(Flags.Overwrite, 'nooverwrite')
|
|
|
+ disp(['File ' matPath ' already exists.']);
|
|
|
+ overWrite = input('Would you like to overwrite (Y/N)? ', 's');
|
|
|
+ if strcmpi(overWrite, 'y')
|
|
|
+ disp('Saving MAT file. This may take a few seconds...');
|
|
|
+ save(matPath, 'NEV', '-v7.3');
|
|
|
+ else
|
|
|
+ disp('File was not overwritten.');
|
|
|
+ end
|
|
|
+ elseif exist(matPath, 'file') == 2 && strcmpi(Flags.Overwrite, 'overwrite')
|
|
|
+ disp(['File ' matPath ' already exists.']);
|
|
|
+ disp('Overwriting the old MAT file. This may take a few seconds...');
|
|
|
+ save(matPath, 'NEV', '-v7.3');
|
|
|
+ else
|
|
|
+ disp('Saving MAT file. This may take a few seconds...');
|
|
|
+ save(matPath, 'NEV', '-v7.3');
|
|
|
+ end
|
|
|
+ clear overWrite;
|
|
|
+end
|
|
|
+
|
|
|
+if ~nargout
|
|
|
+ assignin('base', 'NEV', NEV);
|
|
|
+else
|
|
|
+ varargout{1} = NEV;
|
|
|
+end
|
|
|
+
|
|
|
+fclose(FID);
|
|
|
+clear Flags Trackers FID matPath;
|