123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338 |
- function varargout = openNSx(varargin)
- % openNSx
- %
- % Opens and reads an NSx file then returns all file information in a NSx
- % structure. Works with File Spec 2.1, 2.2, 2.3, and 3.0.
- %
- % OUTPUT = openNSx('ver')
- % OUTPUT = openNSx(FNAME, 'read', 'report', 'e:xx:xx', 'c:xx:xx', 't:xx:xx', MODE, 'precision', 'skipfactor', 'nozeropad').
- %
- % All input arguments are optional. Input arguments can be in any order.
- %
- % 'ver': Immediately return version information for openNSx
- % without processing any files.
- %
- % FNAME: Path of the file to be opened. If FNAME is omitted, a
- % file selection dialog box will appear.
- %
- % 'noread': Do not read the data contained in the file. Return only
- % header information. ('read' input still accepted for
- % legacy purposes, but is redundant with default behavior.)
- % DEFAULT: 'read'
- %
- % 'report': Show a summary report if user passes this argument.
- % DEFAULT: No report.
- %
- % 'electrodes',XX:YY
- % 'e:XX:YY': User can specify which electrodes need to be read. The
- % number of electrodes can be greater than or equal to 1
- % and less than or equal to 256. The electrodes can be
- % selected either by specifying a range (e.g. 20:45) or by
- % indicating individual electrodes (e.g. 3,6,7,90) or both.
- % Note that, when individual channels are to be read, all
- % channels in between will also be read. The prorgam will
- % then remove the unwanted channels. This may result in a
- % large memory footprint. If memory issues arrise please
- % consider placing openNSx in a for loop and reading
- % individual channels.
- % This field needs to be preceded by the prefix 'e:'. See
- % example for more details. If this option is selected the
- % user will be promped for a CMP mapfile (see: KTUEAMapFile)
- % provided by Blackrock Microsystems. This feature requires
- % KTUEAMapFile to be present in path.
- % DEFAULT: will read all existing electrodes.
- %
- % 'channels',XX:YY
- % 'c:XX:YY': User can specify which channels need to be read. The
- % number of channels can be greater than or equal to 1
- % and less than or equal to 272. The channels can be
- % selected either by specifying a range (e.g. 20:45) or by
- % indicating individual channels (e.g. 3,6,7,90) or both.
- % Note that, when individual channels are to be read, all
- % channels in between will also be read. The prorgam will
- % then remove the unwanted channels. This may result in a
- % large memory footprint. If memory issues arrise please
- % consider placing openNSx in a for loop and reading
- % individual channels.
- % This field needs to be preceded by the prefix 'c:'. See
- % example for more details.
- % DEFAULT: will read all existing analog channels.
- %
- % 'duration',XX:YY
- % 't:XX:YY': User can specify the beginning and end of the data
- % segment to be read. If the start time is greater than the
- % length of data the program will exit with an errorNS
- % message. If the end time is greater than the length of
- % data the end packet will be selected for end of data. The
- % user can specify the start and end values by comma
- % (e.g. [20,50]) or by a colon (e.g. [20:50]). To use this
- % argument the user must specify the [electrodes] or the
- % interval will be used for [electrodes] automatically.
- % This field needs to be preceded by the prefix 't:'.
- % Note that if 'mode' is 'sample' the start duration cannot
- % be less than 1. The duration is inclusive.
- % See example for more details.
- % DEFAULT: will read the entire file.
- %
- % MODE: Specify the units of duration values specified with
- % 'duration' (or 't:XX:YY') input. Valid values of MODE are
- % 'sec', 'secs', 'second', 'seconds'
- % 'min', 'mins', 'minute', 'minutes'
- % 'hour', 'hours'
- % 'sample', 'samples'
- % DEFAULT: 'sample'
- %
- % 'uV': Read the recording waveforms in unit of uV instead of raw
- % values. Note that this conversion requires 'double'
- % precision; if this argument is provided and precision has
- % been set to 'int16' or 'short', it will be updated to
- % 'double' with a warning.
- %
- % 'precision',P
- % 'p:P':
- % P Specify the precision P for data read from the NSx file.
- % Valid options are 'double' (64-bit floating point) or
- % 'int16' (or, equivalently, 'short'; 16-bit signed
- % integer). Data are stored in the file as int16 values.
- % Still, while 'int16' uses less memory, be mindful of
- % the limitations of integer data types
- % (https://www.mathworks.com/help/matlab/numeric-types.html).
- % Note that if the argument 'uV' is provided (conversion
- % from raw values to uV units), the precision will be
- % automatically set to 'double' if it is not already.
- % DEFAULT: 'int16'.
- %
- % 'skipfactor',S
- % 's:S': Decimate data read from disk, e.g., to quickly preview
- % data. The integer S will determine how many samples to
- % skip. For example, if S is 2 then every other sample is
- % read. This action is decimation only: no anti-aliasing
- % filter is applied.
- % DEFAULT: 1 (every sample read)
- %
- % 'zeropad': Prepend the data with zeros to compensate for non-zero
- % start time. Note that timestamps in newer data files may
- % be in the 10^18 range. Prepending this many zeros is not
- % advisable for normal computer systems, nor does it carry
- % the same logic as older data files where time zero was
- % relevant to the recording.
- % DEFAULT: No zero padding.
- %
- % 'noalign': Do not apply bug fix for clock drift in Central release
- % 7.6.0. In executing the bug fix (if this argument is not
- % provided), samples may be added or removed to restore
- % clock alignment. Changes are made at evenly spaced points
- % throughout the file. Samples are added by duplicating the
- % prior sample; they are removed by deleting a sample.
- % DEFAULT: Alignment occurs with warnings.
- %
- % 'max_tick_multiple', M:
- % Newer data files use PTP (precision time protocol) and
- % timestamp each sample of data, instead of only the first
- % sample in a frame of contiguous samples. To detect pauses
- % in a PTP recording, openNSx processes the file in frames:
- % it reads the timestamp of the first and last packets in
- % each frame (see 'packets_per_frame') and checks whether
- % the elapsed time is greater than it should be, assuming
- % contiguously recorded packets at the expected sampling
- % rate. The threshold M for the difference of elapsed time
- % is set as a multiple of the expected sampling interval.
- % If M is too small, openNSx will detect spurious pauses.
- % If it is too high, pauses will be missed. Note that due
- % to jitter in sample timing, this value should be set in
- % coordination with the number of packets in each frame
- % (see 'packets_per_frame') to ensure the sum of jittered
- % sampling intervals does not exceed the detection
- % threshold.
- % DEFAULT: 2 (equivalent to missing one sample)
- %
- % 'packets_per_frame', P:
- % Newer data files use PTP (precision time protocol) and
- % timestamp each sample of data, instead of only the first
- % sample in a frame of contiguous samples. To detect pauses
- % in a PTP recording, openNSx processes the file in frames,
- % each containing P packets: it reads the timestamp of the
- % first and last packets in each frame and checks whether
- % the elapsed time is greater than it should be, assuming
- % contiguously recorded packets at the expected sampling
- % rate (see 'max_tick_multiple'). The number of frames F is
- % given by CEIL(TOTAL_PACKETS/P), where TOTAL_PACKETS is
- % the number of packets in the file. Note that this method
- % reads only F+1 timestamps from disk if there are no
- % pauses detected. For each frame containing one or more
- % detected pauses, all P timestamps in the frame are read
- % from disk to identify the specific samples between which
- % the pauses occur. Thus, P can be increased to lower F,
- % but it should not be so large that a vector of P doubles
- % would not fit in memory. Note also that because of jitter
- % in sample timing, setting this value too large may lead
- % to spurious detections (i.e., the sum of jitter could be
- % greater than the detection threshold).
- % DEFAULT: 100,000 packets per frame.
- %
- % OUTPUT: The NSx structure.
- %
- % Example 1:
- % openNSx('report','read','c:\data\sample.ns5', 'e:15:30', 't:3:10','min', 'p:short', 's:5');
- %
- % or equivalently
- % openNSx('report','read','c:\data\sample.ns5', 'electrodes', 15:30, 'duration', 3:10, 'min', 'precision', 'short', 'skipfactor', 5);
- %
- % In the example above, the file c:\data\sample.ns5 will be used. A
- % report of the file contents will be shown. The data will be read from
- % electrodes 15 through 50 in the 3-10 minute time interval. A decimated
- % version of the datafile will be read, where only every 5th sample is
- % read.
- %
- % Example 2:
- % openNSx('read','c:15:30');
- %
- % In the example above, the user will be prompted for the file. The file
- % will be read using 'int16' precision as default. All time points of
- % channels 15 through 30 will be read.
- %
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- % Kian Torab
- % support@blackrockmicro.com
- % Blackrock Microsystems
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- % Version History
- %
- % 5.1.8.2:
- % - Fixed the way DayOfWeek is read in MetaTags.
- %
- % 5.1.9.0:
- % - Fixed a bug where with skipFactor being read correctly as a num.
- %
- % 5.1.10.0:
- % - Updated feature to save data headers for a paused file. It is a
- % dependent feature for seperatePausedNSx.
- %
- % 5.1.11.0:
- % - Fixed an issue where 1 sample would not be read when using the
- % t:xx:xx argument and 'sample'.
- % - Fixed an error when 'duration' was used to load specific data length.
- %
- % 5.1.12.0:
- % - Better error handling if a file is not provided and an output
- % variable was requested by the calling function.
- %
- % 5.2.0.0: June 12, 2014
- % - It removes the extra ElectrodesInfo entried for channels not
- % read if 'c:XX:XX' or 'e:XX:XX' are used.
- % - It reports variable ChannelCount under MetaTags correctly.
- % - It automatically compensate for any NSx file with non-0 beginnings
- % and adds 0s for to the begining of the file to properly align the
- % timestamps.
- %
- % 5.2.1.0: June 12, 2014
- % - Fixed a small bug where extra 0s were tacked on to the beginning of
- % paused file segments.
- % - Updated the version.
- %
- % 5.2.2.0: June 13, 2014
- % - Fixed bug for when 'noread' was used on a paused file.
- %
- % 6.0.1.0: December 2, 2014
- % - Fixed a bug related to file format 2.1 not being read correctly.
- % - Corrected the way Filename, FileExt, and FilePath was being
- % processed.
- % - File dialogue now only shows NSx files on non Windows-based
- % computers.
- % - Added 512 synchronized reading capability.
- % - Now on non-Windows computers only NSx files are shown in the file
- % dialogue.
- % - Fixed the date in NSx.MetaTags.DateTime.
- %
- % 6.1.0.0: March, 15 2015
- % - Added the ability to read from networked drives in Windows.
- % - Fixed the DateTime variable in MetaTags.
- % - Fixed the date in NSx.MetaTags.DateTime (again).
- % - Fixed a bug related to starting and stopping packets when a specific
- % time is passed to the function.
- % - Fixed a bug where 512+ ch rules were being applied to smaller channel
- % count configuration.
- %
- % 6.1.1.0: June 15, 2015
- % - Bug fixes related to timestamps when the recording didn't start at
- % proctime 0.
- %
- % 6.2.0.0: October 1, 2015
- % - Fixed a bug related to reading the correct length of time when a skip
- % factor was used.
- % - Bug fixes related to information that separatePausedNSx depends on.
- % - Added 'uV' as an option to read the data in the unit of uV.
- %
- % 6.2.1.0: April 16, 2016
- % - Fixed a bug related to converting the unit to uV in case of having
- % multiple data segments (paused file).
- %
- % 6.2.2.0: July 6, 2016
- % - Fixed another bug related to converting the unit to uV.
- %
- % 6.3.0.0: August 3, 2016
- % - Added support for loading a segment of paused files.
- %
- % 6.3.1.0: August 31, 2016
- % - Fixed a bug when reading a non-o start across a paused segment.
- %
- % 6.4.0.0: December 1, 2016
- % - Fixed a serious bug related to loading paused files.
- % - Fixed a bug where an empty data segment resulted in a cell structure.
- %
- % 6.4.1.0: June 15, 2017
- % - It is no longer necessary to provide the full path for loading a
- % file.
- %
- % 6.4.2.0: September 1, 2017
- % - Fixed a bug related to reading data from sample that is not 1 and
- % timestamp that used to get reset to 0.
- %
- % 6.4.3.0: September 13, 2017
- % - Removed a redundant block of code that was accidentally placed in the
- % script twice.
- % - Checks to see if there's a newer version of NPMK is available.
- %
- % 6.4.3.1: January 24, 2020
- % - Changed file opening access from r+ to r.
- %
- % 7.0.0.0: January 27, 2020
- % - Added support for 64-bit timestamps in NEV and NSx.
- %
- % 7.1.0.0: April 14, 2020
- % - Added option to load the data without zero padding to compensate for
- % a non-zero start time. (David Kluger)
- % - Bug fixes and documentation updates (David Kluger)
- %
- % 7.1.1.0: June 11, 2020
- % - Fixed a bug related to fread and MATLAB 2020a.
- %
- % 7.3.0.0: September 11, 2020
- % - Fixed a bug related to fread and MATLAB 2020a.
- % - Gives a warning about FileSpec 3.0 and gives the user options for how
- % to proceed.
- % - Added a warning about the data unit and that by default it in the
- % unit of 250 nV or 1/4 ???V.
- % - If the units are in "raw", ths correct information is now written to
- % the electrodes header: 250 nV (raw).
- %
- % 7.3.1.0: October 2, 2020
- % - If the units are in ???V (openNSx('uv'), ths correct information is now
- % written to the electrodes header: 1000 nV (raw).
- %
- % 7.3.2.0: October 23, 2020
- % - Fixed a typo.
- %
- % 7.4.0.0: October 29, 2020
- % - Undid changes made to AnalogUnit and instead implemented
- % NSx.ElectrodesInfo.Resolution to show what the resolution of the data
- % is. By default, the resolution is set to 0.250 ???V. If used with
- % parameter 'uv', the resolution will be 1 ???V. To always convert the
- % data to ???V, divide NSx.Data(CHANNEL,:) by
- % NSx.ElectrodesInfo(CHANNEL).Resolution.
- %
- % 7.4.1.0: April 20, 2021
- % - Fixed a bug related to file opening.
- %
- % 7.4.2.0: May 5, 2021
- % - Fixed a bug related to NeuralSG file format (File Spec 2.1).
- %
- % 7.4.3.0: July 16, 2021
- % - Fixed a minor bug for when the data header is not written properly
- % and the data needs to be used to calculate the data length.
- %
- % 7.4.4.0: April 1, 2023
- % - Accounts for many segments in files for clock drift correction
- % - Changed 'zeropad' default behavior to be 'no'
- %
- % 7.4.5.0: October 5, 2023
- % - Bank numbers on new files are not alpha which causes problems on save
- %
- % 7.4.6.0: December 6, 2023
- % - Better support for reading files recorded from Gemini systems
- % - Improved speed and memory usage for Gemini system recordings
- % - Change messages about errors to actual errors
- % - NPMK SettingsManager, getFile, and NPMKverChecker made optional
- % - Force 'double' precision (with warning) if conversion to uV requested
- % - Repair skipfactor implementation
- % - Clean up documentation
- % - Clean up code
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- %% Define the NSx data structure and sub-branches.
- NSx = struct('MetaTags',[],'Data',[], 'RawData', []);
- NSx.MetaTags = struct('FileTypeID',[],'SamplingLabel',[],'ChannelCount',[],'SamplingFreq',[], 'TimeRes', [], ...
- 'ChannelID',[],'DateTime',[],'DateTimeRaw',[], 'Comment', [], 'FileSpec', [], ...
- 'Timestamp', [], 'DataPoints', [], 'DataDurationSec', [], 'openNSxver', [], 'Filename', [], 'FilePath', [], ...
- 'FileExt', []);
- NSx.MetaTags.openNSxver = '7.4.6.0';
- %% Check for the latest version of NPMK
- if exist('NPMKverChecker','file')==2
- NPMKverChecker
- end
- %% Define constants and defaults
- extHeaderEntrySize = 66;
- NSx.RawData.PausedFile = 0;
- syncShift = 0;
- flagFoundSettingsManager = exist('settingsManager','file')==2;
- flagFoundGetFile = exist('getFile','file')==2;
- NPMKSettings = [];
- if flagFoundSettingsManager
- NPMKSettings = settingsManager;
- end
- % Default values
- flagReport = 0;
- flagReadData = 1;
- flagElectrodeReading = 0;
- flagModifiedTime = 0;
- flagMultiNSP = 1;
- flagZeroPad = 0;
- flagAlign = 1;
- flagConvertToUv = 0;
- flagOneSamplePerPacket = 0;
- requestedTimeScale = 'sample';
- requestedPrecisionType = 'int16';
- requestedSkipFactor = 1;
- requestedPacketsPerFrame = 100000;
- requestedMaxTickMultiple = 2;
- requestedChanRow = [];
- requestedFileName = '';
- %% Process input arguments
- next = '';
- for i=1:length(varargin)
- inputArgument = varargin{i};
- if ischar(inputArgument) && strcmpi(inputArgument, 'ver')
- varargout{1} = NSx.MetaTags.openNSxver;
- return;
- elseif ischar(inputArgument) && strcmpi(inputArgument, 'channels')
- next = 'channels';
- elseif ischar(inputArgument) && strcmpi(inputArgument, 'skipfactor')
- next = 'skipfactor';
- elseif ischar(inputArgument) && strcmpi(inputArgument, 'electrodes')
- next = 'electrodes';
- elseif ischar(inputArgument) && strcmpi(inputArgument, 'duration')
- next = 'duration';
- elseif ischar(inputArgument) && strcmpi(inputArgument, 'precision')
- next = 'precision';
- elseif ischar(inputArgument) && strcmpi(inputArgument, 'packets_per_frame')
- next = 'packets_per_frame';
- elseif ischar(inputArgument) && strcmpi(inputArgument, 'max_tick_multiple')
- next = 'max_tick_multiple';
- elseif ischar(inputArgument) && strcmpi(inputArgument, 'report')
- flagReport = 1;
- elseif ischar(inputArgument) && strcmpi(inputArgument, 'noread')
- flagReadData = 0;
- elseif ischar(inputArgument) && strcmpi(inputArgument, 'nomultinsp')
- flagMultiNSP = 0;
- elseif ischar(inputArgument) && strcmpi(inputArgument, 'zeropad')
- flagZeroPad = 1;
- elseif ischar(inputArgument) && strcmpi(inputArgument, 'uV')
- flagConvertToUv = 1;
- elseif ischar(inputArgument) && strcmpi(inputArgument, 'noalign')
- flagAlign = false;
- elseif ischar(inputArgument) && strcmpi(inputArgument, 'read')
- flagReadData = 1;
- elseif ischar(inputArgument) && ((strncmp(inputArgument, 't:', 2) && inputArgument(3) ~= '\' && inputArgument(3) ~= '/') || strcmpi(next, 'duration'))
- if strncmp(inputArgument, 't:', 2)
- inputArgument(1:2) = [];
- inputArgument = str2num(inputArgument); %#ok<ST2NM>
- elseif ischar(inputArgument)
- inputArgument = str2num(inputArgument); %#ok<ST2NM>
- end
- flagModifiedTime = 1;
- requestedStartValue = inputArgument(1);
- requestedEndValue = inputArgument(end);
- next = '';
- elseif ischar(inputArgument) && ((strncmp(inputArgument, 'e:', 2) && inputArgument(3) ~= '\' && inputArgument(3) ~= '/') || strcmpi(next, 'electrodes'))
- assert(exist('KTUEAMapFile','file')==2,'To read data by ''electrodes'' the function KTUEAMapFile needs to be in path.');
- mapFile = KTUEAMapFile;
- requestedElectrodes = str2num(inputArgument(3:end)); %#ok<ST2NM>
- if min(requestedElectrodes)<1 || max(requestedElectrodes)>128
- assert(min(requestedElectrodes)>=1 && max(requestedElectrodes)<=128, 'The electrode number cannot be less than 1 or greater than 128.');
- end
- requestedChannels = nan(1,length(requestedElectrodes));
- for chanIDX = 1:length(requestedElectrodes)
- requestedChannels(chanIDX) = mapFile.Electrode2Channel(requestedElectrodes(chanIDX));
- end
- flagElectrodeReading = 1;
- next = '';
- elseif ischar(inputArgument) && ((strncmp(inputArgument, 's:', 2) && inputArgument(3) ~= '\' && inputArgument(3) ~= '/') || strcmpi(next, 'skipFactor'))
- if strncmp(inputArgument, 's:', 2)
- requestedSkipFactor = str2num(inputArgument(3:end)); %#ok<ST2NM>
- elseif ischar(inputArgument)
- requestedSkipFactor = str2num(inputArgument); %#ok<ST2NM>
- else
- requestedSkipFactor = inputArgument;
- end
- next = '';
- elseif ischar(inputArgument) && ((strncmp(inputArgument, 'c:', 2) && inputArgument(3) ~= '\' && inputArgument(3) ~= '/') || strcmpi(next, 'channels'))
- if strncmp(inputArgument, 'c:', 2)
- requestedChanRow = str2num(inputArgument(3:end)); %#ok<ST2NM>
- elseif ischar(inputArgument)
- requestedChanRow = str2num(inputArgument(3:end)); %#ok<ST2NM>
- else
- requestedChanRow = inputArgument;
- end
- next = '';
- elseif ischar(inputArgument) && (any(strcmpi(inputArgument,{'double','int16','short'})) || (strncmp(varargin{i}, 'p:', 2) && inputArgument(3) ~= '\' && inputArgument(3) ~= '/') || strcmpi(next, 'precision'))
- if strncmpi(inputArgument, 'p:', 2)
- precisionTypeRaw = inputArgument(3:end);
- else
- precisionTypeRaw = inputArgument;
- end
- switch precisionTypeRaw
- case {'int16','short'}
- requestedPrecisionType = 'int16';
- case 'double'
- requestedPrecisionType = 'double';
- otherwise
- error('Precision type is not valid. Refer to ''help'' for more information.');
- end
- next = '';
- elseif strcmpi(next, 'packets_per_frame')
- if ischar(inputArgument)
- requestedPacketsPerFrame = str2double(inputArgument);
- else
- requestedPacketsPerFrame = inputArgument;
- end
- elseif strcmpi(next, 'max_tick_multiple')
- if ischar(inputArgument)
- requestedMaxTickMultiple = str2double(inputArgument);
- else
- requestedMaxTickMultiple = inputArgument;
- end
- elseif ischar(inputArgument) && ...
- (strncmpi(inputArgument,'hours',4) || strncmpi(inputArgument,'hrs',2) || ...
- strncmpi(inputArgument,'minutes',3) || strncmpi(inputArgument,'mins',3) || ...
- strncmpi(inputArgument,'seconds',3) || strncmpi(inputArgument,'secs',3) || ...
- strncmpi(inputArgument,'samples',4))
- requestedTimeScale = inputArgument;
- elseif ischar(inputArgument) && length(inputArgument)>3 && ...
- (strcmpi(inputArgument(3),'\') || ...
- strcmpi(inputArgument(1),'/') || ...
- strcmpi(inputArgument(2),'/') || ...
- strcmpi(inputArgument(1:2), '\\') || ...
- strcmpi(inputArgument(end-3), '.'))
- requestedFileName = inputArgument;
- assert(exist(requestedFileName, 'file')==2,'The file does not exist.');
- else
- error(['Invalid argument ''' inputArgument '''.']);
- end
- end
- clear next;
- % check uV conversion versus data type
- if flagConvertToUv && ~strcmpi(requestedPrecisionType,'double')
- warning('Conversion to uV requires double precision; updating from %s to comply',requestedPrecisionType);
- requestedPrecisionType = 'double';
- end
- if ~flagReadData
- warning('Reading the header information only.');
- end
- if flagReport
- disp(['openNSx ' NSx.MetaTags.openNSxver]);
- end
- %% Identify data file name, path, and extension
- % for later use, and validate the entry.
- if isempty(requestedFileName)
- title = 'Choose an NSx file...';
- filterSpec = '*.ns*';
- if flagFoundGetFile
- [requestedFileName, requestedFilePath] = getFile(filterSpec, title);
- else
- [requestedFileName, requestedFilePath] = uigetfile(filterSpec, title);
- end
- assert(ischar(requestedFileName),'No file selected');
- [~, ~, requestedFileExtension] = fileparts(requestedFileName);
- else
- if isempty(fileparts(requestedFileName))
- requestedFileName = which(requestedFileName);
- end
- [requestedFilePath,requestedFileName, requestedFileExtension] = fileparts(requestedFileName);
- requestedFileName = [requestedFileName requestedFileExtension];
- requestedFilePath = [requestedFilePath '/'];
- end
- assert(ischar(requestedFileName)||requestedFileName~=0,'Could not identify file to read');
- fileFullPath = fullfile(requestedFilePath, requestedFileName);
- [NSx.MetaTags.FilePath, NSx.MetaTags.Filename, NSx.MetaTags.FileExt] = fileparts(fileFullPath);
- % Check to see if 512 setup and calculate offset
- if flagMultiNSP
- flag512 = regexp(requestedFileName, '-i[0123]-', 'ONCE');
- if ~isempty(flag512)
- syncShift = multiNSPSync(fullfile(requestedFilePath, requestedFileName));
- else
- flagMultiNSP = 0;
- end
- end
- %% Loading .x files for multiNSP configuration
- if strcmpi(requestedFileExtension(2:4), 'ns6') && length(requestedFileExtension) == 5
- requestedFilePath(1) = requestedFileName(end);
- requestedFileName(end) = [];
- end
- %% Measure time required to load data
- tic;
- %% Process file
- FID = fopen([requestedFilePath requestedFileName], 'r', 'ieee-le');
- try
-
- %% Read Headers
- NSx.MetaTags.FileTypeID = fread(FID, [1,8], 'uint8=>char');
- if strcmpi(NSx.MetaTags.FileTypeID, 'NEURALSG')
-
- %% Read Basic Header
- basicHeaderBytes = fread(FID, 24, '*uint8');
- NSx.MetaTags.FileSpec = '2.1';
- NSx.MetaTags.SamplingLabel = char(basicHeaderBytes(1:16));
- NSx.MetaTags.TimeRes = double(30000);
- NSx.MetaTags.SamplingFreq = NSx.MetaTags.TimeRes / double(typecast(basicHeaderBytes(17:20),'uint32'));
- channelCount = double(typecast(basicHeaderBytes(21:24),'uint32'));
- NSx.MetaTags.ChannelCount = channelCount;
-
- %% Read Extended Header
- extendedHeaderSize = channelCount*4;
- extendedHeaderBytes = fread(FID, extendedHeaderSize, '*uint8');
- NSx.MetaTags.ChannelID = typecast(extendedHeaderBytes, 'uint32');
- try
- t = dir(fileFullPath);
- NSx.MetaTags.DateTime = t.date;
- catch ME2
- warning('openNSx:NEURALSG_datetime','Could not compute date from file: %s',ME2.message)
- NSx.MetaTags.DateTime = '';
- end
- timestampSize = 4;
- elseif or(strcmpi(NSx.MetaTags.FileTypeID, 'NEURALCD'), strcmpi(NSx.MetaTags.FileTypeID, 'BRSMPGRP'))
-
- %% Read Basic Header
- basicHeaderBytes = fread(FID, 306, '*uint8');
- NSx.MetaTags.FileSpec = [num2str(double(basicHeaderBytes(1))) '.' num2str(double(basicHeaderBytes(2)))];
- %BasicHeaderSize = double(typecast(BasicHeader(3:6), 'uint32'));
- NSx.MetaTags.SamplingLabel = char(basicHeaderBytes(7:22))';
- NSx.MetaTags.Comment = char(basicHeaderBytes(23:278))';
- NSx.MetaTags.TimeRes = double(typecast(basicHeaderBytes(283:286), 'uint32'));
- NSx.MetaTags.SamplingFreq = double(30000 / double(typecast(basicHeaderBytes(279:282), 'uint32')));
- t = double(typecast(basicHeaderBytes(287:302), 'uint16'));
- channelCount = double(typecast(basicHeaderBytes(303:306), 'uint32'));
- NSx.MetaTags.ChannelCount = channelCount;
- if strcmpi(NSx.MetaTags.FileTypeID, 'NEURALCD')
- timestampSize = 4;
- elseif strcmpi(NSx.MetaTags.FileTypeID, 'BRSMPGRP')
- timestampSize = 8;
- end
-
- % Removing extra garbage characters from the Comment field.
- NSx.MetaTags.Comment(find(NSx.MetaTags.Comment==0,1):end) = 0;
-
- %% Read Extended Header
- extendedHeaderSize = double(channelCount * extHeaderEntrySize);
- extendedHeaderBytes = fread(FID, extendedHeaderSize, '*uint8');
- for headerIDX = 1:channelCount
- byteOffset = double((headerIDX-1)*extHeaderEntrySize);
- NSx.ElectrodesInfo(headerIDX).Type = char(extendedHeaderBytes((1:2)+byteOffset))';
- assert(strcmpi(NSx.ElectrodesInfo(headerIDX).Type, 'CC'),'extended header not supported');
-
- NSx.ElectrodesInfo(headerIDX).ElectrodeID = typecast(extendedHeaderBytes((3:4)+byteOffset), 'uint16');
- NSx.ElectrodesInfo(headerIDX).Label = char(extendedHeaderBytes((5:20)+byteOffset))';
- NSx.ElectrodesInfo(headerIDX).ConnectorBank = extendedHeaderBytes(21+byteOffset);
- NSx.ElectrodesInfo(headerIDX).ConnectorPin = extendedHeaderBytes(22+byteOffset);
- NSx.ElectrodesInfo(headerIDX).MinDigiValue = typecast(extendedHeaderBytes((23:24)+byteOffset), 'int16');
- NSx.ElectrodesInfo(headerIDX).MaxDigiValue = typecast(extendedHeaderBytes((25:26)+byteOffset), 'int16');
- NSx.ElectrodesInfo(headerIDX).MinAnalogValue = typecast(extendedHeaderBytes((27:28)+byteOffset), 'int16');
- NSx.ElectrodesInfo(headerIDX).MaxAnalogValue = typecast(extendedHeaderBytes((29:30)+byteOffset), 'int16');
- NSx.ElectrodesInfo(headerIDX).AnalogUnits = char(extendedHeaderBytes((31:46)+byteOffset))';
- if flagConvertToUv
- NSx.ElectrodesInfo(headerIDX).Resolution = 1;
- else
- NSx.ElectrodesInfo(headerIDX).Resolution = ...
- round(double(NSx.ElectrodesInfo(headerIDX).MaxAnalogValue) / double(NSx.ElectrodesInfo(headerIDX).MaxDigiValue),4);
- end
- % if strcmpi(waveformUnits, 'uV')
- % NSx.ElectrodesInfo(headerIDX).AnalogUnits = '1000 nV (raw) ';
- % else
- % conversion = int16(double(NSx.ElectrodesInfo(headerIDX).MaxAnalogValue) / double(NSx.ElectrodesInfo(headerIDX).MaxDigiValue)*1000);
- % NSx.ElectrodesInfo(headerIDX).AnalogUnits = [num2str(conversion), ' nV (raw) '];
- % end
- NSx.ElectrodesInfo(headerIDX).HighFreqCorner = typecast(extendedHeaderBytes((47:50)+byteOffset), 'uint32');
- NSx.ElectrodesInfo(headerIDX).HighFreqOrder = typecast(extendedHeaderBytes((51:54)+byteOffset), 'uint32');
- NSx.ElectrodesInfo(headerIDX).HighFilterType = typecast(extendedHeaderBytes((55:56)+byteOffset), 'uint16');
- NSx.ElectrodesInfo(headerIDX).LowFreqCorner = typecast(extendedHeaderBytes((57:60)+byteOffset), 'uint32');
- NSx.ElectrodesInfo(headerIDX).LowFreqOrder = typecast(extendedHeaderBytes((61:64)+byteOffset), 'uint32');
- NSx.ElectrodesInfo(headerIDX).LowFilterType = typecast(extendedHeaderBytes((65:66)+byteOffset), 'uint16');
- end
-
- % Parse DateTime
- NSx.MetaTags.DateTimeRaw = t.';
- NSx.MetaTags.DateTime = datestr(datenum(t(1), t(2), t(4), t(5), t(6), t(7)));
- else
- error('Unsupported file spec %s', NSx.MetaTags.FileSpec);
- end
-
- % Check zeropad if timeres is 1e9
- if NSx.MetaTags.TimeRes > 1e5
- assert(~flagZeroPad,'No zeropad for nanosecond resolution timestamps');
- end
-
- % Copy ChannelID to MetaTags for filespec 2.2, 2.3, and 3.0 for compatibility with filespec 2.1
- if or(strcmpi(NSx.MetaTags.FileTypeID, 'NEURALCD'), strcmpi(NSx.MetaTags.FileTypeID, 'BRSMPGRP'))
- NSx.MetaTags.ChannelID = [NSx.ElectrodesInfo.ElectrodeID]';
- end
-
- %% Identify key points in file
- f.EOexH = double(ftell(FID));
- fseek(FID, 0, 'eof');
- f.EOF = double(ftell(FID));
-
- %% Save raw headers for saveNSx
- NSx.RawData.Headers = [uint8(NSx.MetaTags.FileTypeID(:)); basicHeaderBytes(:); extendedHeaderBytes(:)];
-
- %% Determine the number of channels to read
- if ~flagElectrodeReading
- if isempty(requestedChanRow)
- requestedChannels = NSx.MetaTags.ChannelID;
- else
- assert(all(requestedChanRow<=channelCount),'Channel numbers must be less than or equal to the total number of channels in the file (%d)',channelCount);
- requestedChannels = NSx.MetaTags.ChannelID(requestedChanRow);
- NSx.MetaTags.ChannelCount = length(requestedChannels);
- end
- else
- NSx.MetaTags.ChannelCount = length(requestedChannels);
- end
-
- if isempty(requestedChanRow)
- requestedChanRow = nan(1,length(requestedChannels));
- for idx = 1:length(requestedChannels)
- assert(ismember(requestedChannels(idx), NSx.MetaTags.ChannelID),'Channel %d does not exist in this file',requestedChannels(idx));
- requestedChanRow(idx) = find(NSx.MetaTags.ChannelID == requestedChannels(idx),1);
- end
- end
- numChansToRead = double(length(min(requestedChanRow):max(requestedChanRow)));
-
- %% Central v7.6.0 needs corrections for PTP clock drift - DK 20230303
- if NSx.MetaTags.TimeRes > 1e5
- packetSize = 1 + 8 + 4 + channelCount*2; % byte (Header) + uint64 (Timestamp) + uint32 (Samples, always 1) + int16*nChan (Data)
- numPacketsTotal = floor((f.EOF - f.EOexH)/packetSize);
- fseek(FID, f.EOexH + 1 + 8, 'bof'); % byte (Header) + uint64 (Timestamp)
- patchCheck = fread(FID,10,'uint32',packetSize-4); % read "samples" counts from 10 packets
- if sum(patchCheck) == length(patchCheck) % verify all 1
- flagOneSamplePerPacket = true;
- end
- end
-
- %% Identify and describe data segments
- fseek(FID, f.EOexH, 'bof');
- if strcmpi(NSx.MetaTags.FileTypeID, 'NEURALSG')
- NSx.MetaTags.Timestamp = 0; % No timestamp otherwise
- NSx.MetaTags.DataPoints = double(f.EOF-f.EOexH)/(channelCount*2);
- NSx.MetaTags.DataDurationSec = NSx.MetaTags.DataPoints/NSx.MetaTags.SamplingFreq;
- elseif or(strcmpi(NSx.MetaTags.FileTypeID, 'NEURALCD'), strcmpi(NSx.MetaTags.FileTypeID, 'BRSMPGRP'))
- if flagOneSamplePerPacket
-
- % Clock drift patch kills ability to segment files. This check will
- % allow segments to be reintroduced into the data structures if a
- % timestamp difference of 200% greater than expected is identified
- fseek(FID,f.EOexH + 1,'bof'); % + byte (header)
-
- % Process file in frames. initialize with the first packet's
- % timestamp.
- % For each frame, read the timestamp of the last packet.
- % if the difference from the previous frame's last timestamp is
- % larger than expected given consistent sampling rates, define a
- % segment.
- % Move to the next frame.
- ticksPerSample = NSx.MetaTags.TimeRes/NSx.MetaTags.SamplingFreq;
- minimumPauseLength = requestedMaxTickMultiple*ticksPerSample;
- timestampFirst = fread(FID,1,'uint64');
- numPacketsProcessed = 0;
- segmentTimestamps = nan(1,1e3);
- segmentTimestamps(1) = timestampFirst;
- % segmentDatapoints = nan(1,1e3);
- % segmentDurations = nan(1,1e3);
- segmentDatapoints = zeros(1,1e3);
- segmentDurations = zeros(1,1e3);
- currSegment = 1;
- while double(ftell(FID)) < (f.EOF-(packetSize-1-8))
-
- % frames have 'packets_per_frame' packets until the end of the
- % file, when the frame may have fewer packets
- % number of packets per frame includes first/last packet, which
- % means there is one fewer gap than the number of packets
- currPacketStartByte = double(ftell(FID)) - 8 - 1;
- frameNumPackets = min(requestedPacketsPerFrame, (f.EOF - currPacketStartByte)/packetSize);
- if abs(round(frameNumPackets)-frameNumPackets)>0.1
- warning('File not packet-aligned')
- end
- bytesToFrameLastTimestamp = packetSize*(frameNumPackets-1) - 8;
-
- % compute the ticks expected to elapse in this frame with the
- % smallest detectable pause (2x sample time, or 66.6 usec)
- expectedTicksElapsedNoPause = (frameNumPackets-1) * ticksPerSample;
- expectedTicksElapsedMinPause = expectedTicksElapsedNoPause + (minimumPauseLength - ticksPerSample);
-
- % seek to last packet of this frame and read timestamp
- fseek(FID, bytesToFrameLastTimestamp, 'cof');
- timestampLast = fread(FID,1,'uint64');
-
- % check whether elapsed time for this frame meets or exceeds
- % expected length with minimum gap
- actualTicksElapsed = timestampLast - timestampFirst;
- if actualTicksElapsed >= expectedTicksElapsedMinPause
-
- % a gap exists in this frame; we need to identify where it
- % occurs
- % save file pointer position
- currBytePosition = ftell(FID);
-
- % rewind to prior last_timestamp
- fseek(FID, -(bytesToFrameLastTimestamp+8+8), 'cof');
-
- % read all timestamps in this frame
- timestamps = fread(FID, frameNumPackets, 'uint64', packetSize-8)';
-
- % find gaps and store if found
- tsDiffs = diff(timestamps);
- vals = find(tsDiffs > minimumPauseLength);
- for jj=1:length(vals)
- numDatapointsLastSegment = numPacketsProcessed - sum(segmentDatapoints(1:currSegment)) + vals(jj);
- segmentDatapoints(currSegment) = numDatapointsLastSegment;
- segmentDurations(currSegment) = timestamps(vals(jj)) - segmentTimestamps(currSegment) + 1;
- segmentTimestamps(currSegment + 1) = timestamps(vals(jj) + 1);
- currSegment = currSegment + 1;
- end
-
- % restore file pointer position
- fseek(FID, currBytePosition, 'bof');
- end
-
- % update for next round
- % -1 on the number of packets processed because the last packet
- % is included in the next frame also
- timestampFirst = timestampLast;
- numPacketsProcessed = numPacketsProcessed + frameNumPackets - 1;
- end
- numPacketsProcessed = numPacketsProcessed + 1; % account for the overlapped sample on each frame
- assert(numPacketsProcessed == numPacketsTotal, 'Incosistent number of packets processed (%d) versus number of packets in file (%d)',numPacketsProcessed,(f.EOF-f.EOexH)/packetSize);
-
- % compute number of datapoints in the last segment
- % add one to the number of packets processed to account for the
- % last packet of the file not being included in a subsequent frame
- segmentDatapoints(currSegment) = numPacketsProcessed - sum(segmentDatapoints(1:currSegment-1));
- segmentDurations(currSegment) = timestampLast - segmentTimestamps(currSegment) + 1;
- % add into NSx structure
- NSx.MetaTags.Timestamp = segmentTimestamps(1:currSegment);
- NSx.MetaTags.DataPoints = segmentDatapoints(1:currSegment);
- NSx.MetaTags.DataDurationSec = segmentDurations(1:currSegment)/NSx.MetaTags.TimeRes;
- file.MetaTags.DataDurationTimeRes = segmentDurations(1:currSegment);
- else
- segmentCount = 0;
- while double(ftell(FID)) < f.EOF
- headerByte = fread(FID, 1, 'uint8=>double');
- if headerByte ~= 1
- % Fixing another bug in Central 6.01.00.00 TOC where DataPoints is
- % not written back into the Data Header
- %% BIG NEEDS TO BE FIXED
- NSx.MetaTags.DataPoints = floor(double(f.EOF - (f.EOexH+1+timestampSize+4))/(channelCount*2));
- NSx.MetaTags.DataDurationSec = NSx.MetaTags.DataPoints/NSx.MetaTags.SamplingFreq;
- break;
- end
- segmentCount = segmentCount + 1;
- if strcmpi(NSx.MetaTags.FileTypeID, 'NEURALCD')
- startTimestamp = fread(FID, 1, 'uint32');
- elseif strcmpi(NSx.MetaTags.FileTypeID, 'BRSMPGRP')
- startTimestamp = fread(FID, 1, 'uint64');
- end
- if flagMultiNSP
-
- % close existing (read-only) file descriptor
- currBytePosition = ftell(FID);
- fclose(FID);
-
- % open a file descriptor for read/write, write, and close
- FID = fopen([requestedFilePath requestedFileName], 'r+', 'ieee-le');
- startTimestamp = startTimestamp + syncShift;
- fseek(FID, -timestampSize, 'cof');
- fwrite(FID, startTimestamp, '*uint32');
- fclose(FID);
-
- % re-open read-only and seek to remembered position
- FID = fopen([requestedFilePath requestedFileName], 'r', 'ieee-le');
- fseek(FID,currBytePosition,'bof');
- end
- NSx.MetaTags.Timestamp(segmentCount) = startTimestamp;
- NSx.MetaTags.DataPoints(segmentCount) = fread(FID, 1, 'uint32=>double');
- NSx.MetaTags.DataDurationSec(segmentCount) = NSx.MetaTags.DataPoints(segmentCount)/NSx.MetaTags.SamplingFreq;
- file.MetaTags.DataDurationTimeRes(segmentCount) = startTimestamp*NSx.MetaTags.TimeRes/NSx.MetaTags.SamplingFreq;
- fseek(FID, NSx.MetaTags.DataPoints(segmentCount) * channelCount * 2, 'cof');
- % Fixing the bug in 6.01.00.00 TOC where DataPoints is not
- % updated and is left as 0
- % NSx.MetaTags.DataPoints(segmentCount) = (f.EOData(segmentCount)-f.BOData(segmentCount))/(ChannelCount*2);
- end
- end
- end
-
- %% Calculate file pointers for data
- if strcmpi(NSx.MetaTags.FileTypeID, 'NEURALSG')
- % Determining DataPoints
- f.BOData = f.EOexH;
- f.EOData = f.EOF;
- elseif or(strcmpi(NSx.MetaTags.FileTypeID, 'NEURALCD'), strcmpi(NSx.MetaTags.FileTypeID, 'BRSMPGRP'))
- byteOffset = 1 + timestampSize + 4;
- if flagOneSamplePerPacket
- segmentOffset = f.EOexH;
- f.BOData = segmentOffset + byteOffset + [0 packetSize*cumsum(NSx.MetaTags.DataPoints(1:end-1))];
- f.EOData = segmentOffset + packetSize*cumsum(NSx.MetaTags.DataPoints);
- else
- segmentOffset = f.EOexH + (1:length(NSx.MetaTags.DataPoints))*byteOffset;
- f.BOData = segmentOffset + [0 cumsum(channelCount*NSx.MetaTags.DataPoints(1:end-1)*2)];
- f.EOData = segmentOffset + 2*channelCount*cumsum(NSx.MetaTags.DataPoints) - 1;
- end
- end
-
- % Determining if the file has a pause in it
- if length(NSx.MetaTags.DataPoints) > 1
- NSx.RawData.PausedFile = 1;
- end
-
- %% Save data headers for saveNSx
- dataHeaderBytes = cell(1,length(f.BOData));
- for ss = 1:length(f.BOData)
- headerByteSize = 1 + timestampSize + 4;
- fseek(FID, f.BOData(ss)-headerByteSize, 'bof');
- dataHeaderBytes{ss} = fread(FID, headerByteSize, '*uint8');
- end
- NSx.RawData.DataHeader = cat(1,dataHeaderBytes{:});
-
- %% Remove extra ElectrodesInfo for channels not read
- if or(strcmpi(NSx.MetaTags.FileTypeID, 'NEURALCD'), strcmpi(NSx.MetaTags.FileTypeID, 'BRSMPGRP'))
- for headerIDX = length(NSx.ElectrodesInfo):-1:1
- if ~ismember(headerIDX, requestedChanRow)
- NSx.ElectrodesInfo(headerIDX) = [];
- end
- end
- end
-
- %% Identify and validate requested first and last data points
- % Note that new files that use precision time protocol (PTP), for which
- % there is one sample per packet and hence the user request translates
- % to a starting and ending data packet. In older NSx files, there were
- % multiple samples per packet and the user request is for a starting
- % sample (which would exist inside a data packet). This code refers to
- % "data points" as a broad term to encapsulate both scenarios.
- if ~flagModifiedTime
-
- % default whole file
- requestedStartDataPoint = 1;
- requestedEndDataPoint = sum(NSx.MetaTags.DataPoints);
- else
-
- % TO-DO: utilize Gemini sample-by-sample timestamps
- switch requestedTimeScale
- case {'sec', 'secs', 'second', 'seconds'}
-
- % convert seconds to samples
- requestedStartDataPoint = requestedStartValue * NSx.MetaTags.SamplingFreq + 1;
- requestedEndDataPoint = requestedEndValue * NSx.MetaTags.SamplingFreq;
- case {'min', 'mins', 'minute', 'minutes'}
-
- % convert minutes to samples
- requestedStartDataPoint = requestedStartValue * NSx.MetaTags.SamplingFreq * 60 + 1;
- requestedEndDataPoint = requestedEndValue * NSx.MetaTags.SamplingFreq * 60;
- case {'hour', 'hours'}
-
- % convert hours to samples
- requestedStartDataPoint = requestedStartValue * NSx.MetaTags.SamplingFreq * 3600 + 1;
- requestedEndDataPoint = requestedEndValue * NSx.MetaTags.SamplingFreq * 3600;
- case {'sample','samples'}
-
- % carry over samples
- requestedStartDataPoint = requestedStartValue;
- requestedEndDataPoint = requestedEndValue;
- otherwise
-
- % should never get here based on input argument processing
- error('Unknown requested time scale');
- end
- end
-
- % validate start and end data points
- assert(requestedEndDataPoint>=requestedStartDataPoint,'Start data point (%d) must be less than the end data point (%d)',requestedStartDataPoint,requestedEndDataPoint);
- assert(requestedStartDataPoint<=sum(NSx.MetaTags.DataPoints),'Start data point (%d) is greater than total number of data point (%d)',requestedStartDataPoint,sum(NSx.MetaTags.DataPoints));
- if requestedStartDataPoint <= 0
- warning('Start data point (%d) must be greater than or equal to 1; updating to comply.',requestedStartDataPoint);
- requestedStartDataPoint = 1;
- end
- if requestedEndDataPoint > sum(NSx.MetaTags.DataPoints)
- warning('End data point (%d) must be less than or equal to the total number of data points (%d).',requestedEndDataPoint,sum(NSx.MetaTags.DataPoints));
- response = input('Do you wish to update the last requested data point to last one available in the file and continue? (y/N) ', 's');
- if strcmpi(response,'y')
- warning('Changed end data point from %d to %d',requestedEndDataPoint,sum(NSx.MetaTags.DataPoints));
- requestedEndDataPoint = sum(NSx.MetaTags.DataPoints);
- else
- error('Invalid last requested data point');
- end
- end
-
- %% Identify data segments containing requested packets
- requestedSegments = nan(1,2); % first and last requested segments
- startTimeStampShift = 0;
-
- % user requested specific data points: look for start/end segments
- % containing these data points
- if flagModifiedTime
- dataPointOfInterest = requestedStartDataPoint;
- segmentStartDataPoint = zeros(1,length(NSx.MetaTags.DataPoints));
- segmentDataPoints = zeros(1,length(NSx.MetaTags.DataPoints));
- for currSegment = 1:length(NSx.MetaTags.DataPoints)
- if dataPointOfInterest <= sum(NSx.MetaTags.DataPoints(1:currSegment))
- if all(isnan(requestedSegments))
- if currSegment == 1
- segmentStartDataPoint(currSegment) = dataPointOfInterest;
- else
- segmentStartDataPoint(currSegment) = dataPointOfInterest - sum(NSx.MetaTags.DataPoints(1:currSegment-1));
- end
- startTimeStampShift = (segmentStartDataPoint(currSegment)-1) * NSx.MetaTags.TimeRes / NSx.MetaTags.SamplingFreq;
- if requestedEndDataPoint <= sum(NSx.MetaTags.DataPoints(1:currSegment))
- segmentDataPoints(currSegment) = requestedEndDataPoint - sum(NSx.MetaTags.DataPoints(1:currSegment-1)) - segmentStartDataPoint(currSegment) + 1;
- requestedSegments = [currSegment currSegment];
- break;
- end
- segmentDataPoints(currSegment) = sum(NSx.MetaTags.DataPoints(1:currSegment)) - dataPointOfInterest + 1;
- dataPointOfInterest = requestedEndDataPoint;
- else
- segmentStartDataPoint(currSegment) = 1;
- if currSegment == 1
- segmentDataPoints(currSegment) = dataPointOfInterest;
- else
- segmentDataPoints(currSegment) = dataPointOfInterest - sum(NSx.MetaTags.DataPoints(1:currSegment-1));
- requestedSegments(2) = currSegment;
- end
- break;
- end
- requestedSegments(1) = currSegment;
- else
- if all(isnan(requestedSegments))
- segmentStartDataPoint(currSegment) = NSx.MetaTags.DataPoints(currSegment);
- segmentDataPoints(currSegment) = 0;
- elseif isnan(requestedSegments(2))
- segmentStartDataPoint(currSegment) = 1;
- segmentDataPoints(currSegment) = NSx.MetaTags.DataPoints(currSegment);
- else
- segmentStartDataPoint(currSegment) = 1;
- segmentDataPoints(currSegment) = 0;
- end
- end
- end
- else
- requestedSegments = [1 length(f.BOData)];
- segmentStartDataPoint = ones(1,length(f.BOData));
- segmentDataPoints = NSx.MetaTags.DataPoints;
- end
-
- % calculate total number of data points requested
- numDataPointsRequested = sum(floor(segmentDataPoints/requestedSkipFactor));
-
- %% Read data
- file.MetaTags.DataPoints = NSx.MetaTags.DataPoints;
- file.MetaTags.DataDurationSec = NSx.MetaTags.DataDurationSec;
- file.MetaTags.Timestamp = NSx.MetaTags.Timestamp;
- if flagReadData
-
- % loop over requested data segments
- NSx.Data = cell(1,diff(requestedSegments)+1);
- for currSegment = requestedSegments(1):requestedSegments(2)
- outputSegment = currSegment - requestedSegments(1) + 1;
-
- % seek to start of data
- fseek(FID, f.BOData(currSegment), 'bof');
-
- % seek to first requested packet in the current segment
- if flagOneSamplePerPacket
- fseek(FID, (segmentStartDataPoint(currSegment) - 1) * packetSize, 'cof');
- else
- fseek(FID, (segmentStartDataPoint(currSegment) - 1) * 2 * channelCount, 'cof');
- end
-
- % seek to first requested channel in the current packet
- fseek(FID, (find(NSx.MetaTags.ChannelID == min(requestedChannels))-1) * 2, 'cof');
-
- % set up parameters for reading data
- precisionString = sprintf('%d*int16=>%s',numChansToRead,requestedPrecisionType);
- outputDimensions = [numChansToRead floor(segmentDataPoints(currSegment)/requestedSkipFactor)];
- if flagOneSamplePerPacket
- bytesToSkipNormal = packetSize - 2*numChansToRead; % standard (i.e., skip factor==1)
- bytesSkipFactor = packetSize*(requestedSkipFactor - 1); % additional to skip (skip factor > 1)
- else
- bytesToSkipNormal = 2*(channelCount - numChansToRead);
- bytesSkipFactor = 2*channelCount*(requestedSkipFactor-1);
- end
- bytesToSkip = bytesToSkipNormal + bytesSkipFactor; % total
-
- % read data
- NSx.Data{outputSegment} = fread(FID, outputDimensions, precisionString, bytesToSkip);
-
- % define user tags: info specific to data being read
- NSx.MetaTags.Timestamp = NSx.MetaTags.Timestamp(requestedSegments(1):requestedSegments(2));
- NSx.MetaTags.Timestamp(1) = NSx.MetaTags.Timestamp(1) + startTimeStampShift;
- NSx.MetaTags.DataPoints = cellfun(@(x) size(x,2), NSx.Data, 'UniformOutput', true);
- NSx.MetaTags.DataDurationSec = NSx.MetaTags.DataDurationSec(requestedSegments(1):requestedSegments(2));
- NSx.MetaTags.DataDurationSec(1) = NSx.MetaTags.DataDurationSec(1) - (segmentStartDataPoint(requestedSegments(1))-1)/NSx.MetaTags.SamplingFreq;
- NSx.MetaTags.DataDurationSec(end) = NSx.MetaTags.DataPoints(end)/NSx.MetaTags.SamplingFreq;
- end
-
- % verify amount of data read from disk
- numDataPointsRead = sum(cellfun(@(x)size(x,2),NSx.Data));
- if numDataPointsRead~=numDataPointsRequested
- warning('Expected to read %d data points, but output has %d data points.',numDataPointsRequested,numDataPointsRead);
- end
- end
- catch ME
- fclose(FID);
- rethrow(ME);
- end
- fclose(FID);
- %% Bug fix
- % Fix a bug in 6.03 where data packets with 0 length may be added
- if any(NSx.MetaTags.DataPoints == 0) && flagReadData
- segmentsThatAreZero = find(NSx.MetaTags.DataPoints == 0);
- NSx.MetaTags.DataPoints(segmentsThatAreZero) = [];
- NSx.MetaTags.DataDurationSec(segmentsThatAreZero) = [];
- NSx.MetaTags.Timestamp(segmentsThatAreZero) = [];
- NSx.Data(segmentsThatAreZero) = [];
- end
- %% Remove extra channels that were read, but weren't supposed to be read
- % Commenting this section out since I think that previous code should
- % capture this - DK 20230303
- % channelThatWereRead = min(userRequestedChanRow):max(userRequestedChanRow);
- % if ~isempty(setdiff(channelThatWereRead,userRequestedChanRow))
- % deleteChannels = setdiff(channelThatWereRead, userRequestedChanRow) - min(userRequestedChanRow) + 1;
- % if NSx.RawData.PausedFile
- % for segIDX = 1:size(NSx.Data,2)
- % NSx.Data{segIDX}(deleteChannels,:) = [];
- % end
- % else
- % NSx.Data(deleteChannels,:) = [];
- % end
- % end
- %% Adjust the ChannelID variable to match the read electrodes
- channelIDToDelete = setdiff(1:channelCount, requestedChanRow);
- NSx.MetaTags.ChannelID(channelIDToDelete) = [];
- %% Zero-pad data if requested
- if flagZeroPad
-
- % only operate on first data segment
- currSegment = 1;
-
- % compute how many zeros and total number of values to add
- numZerosToAdd = floor(NSx.MetaTags.Timestamp(currSegment) / requestedSkipFactor);
- if flagMultiNSP
- numZerosToAdd = numZerosToAdd + syncShift;
- end
- numValuesToAdd = NSx.MetaTags.ChannelCount * numZerosToAdd;
-
- % sanity check
- if strcmpi(NSx.MetaTags.FileTypeID, 'BRSMPGRP')
-
- % calculate how many values and how many bytes
- numValuesOfData = sum(cellfun(@numel,NSx.Data));
- if strcmpi(requestedPrecisionType,'int16')
- numBytesToAdd = numValuesToAdd*2;
- numBytesOfData = numValuesOfData*2;
- elseif strcmpi(requestedPrecisionType,'double')
- numBytesToAdd = numValuesToAdd*8;
- numBytesOfData = numValuesOfData*8;
- end
-
- % check whether to show the warning
- flagShowZeroPadWarning = 1;
- if flagFoundSettingsManager
- flagShowZeroPadWarning = NPMKSettings.ShowZeroPadWarning;
- end
-
- % generate warning and ask to continue
- if NSx.MetaTags.Timestamp(1) > 30000 && flagShowZeroPadWarning
- warning('Zero padding would add %d bytes to data which already require %d bytes in memory', numBytesToAdd, numBytesOfData);
- response = input('Do you wish to continue? (y/N) ', 's');
- if ~strcmpi(response, 'y')
- warning('Turned off zero padding by user request');
- flagZeroPad = 0;
- end
- end
-
- % check on continuing to show this warning
- if flagFoundSettingsManager
- response = input('Do you want NPMK to continue to ask you about this every time? (Y/n) ', 's');
- if ~strcmpi(response, 'n')
- NPMKSettings.ShowZeroPadWarning = 1;
- else
- NPMKSettings.ShowZeroPadWarning = 0;
- end
- settingsManager(NPMKSettings);
- end
- end
-
- % perform zero padding
- % (flagZeroPad may be set to false above, so need to re-evaluate)
- if requestedStartDataPoint == 1 && flagZeroPad
-
- % only for the first data segment
- if flagReadData
- NSx.Data{currSegment} = [zeros(NSx.MetaTags.ChannelCount, numZerosToAdd, requestedPrecisionType) NSx.Data{currSegment}];
- end
-
- % update metadata
- NSx.MetaTags.DataPoints(currSegment) = size(NSx.Data{currSegment},2);
- NSx.MetaTags.Timestamp(currSegment) = 0;
- NSx.MetaTags.DataDurationSec(currSegment) = size(NSx.Data{currSegment},2)/NSx.MetaTags.SamplingFreq;
- end
- end
- %% Adjust for the data's unit.
- if flagConvertToUv
- NSx.Data = cellfun(@(x) bsxfun(@rdivide, x, 1./(double([NSx.ElectrodesInfo.MaxAnalogValue])./double([NSx.ElectrodesInfo.MaxDigiValue]))'),NSx.Data ,'UniformOutput',false);
- else
- flagShowuVWarning = 1;
- if flagFoundSettingsManager
- flagShowuVWarning = NPMKSettings.ShowuVWarning;
- end
- if flagShowuVWarning
- warning('Note that data are in units of 1/4 ??V; see ''uv'' argument.');
- end
- if flagShowuVWarning && flagFoundSettingsManager
- response = input('Do you want NPMK to continue to warn you about this every time? (Y/n) ', 's');
- if ~strcmpi(response, 'n')
- NPMKSettings.ShowuVWarning = 1;
- else
- NPMKSettings.ShowuVWarning = 0;
- end
- settingsManager(NPMKSettings);
- end
- end
- %% Add implementation of samplealign for cases where it is needed
- if flagOneSamplePerPacket && flagAlign
- for ii = 1:length(NSx.Data)
- fileDataLength = file.MetaTags.DataPoints(ii);
- fileDuration = file.MetaTags.DataDurationTimeRes(ii);
- % Calculate the ratio between time gaps and expected time gap
- % based on the sampling rate of the recording. A recording
- % where the claimed sampling rate and true sampling rate based
- % off PTP time are identical will have a ratio of 1;
- samplingRates = fileDuration/fileDataLength/NSx.MetaTags.TimeRes*NSx.MetaTags.SamplingFreq;
- % Calculate the number of samples that should be added or
- % removed
- addedSamples = round((samplingRates-1)*fileDataLength);
- % Establish where the points should be added or removed
- gapIndex = round(fileDataLength/(abs(addedSamples)+1));
- % calculate the portion of samples added/subtracted to the
- % requested data, which may be shorter than the full file
- % use floor because we need addedsamples+1 sections to avoid
- % adding/subtracting samples at the beginning or end of the data.
- addedSamples = floor(addedSamples * NSx.MetaTags.DataPoints(ii)/fileDataLength);
- if addedSamples == 0
- continue;
- end
-
- % split into cell arrays
- dim1Size = size(NSx.Data{ii},1);
- if gapIndex >= size(NSx.Data{ii},2)
- if abs(addedSamples)>1
- warning('Expected to add or remove only one sample')
- end
- dim2Size = [round(size(NSx.Data{ii},2)/2) size(NSx.Data{ii},2)-round(size(NSx.Data{ii},2)/2)];
- else
- dim2Size = [repmat(gapIndex,1,abs(addedSamples)) size(NSx.Data{ii},2) - gapIndex*abs(addedSamples)];
- end
- NSx.Data{ii} = mat2cell(NSx.Data{ii},dim1Size,dim2Size);
- % add or subtract
- if abs(addedSamples)==1
- sampleString = sprintf('%d sample',abs(addedSamples));
- whereString = 'at midpoint';
- else
- sampleString = sprintf('%d samples',abs(addedSamples));
- whereString = 'evenly spaced';
- end
- if length(NSx.Data)==1
- segmentString = 'the data';
- else
- segmentString = sprintf('data segment %d/%d',ii,length(NSx.Data));
- end
- if addedSamples>0
- NSx.Data{ii}(1:end-1) = cellfun(@(x) [x x(:,end)], NSx.Data{ii}(1:end-1), 'UniformOutput',false);
- warning('Added %s to %s (%s) for clock drift alignment',sampleString,segmentString,whereString)
- elseif addedSamples<0
- NSx.Data{ii}(1:end-1) = cellfun(@(x) x(:,1:end-1), NSx.Data{ii}(1:end-1), 'UniformOutput',false);
- warning('Removed %s from %s (%s) for clock drift alignment',sampleString,segmentString,whereString)
- end
- % combine to form the full data again
- NSx.Data{ii} = cat(2,NSx.Data{ii}{:});
- % recompute some metadata
- NSx.MetaTags.DataPoints(ii) = size(NSx.Data{ii},2);
- NSx.MetaTags.DataDurationSec(ii) = size(NSx.Data{ii},2)/NSx.MetaTags.SamplingFreq;
- end
- end
- % Convert data points in sample to seconds
- NSx.MetaTags.DataPointsSec = double(NSx.MetaTags.DataPoints)/NSx.MetaTags.SamplingFreq;
- % Remove the cell if there is only one recorded segment present
- if iscell(NSx.Data) && length(NSx.Data)==1
- NSx.Data = NSx.Data{1};
- end
- % Display a report of basic file information and the Basic Header.
- if flagReport
- disp( '*** FILE INFO **************************');
- disp(['File Path = ' NSx.MetaTags.FilePath]);
- disp(['File Name = ' NSx.MetaTags.Filename]);
- disp(['File Extension = ' NSx.MetaTags.FileExt]);
- disp(['File Version = ' NSx.MetaTags.FileSpec]);
- disp(['Duration (seconds) = ' num2str(NSx.MetaTags.DataDurationSec)]);
- disp(['Total Datapoints = ' num2str(NSx.MetaTags.DataPoints)]);
- disp(' ');
- disp( '*** BASIC HEADER ***********************');
- disp(['File Type ID = ' NSx.MetaTags.FileTypeID]);
- disp(['Sample Frequency = ' num2str(double(NSx.MetaTags.SamplingFreq))]);
- disp(['Electrodes Read = ' num2str(double(NSx.MetaTags.ChannelCount))]);
- disp(['Datapoints Read = ' num2str(size(NSx.Data,2))]);
- end
- % Create output variable in user workspace even if no output argument
- outputName = ['NS' requestedFileExtension(4)];
- if (nargout == 0)
- assignin('caller', outputName, NSx);
- else
- varargout{1} = NSx;
- end
- % Print the load time
- if flagReport
- disp(['The load time for ' outputName ' file was ' num2str(toc, '%0.1f') ' seconds.']);
- end
- end
|