Scheduled service maintenance on November 22


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

We apologize for any inconvenience.

openNSx.m 64 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338
  1. function varargout = openNSx(varargin)
  2. % openNSx
  3. %
  4. % Opens and reads an NSx file then returns all file information in a NSx
  5. % structure. Works with File Spec 2.1, 2.2, 2.3, and 3.0.
  6. %
  7. % OUTPUT = openNSx('ver')
  8. % OUTPUT = openNSx(FNAME, 'read', 'report', 'e:xx:xx', 'c:xx:xx', 't:xx:xx', MODE, 'precision', 'skipfactor', 'nozeropad').
  9. %
  10. % All input arguments are optional. Input arguments can be in any order.
  11. %
  12. % 'ver': Immediately return version information for openNSx
  13. % without processing any files.
  14. %
  15. % FNAME: Path of the file to be opened. If FNAME is omitted, a
  16. % file selection dialog box will appear.
  17. %
  18. % 'noread': Do not read the data contained in the file. Return only
  19. % header information. ('read' input still accepted for
  20. % legacy purposes, but is redundant with default behavior.)
  21. % DEFAULT: 'read'
  22. %
  23. % 'report': Show a summary report if user passes this argument.
  24. % DEFAULT: No report.
  25. %
  26. % 'electrodes',XX:YY
  27. % 'e:XX:YY': User can specify which electrodes need to be read. The
  28. % number of electrodes can be greater than or equal to 1
  29. % and less than or equal to 256. The electrodes can be
  30. % selected either by specifying a range (e.g. 20:45) or by
  31. % indicating individual electrodes (e.g. 3,6,7,90) or both.
  32. % Note that, when individual channels are to be read, all
  33. % channels in between will also be read. The prorgam will
  34. % then remove the unwanted channels. This may result in a
  35. % large memory footprint. If memory issues arrise please
  36. % consider placing openNSx in a for loop and reading
  37. % individual channels.
  38. % This field needs to be preceded by the prefix 'e:'. See
  39. % example for more details. If this option is selected the
  40. % user will be promped for a CMP mapfile (see: KTUEAMapFile)
  41. % provided by Blackrock Microsystems. This feature requires
  42. % KTUEAMapFile to be present in path.
  43. % DEFAULT: will read all existing electrodes.
  44. %
  45. % 'channels',XX:YY
  46. % 'c:XX:YY': User can specify which channels need to be read. The
  47. % number of channels can be greater than or equal to 1
  48. % and less than or equal to 272. The channels can be
  49. % selected either by specifying a range (e.g. 20:45) or by
  50. % indicating individual channels (e.g. 3,6,7,90) or both.
  51. % Note that, when individual channels are to be read, all
  52. % channels in between will also be read. The prorgam will
  53. % then remove the unwanted channels. This may result in a
  54. % large memory footprint. If memory issues arrise please
  55. % consider placing openNSx in a for loop and reading
  56. % individual channels.
  57. % This field needs to be preceded by the prefix 'c:'. See
  58. % example for more details.
  59. % DEFAULT: will read all existing analog channels.
  60. %
  61. % 'duration',XX:YY
  62. % 't:XX:YY': User can specify the beginning and end of the data
  63. % segment to be read. If the start time is greater than the
  64. % length of data the program will exit with an errorNS
  65. % message. If the end time is greater than the length of
  66. % data the end packet will be selected for end of data. The
  67. % user can specify the start and end values by comma
  68. % (e.g. [20,50]) or by a colon (e.g. [20:50]). To use this
  69. % argument the user must specify the [electrodes] or the
  70. % interval will be used for [electrodes] automatically.
  71. % This field needs to be preceded by the prefix 't:'.
  72. % Note that if 'mode' is 'sample' the start duration cannot
  73. % be less than 1. The duration is inclusive.
  74. % See example for more details.
  75. % DEFAULT: will read the entire file.
  76. %
  77. % MODE: Specify the units of duration values specified with
  78. % 'duration' (or 't:XX:YY') input. Valid values of MODE are
  79. % 'sec', 'secs', 'second', 'seconds'
  80. % 'min', 'mins', 'minute', 'minutes'
  81. % 'hour', 'hours'
  82. % 'sample', 'samples'
  83. % DEFAULT: 'sample'
  84. %
  85. % 'uV': Read the recording waveforms in unit of uV instead of raw
  86. % values. Note that this conversion requires 'double'
  87. % precision; if this argument is provided and precision has
  88. % been set to 'int16' or 'short', it will be updated to
  89. % 'double' with a warning.
  90. %
  91. % 'precision',P
  92. % 'p:P':
  93. % P Specify the precision P for data read from the NSx file.
  94. % Valid options are 'double' (64-bit floating point) or
  95. % 'int16' (or, equivalently, 'short'; 16-bit signed
  96. % integer). Data are stored in the file as int16 values.
  97. % Still, while 'int16' uses less memory, be mindful of
  98. % the limitations of integer data types
  99. % (https://www.mathworks.com/help/matlab/numeric-types.html).
  100. % Note that if the argument 'uV' is provided (conversion
  101. % from raw values to uV units), the precision will be
  102. % automatically set to 'double' if it is not already.
  103. % DEFAULT: 'int16'.
  104. %
  105. % 'skipfactor',S
  106. % 's:S': Decimate data read from disk, e.g., to quickly preview
  107. % data. The integer S will determine how many samples to
  108. % skip. For example, if S is 2 then every other sample is
  109. % read. This action is decimation only: no anti-aliasing
  110. % filter is applied.
  111. % DEFAULT: 1 (every sample read)
  112. %
  113. % 'zeropad': Prepend the data with zeros to compensate for non-zero
  114. % start time. Note that timestamps in newer data files may
  115. % be in the 10^18 range. Prepending this many zeros is not
  116. % advisable for normal computer systems, nor does it carry
  117. % the same logic as older data files where time zero was
  118. % relevant to the recording.
  119. % DEFAULT: No zero padding.
  120. %
  121. % 'noalign': Do not apply bug fix for clock drift in Central release
  122. % 7.6.0. In executing the bug fix (if this argument is not
  123. % provided), samples may be added or removed to restore
  124. % clock alignment. Changes are made at evenly spaced points
  125. % throughout the file. Samples are added by duplicating the
  126. % prior sample; they are removed by deleting a sample.
  127. % DEFAULT: Alignment occurs with warnings.
  128. %
  129. % 'max_tick_multiple', M:
  130. % Newer data files use PTP (precision time protocol) and
  131. % timestamp each sample of data, instead of only the first
  132. % sample in a frame of contiguous samples. To detect pauses
  133. % in a PTP recording, openNSx processes the file in frames:
  134. % it reads the timestamp of the first and last packets in
  135. % each frame (see 'packets_per_frame') and checks whether
  136. % the elapsed time is greater than it should be, assuming
  137. % contiguously recorded packets at the expected sampling
  138. % rate. The threshold M for the difference of elapsed time
  139. % is set as a multiple of the expected sampling interval.
  140. % If M is too small, openNSx will detect spurious pauses.
  141. % If it is too high, pauses will be missed. Note that due
  142. % to jitter in sample timing, this value should be set in
  143. % coordination with the number of packets in each frame
  144. % (see 'packets_per_frame') to ensure the sum of jittered
  145. % sampling intervals does not exceed the detection
  146. % threshold.
  147. % DEFAULT: 2 (equivalent to missing one sample)
  148. %
  149. % 'packets_per_frame', P:
  150. % Newer data files use PTP (precision time protocol) and
  151. % timestamp each sample of data, instead of only the first
  152. % sample in a frame of contiguous samples. To detect pauses
  153. % in a PTP recording, openNSx processes the file in frames,
  154. % each containing P packets: it reads the timestamp of the
  155. % first and last packets in each frame and checks whether
  156. % the elapsed time is greater than it should be, assuming
  157. % contiguously recorded packets at the expected sampling
  158. % rate (see 'max_tick_multiple'). The number of frames F is
  159. % given by CEIL(TOTAL_PACKETS/P), where TOTAL_PACKETS is
  160. % the number of packets in the file. Note that this method
  161. % reads only F+1 timestamps from disk if there are no
  162. % pauses detected. For each frame containing one or more
  163. % detected pauses, all P timestamps in the frame are read
  164. % from disk to identify the specific samples between which
  165. % the pauses occur. Thus, P can be increased to lower F,
  166. % but it should not be so large that a vector of P doubles
  167. % would not fit in memory. Note also that because of jitter
  168. % in sample timing, setting this value too large may lead
  169. % to spurious detections (i.e., the sum of jitter could be
  170. % greater than the detection threshold).
  171. % DEFAULT: 100,000 packets per frame.
  172. %
  173. % OUTPUT: The NSx structure.
  174. %
  175. % Example 1:
  176. % openNSx('report','read','c:\data\sample.ns5', 'e:15:30', 't:3:10','min', 'p:short', 's:5');
  177. %
  178. % or equivalently
  179. % openNSx('report','read','c:\data\sample.ns5', 'electrodes', 15:30, 'duration', 3:10, 'min', 'precision', 'short', 'skipfactor', 5);
  180. %
  181. % In the example above, the file c:\data\sample.ns5 will be used. A
  182. % report of the file contents will be shown. The data will be read from
  183. % electrodes 15 through 50 in the 3-10 minute time interval. A decimated
  184. % version of the datafile will be read, where only every 5th sample is
  185. % read.
  186. %
  187. % Example 2:
  188. % openNSx('read','c:15:30');
  189. %
  190. % In the example above, the user will be prompted for the file. The file
  191. % will be read using 'int16' precision as default. All time points of
  192. % channels 15 through 30 will be read.
  193. %
  194. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  195. % Kian Torab
  196. % support@blackrockmicro.com
  197. % Blackrock Microsystems
  198. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  199. % Version History
  200. %
  201. % 5.1.8.2:
  202. % - Fixed the way DayOfWeek is read in MetaTags.
  203. %
  204. % 5.1.9.0:
  205. % - Fixed a bug where with skipFactor being read correctly as a num.
  206. %
  207. % 5.1.10.0:
  208. % - Updated feature to save data headers for a paused file. It is a
  209. % dependent feature for seperatePausedNSx.
  210. %
  211. % 5.1.11.0:
  212. % - Fixed an issue where 1 sample would not be read when using the
  213. % t:xx:xx argument and 'sample'.
  214. % - Fixed an error when 'duration' was used to load specific data length.
  215. %
  216. % 5.1.12.0:
  217. % - Better error handling if a file is not provided and an output
  218. % variable was requested by the calling function.
  219. %
  220. % 5.2.0.0: June 12, 2014
  221. % - It removes the extra ElectrodesInfo entried for channels not
  222. % read if 'c:XX:XX' or 'e:XX:XX' are used.
  223. % - It reports variable ChannelCount under MetaTags correctly.
  224. % - It automatically compensate for any NSx file with non-0 beginnings
  225. % and adds 0s for to the begining of the file to properly align the
  226. % timestamps.
  227. %
  228. % 5.2.1.0: June 12, 2014
  229. % - Fixed a small bug where extra 0s were tacked on to the beginning of
  230. % paused file segments.
  231. % - Updated the version.
  232. %
  233. % 5.2.2.0: June 13, 2014
  234. % - Fixed bug for when 'noread' was used on a paused file.
  235. %
  236. % 6.0.1.0: December 2, 2014
  237. % - Fixed a bug related to file format 2.1 not being read correctly.
  238. % - Corrected the way Filename, FileExt, and FilePath was being
  239. % processed.
  240. % - File dialogue now only shows NSx files on non Windows-based
  241. % computers.
  242. % - Added 512 synchronized reading capability.
  243. % - Now on non-Windows computers only NSx files are shown in the file
  244. % dialogue.
  245. % - Fixed the date in NSx.MetaTags.DateTime.
  246. %
  247. % 6.1.0.0: March, 15 2015
  248. % - Added the ability to read from networked drives in Windows.
  249. % - Fixed the DateTime variable in MetaTags.
  250. % - Fixed the date in NSx.MetaTags.DateTime (again).
  251. % - Fixed a bug related to starting and stopping packets when a specific
  252. % time is passed to the function.
  253. % - Fixed a bug where 512+ ch rules were being applied to smaller channel
  254. % count configuration.
  255. %
  256. % 6.1.1.0: June 15, 2015
  257. % - Bug fixes related to timestamps when the recording didn't start at
  258. % proctime 0.
  259. %
  260. % 6.2.0.0: October 1, 2015
  261. % - Fixed a bug related to reading the correct length of time when a skip
  262. % factor was used.
  263. % - Bug fixes related to information that separatePausedNSx depends on.
  264. % - Added 'uV' as an option to read the data in the unit of uV.
  265. %
  266. % 6.2.1.0: April 16, 2016
  267. % - Fixed a bug related to converting the unit to uV in case of having
  268. % multiple data segments (paused file).
  269. %
  270. % 6.2.2.0: July 6, 2016
  271. % - Fixed another bug related to converting the unit to uV.
  272. %
  273. % 6.3.0.0: August 3, 2016
  274. % - Added support for loading a segment of paused files.
  275. %
  276. % 6.3.1.0: August 31, 2016
  277. % - Fixed a bug when reading a non-o start across a paused segment.
  278. %
  279. % 6.4.0.0: December 1, 2016
  280. % - Fixed a serious bug related to loading paused files.
  281. % - Fixed a bug where an empty data segment resulted in a cell structure.
  282. %
  283. % 6.4.1.0: June 15, 2017
  284. % - It is no longer necessary to provide the full path for loading a
  285. % file.
  286. %
  287. % 6.4.2.0: September 1, 2017
  288. % - Fixed a bug related to reading data from sample that is not 1 and
  289. % timestamp that used to get reset to 0.
  290. %
  291. % 6.4.3.0: September 13, 2017
  292. % - Removed a redundant block of code that was accidentally placed in the
  293. % script twice.
  294. % - Checks to see if there's a newer version of NPMK is available.
  295. %
  296. % 6.4.3.1: January 24, 2020
  297. % - Changed file opening access from r+ to r.
  298. %
  299. % 7.0.0.0: January 27, 2020
  300. % - Added support for 64-bit timestamps in NEV and NSx.
  301. %
  302. % 7.1.0.0: April 14, 2020
  303. % - Added option to load the data without zero padding to compensate for
  304. % a non-zero start time. (David Kluger)
  305. % - Bug fixes and documentation updates (David Kluger)
  306. %
  307. % 7.1.1.0: June 11, 2020
  308. % - Fixed a bug related to fread and MATLAB 2020a.
  309. %
  310. % 7.3.0.0: September 11, 2020
  311. % - Fixed a bug related to fread and MATLAB 2020a.
  312. % - Gives a warning about FileSpec 3.0 and gives the user options for how
  313. % to proceed.
  314. % - Added a warning about the data unit and that by default it in the
  315. % unit of 250 nV or 1/4 ???V.
  316. % - If the units are in "raw", ths correct information is now written to
  317. % the electrodes header: 250 nV (raw).
  318. %
  319. % 7.3.1.0: October 2, 2020
  320. % - If the units are in ???V (openNSx('uv'), ths correct information is now
  321. % written to the electrodes header: 1000 nV (raw).
  322. %
  323. % 7.3.2.0: October 23, 2020
  324. % - Fixed a typo.
  325. %
  326. % 7.4.0.0: October 29, 2020
  327. % - Undid changes made to AnalogUnit and instead implemented
  328. % NSx.ElectrodesInfo.Resolution to show what the resolution of the data
  329. % is. By default, the resolution is set to 0.250 ???V. If used with
  330. % parameter 'uv', the resolution will be 1 ???V. To always convert the
  331. % data to ???V, divide NSx.Data(CHANNEL,:) by
  332. % NSx.ElectrodesInfo(CHANNEL).Resolution.
  333. %
  334. % 7.4.1.0: April 20, 2021
  335. % - Fixed a bug related to file opening.
  336. %
  337. % 7.4.2.0: May 5, 2021
  338. % - Fixed a bug related to NeuralSG file format (File Spec 2.1).
  339. %
  340. % 7.4.3.0: July 16, 2021
  341. % - Fixed a minor bug for when the data header is not written properly
  342. % and the data needs to be used to calculate the data length.
  343. %
  344. % 7.4.4.0: April 1, 2023
  345. % - Accounts for many segments in files for clock drift correction
  346. % - Changed 'zeropad' default behavior to be 'no'
  347. %
  348. % 7.4.5.0: October 5, 2023
  349. % - Bank numbers on new files are not alpha which causes problems on save
  350. %
  351. % 7.4.6.0: December 6, 2023
  352. % - Better support for reading files recorded from Gemini systems
  353. % - Improved speed and memory usage for Gemini system recordings
  354. % - Change messages about errors to actual errors
  355. % - NPMK SettingsManager, getFile, and NPMKverChecker made optional
  356. % - Force 'double' precision (with warning) if conversion to uV requested
  357. % - Repair skipfactor implementation
  358. % - Clean up documentation
  359. % - Clean up code
  360. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  361. %% Define the NSx data structure and sub-branches.
  362. NSx = struct('MetaTags',[],'Data',[], 'RawData', []);
  363. NSx.MetaTags = struct('FileTypeID',[],'SamplingLabel',[],'ChannelCount',[],'SamplingFreq',[], 'TimeRes', [], ...
  364. 'ChannelID',[],'DateTime',[],'DateTimeRaw',[], 'Comment', [], 'FileSpec', [], ...
  365. 'Timestamp', [], 'DataPoints', [], 'DataDurationSec', [], 'openNSxver', [], 'Filename', [], 'FilePath', [], ...
  366. 'FileExt', []);
  367. NSx.MetaTags.openNSxver = '7.4.6.0';
  368. %% Check for the latest version of NPMK
  369. if exist('NPMKverChecker','file')==2
  370. NPMKverChecker
  371. end
  372. %% Define constants and defaults
  373. extHeaderEntrySize = 66;
  374. NSx.RawData.PausedFile = 0;
  375. syncShift = 0;
  376. flagFoundSettingsManager = exist('settingsManager','file')==2;
  377. flagFoundGetFile = exist('getFile','file')==2;
  378. NPMKSettings = [];
  379. if flagFoundSettingsManager
  380. NPMKSettings = settingsManager;
  381. end
  382. % Default values
  383. flagReport = 0;
  384. flagReadData = 1;
  385. flagElectrodeReading = 0;
  386. flagModifiedTime = 0;
  387. flagMultiNSP = 1;
  388. flagZeroPad = 0;
  389. flagAlign = 1;
  390. flagConvertToUv = 0;
  391. flagOneSamplePerPacket = 0;
  392. requestedTimeScale = 'sample';
  393. requestedPrecisionType = 'int16';
  394. requestedSkipFactor = 1;
  395. requestedPacketsPerFrame = 100000;
  396. requestedMaxTickMultiple = 2;
  397. requestedChanRow = [];
  398. requestedFileName = '';
  399. %% Process input arguments
  400. next = '';
  401. for i=1:length(varargin)
  402. inputArgument = varargin{i};
  403. if ischar(inputArgument) && strcmpi(inputArgument, 'ver')
  404. varargout{1} = NSx.MetaTags.openNSxver;
  405. return;
  406. elseif ischar(inputArgument) && strcmpi(inputArgument, 'channels')
  407. next = 'channels';
  408. elseif ischar(inputArgument) && strcmpi(inputArgument, 'skipfactor')
  409. next = 'skipfactor';
  410. elseif ischar(inputArgument) && strcmpi(inputArgument, 'electrodes')
  411. next = 'electrodes';
  412. elseif ischar(inputArgument) && strcmpi(inputArgument, 'duration')
  413. next = 'duration';
  414. elseif ischar(inputArgument) && strcmpi(inputArgument, 'precision')
  415. next = 'precision';
  416. elseif ischar(inputArgument) && strcmpi(inputArgument, 'packets_per_frame')
  417. next = 'packets_per_frame';
  418. elseif ischar(inputArgument) && strcmpi(inputArgument, 'max_tick_multiple')
  419. next = 'max_tick_multiple';
  420. elseif ischar(inputArgument) && strcmpi(inputArgument, 'report')
  421. flagReport = 1;
  422. elseif ischar(inputArgument) && strcmpi(inputArgument, 'noread')
  423. flagReadData = 0;
  424. elseif ischar(inputArgument) && strcmpi(inputArgument, 'nomultinsp')
  425. flagMultiNSP = 0;
  426. elseif ischar(inputArgument) && strcmpi(inputArgument, 'zeropad')
  427. flagZeroPad = 1;
  428. elseif ischar(inputArgument) && strcmpi(inputArgument, 'uV')
  429. flagConvertToUv = 1;
  430. elseif ischar(inputArgument) && strcmpi(inputArgument, 'noalign')
  431. flagAlign = false;
  432. elseif ischar(inputArgument) && strcmpi(inputArgument, 'read')
  433. flagReadData = 1;
  434. elseif ischar(inputArgument) && ((strncmp(inputArgument, 't:', 2) && inputArgument(3) ~= '\' && inputArgument(3) ~= '/') || strcmpi(next, 'duration'))
  435. if strncmp(inputArgument, 't:', 2)
  436. inputArgument(1:2) = [];
  437. inputArgument = str2num(inputArgument); %#ok<ST2NM>
  438. elseif ischar(inputArgument)
  439. inputArgument = str2num(inputArgument); %#ok<ST2NM>
  440. end
  441. flagModifiedTime = 1;
  442. requestedStartValue = inputArgument(1);
  443. requestedEndValue = inputArgument(end);
  444. next = '';
  445. elseif ischar(inputArgument) && ((strncmp(inputArgument, 'e:', 2) && inputArgument(3) ~= '\' && inputArgument(3) ~= '/') || strcmpi(next, 'electrodes'))
  446. assert(exist('KTUEAMapFile','file')==2,'To read data by ''electrodes'' the function KTUEAMapFile needs to be in path.');
  447. mapFile = KTUEAMapFile;
  448. requestedElectrodes = str2num(inputArgument(3:end)); %#ok<ST2NM>
  449. if min(requestedElectrodes)<1 || max(requestedElectrodes)>128
  450. assert(min(requestedElectrodes)>=1 && max(requestedElectrodes)<=128, 'The electrode number cannot be less than 1 or greater than 128.');
  451. end
  452. requestedChannels = nan(1,length(requestedElectrodes));
  453. for chanIDX = 1:length(requestedElectrodes)
  454. requestedChannels(chanIDX) = mapFile.Electrode2Channel(requestedElectrodes(chanIDX));
  455. end
  456. flagElectrodeReading = 1;
  457. next = '';
  458. elseif ischar(inputArgument) && ((strncmp(inputArgument, 's:', 2) && inputArgument(3) ~= '\' && inputArgument(3) ~= '/') || strcmpi(next, 'skipFactor'))
  459. if strncmp(inputArgument, 's:', 2)
  460. requestedSkipFactor = str2num(inputArgument(3:end)); %#ok<ST2NM>
  461. elseif ischar(inputArgument)
  462. requestedSkipFactor = str2num(inputArgument); %#ok<ST2NM>
  463. else
  464. requestedSkipFactor = inputArgument;
  465. end
  466. next = '';
  467. elseif ischar(inputArgument) && ((strncmp(inputArgument, 'c:', 2) && inputArgument(3) ~= '\' && inputArgument(3) ~= '/') || strcmpi(next, 'channels'))
  468. if strncmp(inputArgument, 'c:', 2)
  469. requestedChanRow = str2num(inputArgument(3:end)); %#ok<ST2NM>
  470. elseif ischar(inputArgument)
  471. requestedChanRow = str2num(inputArgument(3:end)); %#ok<ST2NM>
  472. else
  473. requestedChanRow = inputArgument;
  474. end
  475. next = '';
  476. elseif ischar(inputArgument) && (any(strcmpi(inputArgument,{'double','int16','short'})) || (strncmp(varargin{i}, 'p:', 2) && inputArgument(3) ~= '\' && inputArgument(3) ~= '/') || strcmpi(next, 'precision'))
  477. if strncmpi(inputArgument, 'p:', 2)
  478. precisionTypeRaw = inputArgument(3:end);
  479. else
  480. precisionTypeRaw = inputArgument;
  481. end
  482. switch precisionTypeRaw
  483. case {'int16','short'}
  484. requestedPrecisionType = 'int16';
  485. case 'double'
  486. requestedPrecisionType = 'double';
  487. otherwise
  488. error('Precision type is not valid. Refer to ''help'' for more information.');
  489. end
  490. next = '';
  491. elseif strcmpi(next, 'packets_per_frame')
  492. if ischar(inputArgument)
  493. requestedPacketsPerFrame = str2double(inputArgument);
  494. else
  495. requestedPacketsPerFrame = inputArgument;
  496. end
  497. elseif strcmpi(next, 'max_tick_multiple')
  498. if ischar(inputArgument)
  499. requestedMaxTickMultiple = str2double(inputArgument);
  500. else
  501. requestedMaxTickMultiple = inputArgument;
  502. end
  503. elseif ischar(inputArgument) && ...
  504. (strncmpi(inputArgument,'hours',4) || strncmpi(inputArgument,'hrs',2) || ...
  505. strncmpi(inputArgument,'minutes',3) || strncmpi(inputArgument,'mins',3) || ...
  506. strncmpi(inputArgument,'seconds',3) || strncmpi(inputArgument,'secs',3) || ...
  507. strncmpi(inputArgument,'samples',4))
  508. requestedTimeScale = inputArgument;
  509. elseif ischar(inputArgument) && length(inputArgument)>3 && ...
  510. (strcmpi(inputArgument(3),'\') || ...
  511. strcmpi(inputArgument(1),'/') || ...
  512. strcmpi(inputArgument(2),'/') || ...
  513. strcmpi(inputArgument(1:2), '\\') || ...
  514. strcmpi(inputArgument(end-3), '.'))
  515. requestedFileName = inputArgument;
  516. assert(exist(requestedFileName, 'file')==2,'The file does not exist.');
  517. else
  518. error(['Invalid argument ''' inputArgument '''.']);
  519. end
  520. end
  521. clear next;
  522. % check uV conversion versus data type
  523. if flagConvertToUv && ~strcmpi(requestedPrecisionType,'double')
  524. warning('Conversion to uV requires double precision; updating from %s to comply',requestedPrecisionType);
  525. requestedPrecisionType = 'double';
  526. end
  527. if ~flagReadData
  528. warning('Reading the header information only.');
  529. end
  530. if flagReport
  531. disp(['openNSx ' NSx.MetaTags.openNSxver]);
  532. end
  533. %% Identify data file name, path, and extension
  534. % for later use, and validate the entry.
  535. if isempty(requestedFileName)
  536. title = 'Choose an NSx file...';
  537. filterSpec = '*.ns*';
  538. if flagFoundGetFile
  539. [requestedFileName, requestedFilePath] = getFile(filterSpec, title);
  540. else
  541. [requestedFileName, requestedFilePath] = uigetfile(filterSpec, title);
  542. end
  543. assert(ischar(requestedFileName),'No file selected');
  544. [~, ~, requestedFileExtension] = fileparts(requestedFileName);
  545. else
  546. if isempty(fileparts(requestedFileName))
  547. requestedFileName = which(requestedFileName);
  548. end
  549. [requestedFilePath,requestedFileName, requestedFileExtension] = fileparts(requestedFileName);
  550. requestedFileName = [requestedFileName requestedFileExtension];
  551. requestedFilePath = [requestedFilePath '/'];
  552. end
  553. assert(ischar(requestedFileName)||requestedFileName~=0,'Could not identify file to read');
  554. fileFullPath = fullfile(requestedFilePath, requestedFileName);
  555. [NSx.MetaTags.FilePath, NSx.MetaTags.Filename, NSx.MetaTags.FileExt] = fileparts(fileFullPath);
  556. % Check to see if 512 setup and calculate offset
  557. if flagMultiNSP
  558. flag512 = regexp(requestedFileName, '-i[0123]-', 'ONCE');
  559. if ~isempty(flag512)
  560. syncShift = multiNSPSync(fullfile(requestedFilePath, requestedFileName));
  561. else
  562. flagMultiNSP = 0;
  563. end
  564. end
  565. %% Loading .x files for multiNSP configuration
  566. if strcmpi(requestedFileExtension(2:4), 'ns6') && length(requestedFileExtension) == 5
  567. requestedFilePath(1) = requestedFileName(end);
  568. requestedFileName(end) = [];
  569. end
  570. %% Measure time required to load data
  571. tic;
  572. %% Process file
  573. FID = fopen([requestedFilePath requestedFileName], 'r', 'ieee-le');
  574. try
  575. %% Read Headers
  576. NSx.MetaTags.FileTypeID = fread(FID, [1,8], 'uint8=>char');
  577. if strcmpi(NSx.MetaTags.FileTypeID, 'NEURALSG')
  578. %% Read Basic Header
  579. basicHeaderBytes = fread(FID, 24, '*uint8');
  580. NSx.MetaTags.FileSpec = '2.1';
  581. NSx.MetaTags.SamplingLabel = char(basicHeaderBytes(1:16));
  582. NSx.MetaTags.TimeRes = double(30000);
  583. NSx.MetaTags.SamplingFreq = NSx.MetaTags.TimeRes / double(typecast(basicHeaderBytes(17:20),'uint32'));
  584. channelCount = double(typecast(basicHeaderBytes(21:24),'uint32'));
  585. NSx.MetaTags.ChannelCount = channelCount;
  586. %% Read Extended Header
  587. extendedHeaderSize = channelCount*4;
  588. extendedHeaderBytes = fread(FID, extendedHeaderSize, '*uint8');
  589. NSx.MetaTags.ChannelID = typecast(extendedHeaderBytes, 'uint32');
  590. try
  591. t = dir(fileFullPath);
  592. NSx.MetaTags.DateTime = t.date;
  593. catch ME2
  594. warning('openNSx:NEURALSG_datetime','Could not compute date from file: %s',ME2.message)
  595. NSx.MetaTags.DateTime = '';
  596. end
  597. timestampSize = 4;
  598. elseif or(strcmpi(NSx.MetaTags.FileTypeID, 'NEURALCD'), strcmpi(NSx.MetaTags.FileTypeID, 'BRSMPGRP'))
  599. %% Read Basic Header
  600. basicHeaderBytes = fread(FID, 306, '*uint8');
  601. NSx.MetaTags.FileSpec = [num2str(double(basicHeaderBytes(1))) '.' num2str(double(basicHeaderBytes(2)))];
  602. %BasicHeaderSize = double(typecast(BasicHeader(3:6), 'uint32'));
  603. NSx.MetaTags.SamplingLabel = char(basicHeaderBytes(7:22))';
  604. NSx.MetaTags.Comment = char(basicHeaderBytes(23:278))';
  605. NSx.MetaTags.TimeRes = double(typecast(basicHeaderBytes(283:286), 'uint32'));
  606. NSx.MetaTags.SamplingFreq = double(30000 / double(typecast(basicHeaderBytes(279:282), 'uint32')));
  607. t = double(typecast(basicHeaderBytes(287:302), 'uint16'));
  608. channelCount = double(typecast(basicHeaderBytes(303:306), 'uint32'));
  609. NSx.MetaTags.ChannelCount = channelCount;
  610. if strcmpi(NSx.MetaTags.FileTypeID, 'NEURALCD')
  611. timestampSize = 4;
  612. elseif strcmpi(NSx.MetaTags.FileTypeID, 'BRSMPGRP')
  613. timestampSize = 8;
  614. end
  615. % Removing extra garbage characters from the Comment field.
  616. NSx.MetaTags.Comment(find(NSx.MetaTags.Comment==0,1):end) = 0;
  617. %% Read Extended Header
  618. extendedHeaderSize = double(channelCount * extHeaderEntrySize);
  619. extendedHeaderBytes = fread(FID, extendedHeaderSize, '*uint8');
  620. for headerIDX = 1:channelCount
  621. byteOffset = double((headerIDX-1)*extHeaderEntrySize);
  622. NSx.ElectrodesInfo(headerIDX).Type = char(extendedHeaderBytes((1:2)+byteOffset))';
  623. assert(strcmpi(NSx.ElectrodesInfo(headerIDX).Type, 'CC'),'extended header not supported');
  624. NSx.ElectrodesInfo(headerIDX).ElectrodeID = typecast(extendedHeaderBytes((3:4)+byteOffset), 'uint16');
  625. NSx.ElectrodesInfo(headerIDX).Label = char(extendedHeaderBytes((5:20)+byteOffset))';
  626. NSx.ElectrodesInfo(headerIDX).ConnectorBank = extendedHeaderBytes(21+byteOffset);
  627. NSx.ElectrodesInfo(headerIDX).ConnectorPin = extendedHeaderBytes(22+byteOffset);
  628. NSx.ElectrodesInfo(headerIDX).MinDigiValue = typecast(extendedHeaderBytes((23:24)+byteOffset), 'int16');
  629. NSx.ElectrodesInfo(headerIDX).MaxDigiValue = typecast(extendedHeaderBytes((25:26)+byteOffset), 'int16');
  630. NSx.ElectrodesInfo(headerIDX).MinAnalogValue = typecast(extendedHeaderBytes((27:28)+byteOffset), 'int16');
  631. NSx.ElectrodesInfo(headerIDX).MaxAnalogValue = typecast(extendedHeaderBytes((29:30)+byteOffset), 'int16');
  632. NSx.ElectrodesInfo(headerIDX).AnalogUnits = char(extendedHeaderBytes((31:46)+byteOffset))';
  633. if flagConvertToUv
  634. NSx.ElectrodesInfo(headerIDX).Resolution = 1;
  635. else
  636. NSx.ElectrodesInfo(headerIDX).Resolution = ...
  637. round(double(NSx.ElectrodesInfo(headerIDX).MaxAnalogValue) / double(NSx.ElectrodesInfo(headerIDX).MaxDigiValue),4);
  638. end
  639. % if strcmpi(waveformUnits, 'uV')
  640. % NSx.ElectrodesInfo(headerIDX).AnalogUnits = '1000 nV (raw) ';
  641. % else
  642. % conversion = int16(double(NSx.ElectrodesInfo(headerIDX).MaxAnalogValue) / double(NSx.ElectrodesInfo(headerIDX).MaxDigiValue)*1000);
  643. % NSx.ElectrodesInfo(headerIDX).AnalogUnits = [num2str(conversion), ' nV (raw) '];
  644. % end
  645. NSx.ElectrodesInfo(headerIDX).HighFreqCorner = typecast(extendedHeaderBytes((47:50)+byteOffset), 'uint32');
  646. NSx.ElectrodesInfo(headerIDX).HighFreqOrder = typecast(extendedHeaderBytes((51:54)+byteOffset), 'uint32');
  647. NSx.ElectrodesInfo(headerIDX).HighFilterType = typecast(extendedHeaderBytes((55:56)+byteOffset), 'uint16');
  648. NSx.ElectrodesInfo(headerIDX).LowFreqCorner = typecast(extendedHeaderBytes((57:60)+byteOffset), 'uint32');
  649. NSx.ElectrodesInfo(headerIDX).LowFreqOrder = typecast(extendedHeaderBytes((61:64)+byteOffset), 'uint32');
  650. NSx.ElectrodesInfo(headerIDX).LowFilterType = typecast(extendedHeaderBytes((65:66)+byteOffset), 'uint16');
  651. end
  652. % Parse DateTime
  653. NSx.MetaTags.DateTimeRaw = t.';
  654. NSx.MetaTags.DateTime = datestr(datenum(t(1), t(2), t(4), t(5), t(6), t(7)));
  655. else
  656. error('Unsupported file spec %s', NSx.MetaTags.FileSpec);
  657. end
  658. % Check zeropad if timeres is 1e9
  659. if NSx.MetaTags.TimeRes > 1e5
  660. assert(~flagZeroPad,'No zeropad for nanosecond resolution timestamps');
  661. end
  662. % Copy ChannelID to MetaTags for filespec 2.2, 2.3, and 3.0 for compatibility with filespec 2.1
  663. if or(strcmpi(NSx.MetaTags.FileTypeID, 'NEURALCD'), strcmpi(NSx.MetaTags.FileTypeID, 'BRSMPGRP'))
  664. NSx.MetaTags.ChannelID = [NSx.ElectrodesInfo.ElectrodeID]';
  665. end
  666. %% Identify key points in file
  667. f.EOexH = double(ftell(FID));
  668. fseek(FID, 0, 'eof');
  669. f.EOF = double(ftell(FID));
  670. %% Save raw headers for saveNSx
  671. NSx.RawData.Headers = [uint8(NSx.MetaTags.FileTypeID(:)); basicHeaderBytes(:); extendedHeaderBytes(:)];
  672. %% Determine the number of channels to read
  673. if ~flagElectrodeReading
  674. if isempty(requestedChanRow)
  675. requestedChannels = NSx.MetaTags.ChannelID;
  676. else
  677. assert(all(requestedChanRow<=channelCount),'Channel numbers must be less than or equal to the total number of channels in the file (%d)',channelCount);
  678. requestedChannels = NSx.MetaTags.ChannelID(requestedChanRow);
  679. NSx.MetaTags.ChannelCount = length(requestedChannels);
  680. end
  681. else
  682. NSx.MetaTags.ChannelCount = length(requestedChannels);
  683. end
  684. if isempty(requestedChanRow)
  685. requestedChanRow = nan(1,length(requestedChannels));
  686. for idx = 1:length(requestedChannels)
  687. assert(ismember(requestedChannels(idx), NSx.MetaTags.ChannelID),'Channel %d does not exist in this file',requestedChannels(idx));
  688. requestedChanRow(idx) = find(NSx.MetaTags.ChannelID == requestedChannels(idx),1);
  689. end
  690. end
  691. numChansToRead = double(length(min(requestedChanRow):max(requestedChanRow)));
  692. %% Central v7.6.0 needs corrections for PTP clock drift - DK 20230303
  693. if NSx.MetaTags.TimeRes > 1e5
  694. packetSize = 1 + 8 + 4 + channelCount*2; % byte (Header) + uint64 (Timestamp) + uint32 (Samples, always 1) + int16*nChan (Data)
  695. numPacketsTotal = floor((f.EOF - f.EOexH)/packetSize);
  696. fseek(FID, f.EOexH + 1 + 8, 'bof'); % byte (Header) + uint64 (Timestamp)
  697. patchCheck = fread(FID,10,'uint32',packetSize-4); % read "samples" counts from 10 packets
  698. if sum(patchCheck) == length(patchCheck) % verify all 1
  699. flagOneSamplePerPacket = true;
  700. end
  701. end
  702. %% Identify and describe data segments
  703. fseek(FID, f.EOexH, 'bof');
  704. if strcmpi(NSx.MetaTags.FileTypeID, 'NEURALSG')
  705. NSx.MetaTags.Timestamp = 0; % No timestamp otherwise
  706. NSx.MetaTags.DataPoints = double(f.EOF-f.EOexH)/(channelCount*2);
  707. NSx.MetaTags.DataDurationSec = NSx.MetaTags.DataPoints/NSx.MetaTags.SamplingFreq;
  708. elseif or(strcmpi(NSx.MetaTags.FileTypeID, 'NEURALCD'), strcmpi(NSx.MetaTags.FileTypeID, 'BRSMPGRP'))
  709. if flagOneSamplePerPacket
  710. % Clock drift patch kills ability to segment files. This check will
  711. % allow segments to be reintroduced into the data structures if a
  712. % timestamp difference of 200% greater than expected is identified
  713. fseek(FID,f.EOexH + 1,'bof'); % + byte (header)
  714. % Process file in frames. initialize with the first packet's
  715. % timestamp.
  716. % For each frame, read the timestamp of the last packet.
  717. % if the difference from the previous frame's last timestamp is
  718. % larger than expected given consistent sampling rates, define a
  719. % segment.
  720. % Move to the next frame.
  721. ticksPerSample = NSx.MetaTags.TimeRes/NSx.MetaTags.SamplingFreq;
  722. minimumPauseLength = requestedMaxTickMultiple*ticksPerSample;
  723. timestampFirst = fread(FID,1,'uint64');
  724. numPacketsProcessed = 0;
  725. segmentTimestamps = nan(1,1e3);
  726. segmentTimestamps(1) = timestampFirst;
  727. % segmentDatapoints = nan(1,1e3);
  728. % segmentDurations = nan(1,1e3);
  729. segmentDatapoints = zeros(1,1e3);
  730. segmentDurations = zeros(1,1e3);
  731. currSegment = 1;
  732. while double(ftell(FID)) < (f.EOF-(packetSize-1-8))
  733. % frames have 'packets_per_frame' packets until the end of the
  734. % file, when the frame may have fewer packets
  735. % number of packets per frame includes first/last packet, which
  736. % means there is one fewer gap than the number of packets
  737. currPacketStartByte = double(ftell(FID)) - 8 - 1;
  738. frameNumPackets = min(requestedPacketsPerFrame, (f.EOF - currPacketStartByte)/packetSize);
  739. if abs(round(frameNumPackets)-frameNumPackets)>0.1
  740. warning('File not packet-aligned')
  741. end
  742. bytesToFrameLastTimestamp = packetSize*(frameNumPackets-1) - 8;
  743. % compute the ticks expected to elapse in this frame with the
  744. % smallest detectable pause (2x sample time, or 66.6 usec)
  745. expectedTicksElapsedNoPause = (frameNumPackets-1) * ticksPerSample;
  746. expectedTicksElapsedMinPause = expectedTicksElapsedNoPause + (minimumPauseLength - ticksPerSample);
  747. % seek to last packet of this frame and read timestamp
  748. fseek(FID, bytesToFrameLastTimestamp, 'cof');
  749. timestampLast = fread(FID,1,'uint64');
  750. % check whether elapsed time for this frame meets or exceeds
  751. % expected length with minimum gap
  752. actualTicksElapsed = timestampLast - timestampFirst;
  753. if actualTicksElapsed >= expectedTicksElapsedMinPause
  754. % a gap exists in this frame; we need to identify where it
  755. % occurs
  756. % save file pointer position
  757. currBytePosition = ftell(FID);
  758. % rewind to prior last_timestamp
  759. fseek(FID, -(bytesToFrameLastTimestamp+8+8), 'cof');
  760. % read all timestamps in this frame
  761. timestamps = fread(FID, frameNumPackets, 'uint64', packetSize-8)';
  762. % find gaps and store if found
  763. tsDiffs = diff(timestamps);
  764. vals = find(tsDiffs > minimumPauseLength);
  765. for jj=1:length(vals)
  766. numDatapointsLastSegment = numPacketsProcessed - sum(segmentDatapoints(1:currSegment)) + vals(jj);
  767. segmentDatapoints(currSegment) = numDatapointsLastSegment;
  768. segmentDurations(currSegment) = timestamps(vals(jj)) - segmentTimestamps(currSegment) + 1;
  769. segmentTimestamps(currSegment + 1) = timestamps(vals(jj) + 1);
  770. currSegment = currSegment + 1;
  771. end
  772. % restore file pointer position
  773. fseek(FID, currBytePosition, 'bof');
  774. end
  775. % update for next round
  776. % -1 on the number of packets processed because the last packet
  777. % is included in the next frame also
  778. timestampFirst = timestampLast;
  779. numPacketsProcessed = numPacketsProcessed + frameNumPackets - 1;
  780. end
  781. numPacketsProcessed = numPacketsProcessed + 1; % account for the overlapped sample on each frame
  782. assert(numPacketsProcessed == numPacketsTotal, 'Incosistent number of packets processed (%d) versus number of packets in file (%d)',numPacketsProcessed,(f.EOF-f.EOexH)/packetSize);
  783. % compute number of datapoints in the last segment
  784. % add one to the number of packets processed to account for the
  785. % last packet of the file not being included in a subsequent frame
  786. segmentDatapoints(currSegment) = numPacketsProcessed - sum(segmentDatapoints(1:currSegment-1));
  787. segmentDurations(currSegment) = timestampLast - segmentTimestamps(currSegment) + 1;
  788. % add into NSx structure
  789. NSx.MetaTags.Timestamp = segmentTimestamps(1:currSegment);
  790. NSx.MetaTags.DataPoints = segmentDatapoints(1:currSegment);
  791. NSx.MetaTags.DataDurationSec = segmentDurations(1:currSegment)/NSx.MetaTags.TimeRes;
  792. file.MetaTags.DataDurationTimeRes = segmentDurations(1:currSegment);
  793. else
  794. segmentCount = 0;
  795. while double(ftell(FID)) < f.EOF
  796. headerByte = fread(FID, 1, 'uint8=>double');
  797. if headerByte ~= 1
  798. % Fixing another bug in Central 6.01.00.00 TOC where DataPoints is
  799. % not written back into the Data Header
  800. %% BIG NEEDS TO BE FIXED
  801. NSx.MetaTags.DataPoints = floor(double(f.EOF - (f.EOexH+1+timestampSize+4))/(channelCount*2));
  802. NSx.MetaTags.DataDurationSec = NSx.MetaTags.DataPoints/NSx.MetaTags.SamplingFreq;
  803. break;
  804. end
  805. segmentCount = segmentCount + 1;
  806. if strcmpi(NSx.MetaTags.FileTypeID, 'NEURALCD')
  807. startTimestamp = fread(FID, 1, 'uint32');
  808. elseif strcmpi(NSx.MetaTags.FileTypeID, 'BRSMPGRP')
  809. startTimestamp = fread(FID, 1, 'uint64');
  810. end
  811. if flagMultiNSP
  812. % close existing (read-only) file descriptor
  813. currBytePosition = ftell(FID);
  814. fclose(FID);
  815. % open a file descriptor for read/write, write, and close
  816. FID = fopen([requestedFilePath requestedFileName], 'r+', 'ieee-le');
  817. startTimestamp = startTimestamp + syncShift;
  818. fseek(FID, -timestampSize, 'cof');
  819. fwrite(FID, startTimestamp, '*uint32');
  820. fclose(FID);
  821. % re-open read-only and seek to remembered position
  822. FID = fopen([requestedFilePath requestedFileName], 'r', 'ieee-le');
  823. fseek(FID,currBytePosition,'bof');
  824. end
  825. NSx.MetaTags.Timestamp(segmentCount) = startTimestamp;
  826. NSx.MetaTags.DataPoints(segmentCount) = fread(FID, 1, 'uint32=>double');
  827. NSx.MetaTags.DataDurationSec(segmentCount) = NSx.MetaTags.DataPoints(segmentCount)/NSx.MetaTags.SamplingFreq;
  828. file.MetaTags.DataDurationTimeRes(segmentCount) = startTimestamp*NSx.MetaTags.TimeRes/NSx.MetaTags.SamplingFreq;
  829. fseek(FID, NSx.MetaTags.DataPoints(segmentCount) * channelCount * 2, 'cof');
  830. % Fixing the bug in 6.01.00.00 TOC where DataPoints is not
  831. % updated and is left as 0
  832. % NSx.MetaTags.DataPoints(segmentCount) = (f.EOData(segmentCount)-f.BOData(segmentCount))/(ChannelCount*2);
  833. end
  834. end
  835. end
  836. %% Calculate file pointers for data
  837. if strcmpi(NSx.MetaTags.FileTypeID, 'NEURALSG')
  838. % Determining DataPoints
  839. f.BOData = f.EOexH;
  840. f.EOData = f.EOF;
  841. elseif or(strcmpi(NSx.MetaTags.FileTypeID, 'NEURALCD'), strcmpi(NSx.MetaTags.FileTypeID, 'BRSMPGRP'))
  842. byteOffset = 1 + timestampSize + 4;
  843. if flagOneSamplePerPacket
  844. segmentOffset = f.EOexH;
  845. f.BOData = segmentOffset + byteOffset + [0 packetSize*cumsum(NSx.MetaTags.DataPoints(1:end-1))];
  846. f.EOData = segmentOffset + packetSize*cumsum(NSx.MetaTags.DataPoints);
  847. else
  848. segmentOffset = f.EOexH + (1:length(NSx.MetaTags.DataPoints))*byteOffset;
  849. f.BOData = segmentOffset + [0 cumsum(channelCount*NSx.MetaTags.DataPoints(1:end-1)*2)];
  850. f.EOData = segmentOffset + 2*channelCount*cumsum(NSx.MetaTags.DataPoints) - 1;
  851. end
  852. end
  853. % Determining if the file has a pause in it
  854. if length(NSx.MetaTags.DataPoints) > 1
  855. NSx.RawData.PausedFile = 1;
  856. end
  857. %% Save data headers for saveNSx
  858. dataHeaderBytes = cell(1,length(f.BOData));
  859. for ss = 1:length(f.BOData)
  860. headerByteSize = 1 + timestampSize + 4;
  861. fseek(FID, f.BOData(ss)-headerByteSize, 'bof');
  862. dataHeaderBytes{ss} = fread(FID, headerByteSize, '*uint8');
  863. end
  864. NSx.RawData.DataHeader = cat(1,dataHeaderBytes{:});
  865. %% Remove extra ElectrodesInfo for channels not read
  866. if or(strcmpi(NSx.MetaTags.FileTypeID, 'NEURALCD'), strcmpi(NSx.MetaTags.FileTypeID, 'BRSMPGRP'))
  867. for headerIDX = length(NSx.ElectrodesInfo):-1:1
  868. if ~ismember(headerIDX, requestedChanRow)
  869. NSx.ElectrodesInfo(headerIDX) = [];
  870. end
  871. end
  872. end
  873. %% Identify and validate requested first and last data points
  874. % Note that new files that use precision time protocol (PTP), for which
  875. % there is one sample per packet and hence the user request translates
  876. % to a starting and ending data packet. In older NSx files, there were
  877. % multiple samples per packet and the user request is for a starting
  878. % sample (which would exist inside a data packet). This code refers to
  879. % "data points" as a broad term to encapsulate both scenarios.
  880. if ~flagModifiedTime
  881. % default whole file
  882. requestedStartDataPoint = 1;
  883. requestedEndDataPoint = sum(NSx.MetaTags.DataPoints);
  884. else
  885. % TO-DO: utilize Gemini sample-by-sample timestamps
  886. switch requestedTimeScale
  887. case {'sec', 'secs', 'second', 'seconds'}
  888. % convert seconds to samples
  889. requestedStartDataPoint = requestedStartValue * NSx.MetaTags.SamplingFreq + 1;
  890. requestedEndDataPoint = requestedEndValue * NSx.MetaTags.SamplingFreq;
  891. case {'min', 'mins', 'minute', 'minutes'}
  892. % convert minutes to samples
  893. requestedStartDataPoint = requestedStartValue * NSx.MetaTags.SamplingFreq * 60 + 1;
  894. requestedEndDataPoint = requestedEndValue * NSx.MetaTags.SamplingFreq * 60;
  895. case {'hour', 'hours'}
  896. % convert hours to samples
  897. requestedStartDataPoint = requestedStartValue * NSx.MetaTags.SamplingFreq * 3600 + 1;
  898. requestedEndDataPoint = requestedEndValue * NSx.MetaTags.SamplingFreq * 3600;
  899. case {'sample','samples'}
  900. % carry over samples
  901. requestedStartDataPoint = requestedStartValue;
  902. requestedEndDataPoint = requestedEndValue;
  903. otherwise
  904. % should never get here based on input argument processing
  905. error('Unknown requested time scale');
  906. end
  907. end
  908. % validate start and end data points
  909. assert(requestedEndDataPoint>=requestedStartDataPoint,'Start data point (%d) must be less than the end data point (%d)',requestedStartDataPoint,requestedEndDataPoint);
  910. assert(requestedStartDataPoint<=sum(NSx.MetaTags.DataPoints),'Start data point (%d) is greater than total number of data point (%d)',requestedStartDataPoint,sum(NSx.MetaTags.DataPoints));
  911. if requestedStartDataPoint <= 0
  912. warning('Start data point (%d) must be greater than or equal to 1; updating to comply.',requestedStartDataPoint);
  913. requestedStartDataPoint = 1;
  914. end
  915. if requestedEndDataPoint > sum(NSx.MetaTags.DataPoints)
  916. warning('End data point (%d) must be less than or equal to the total number of data points (%d).',requestedEndDataPoint,sum(NSx.MetaTags.DataPoints));
  917. response = input('Do you wish to update the last requested data point to last one available in the file and continue? (y/N) ', 's');
  918. if strcmpi(response,'y')
  919. warning('Changed end data point from %d to %d',requestedEndDataPoint,sum(NSx.MetaTags.DataPoints));
  920. requestedEndDataPoint = sum(NSx.MetaTags.DataPoints);
  921. else
  922. error('Invalid last requested data point');
  923. end
  924. end
  925. %% Identify data segments containing requested packets
  926. requestedSegments = nan(1,2); % first and last requested segments
  927. startTimeStampShift = 0;
  928. % user requested specific data points: look for start/end segments
  929. % containing these data points
  930. if flagModifiedTime
  931. dataPointOfInterest = requestedStartDataPoint;
  932. segmentStartDataPoint = zeros(1,length(NSx.MetaTags.DataPoints));
  933. segmentDataPoints = zeros(1,length(NSx.MetaTags.DataPoints));
  934. for currSegment = 1:length(NSx.MetaTags.DataPoints)
  935. if dataPointOfInterest <= sum(NSx.MetaTags.DataPoints(1:currSegment))
  936. if all(isnan(requestedSegments))
  937. if currSegment == 1
  938. segmentStartDataPoint(currSegment) = dataPointOfInterest;
  939. else
  940. segmentStartDataPoint(currSegment) = dataPointOfInterest - sum(NSx.MetaTags.DataPoints(1:currSegment-1));
  941. end
  942. startTimeStampShift = (segmentStartDataPoint(currSegment)-1) * NSx.MetaTags.TimeRes / NSx.MetaTags.SamplingFreq;
  943. if requestedEndDataPoint <= sum(NSx.MetaTags.DataPoints(1:currSegment))
  944. segmentDataPoints(currSegment) = requestedEndDataPoint - sum(NSx.MetaTags.DataPoints(1:currSegment-1)) - segmentStartDataPoint(currSegment) + 1;
  945. requestedSegments = [currSegment currSegment];
  946. break;
  947. end
  948. segmentDataPoints(currSegment) = sum(NSx.MetaTags.DataPoints(1:currSegment)) - dataPointOfInterest + 1;
  949. dataPointOfInterest = requestedEndDataPoint;
  950. else
  951. segmentStartDataPoint(currSegment) = 1;
  952. if currSegment == 1
  953. segmentDataPoints(currSegment) = dataPointOfInterest;
  954. else
  955. segmentDataPoints(currSegment) = dataPointOfInterest - sum(NSx.MetaTags.DataPoints(1:currSegment-1));
  956. requestedSegments(2) = currSegment;
  957. end
  958. break;
  959. end
  960. requestedSegments(1) = currSegment;
  961. else
  962. if all(isnan(requestedSegments))
  963. segmentStartDataPoint(currSegment) = NSx.MetaTags.DataPoints(currSegment);
  964. segmentDataPoints(currSegment) = 0;
  965. elseif isnan(requestedSegments(2))
  966. segmentStartDataPoint(currSegment) = 1;
  967. segmentDataPoints(currSegment) = NSx.MetaTags.DataPoints(currSegment);
  968. else
  969. segmentStartDataPoint(currSegment) = 1;
  970. segmentDataPoints(currSegment) = 0;
  971. end
  972. end
  973. end
  974. else
  975. requestedSegments = [1 length(f.BOData)];
  976. segmentStartDataPoint = ones(1,length(f.BOData));
  977. segmentDataPoints = NSx.MetaTags.DataPoints;
  978. end
  979. % calculate total number of data points requested
  980. numDataPointsRequested = sum(floor(segmentDataPoints/requestedSkipFactor));
  981. %% Read data
  982. file.MetaTags.DataPoints = NSx.MetaTags.DataPoints;
  983. file.MetaTags.DataDurationSec = NSx.MetaTags.DataDurationSec;
  984. file.MetaTags.Timestamp = NSx.MetaTags.Timestamp;
  985. if flagReadData
  986. % loop over requested data segments
  987. NSx.Data = cell(1,diff(requestedSegments)+1);
  988. for currSegment = requestedSegments(1):requestedSegments(2)
  989. outputSegment = currSegment - requestedSegments(1) + 1;
  990. % seek to start of data
  991. fseek(FID, f.BOData(currSegment), 'bof');
  992. % seek to first requested packet in the current segment
  993. if flagOneSamplePerPacket
  994. fseek(FID, (segmentStartDataPoint(currSegment) - 1) * packetSize, 'cof');
  995. else
  996. fseek(FID, (segmentStartDataPoint(currSegment) - 1) * 2 * channelCount, 'cof');
  997. end
  998. % seek to first requested channel in the current packet
  999. fseek(FID, (find(NSx.MetaTags.ChannelID == min(requestedChannels))-1) * 2, 'cof');
  1000. % set up parameters for reading data
  1001. precisionString = sprintf('%d*int16=>%s',numChansToRead,requestedPrecisionType);
  1002. outputDimensions = [numChansToRead floor(segmentDataPoints(currSegment)/requestedSkipFactor)];
  1003. if flagOneSamplePerPacket
  1004. bytesToSkipNormal = packetSize - 2*numChansToRead; % standard (i.e., skip factor==1)
  1005. bytesSkipFactor = packetSize*(requestedSkipFactor - 1); % additional to skip (skip factor > 1)
  1006. else
  1007. bytesToSkipNormal = 2*(channelCount - numChansToRead);
  1008. bytesSkipFactor = 2*channelCount*(requestedSkipFactor-1);
  1009. end
  1010. bytesToSkip = bytesToSkipNormal + bytesSkipFactor; % total
  1011. % read data
  1012. NSx.Data{outputSegment} = fread(FID, outputDimensions, precisionString, bytesToSkip);
  1013. % define user tags: info specific to data being read
  1014. NSx.MetaTags.Timestamp = NSx.MetaTags.Timestamp(requestedSegments(1):requestedSegments(2));
  1015. NSx.MetaTags.Timestamp(1) = NSx.MetaTags.Timestamp(1) + startTimeStampShift;
  1016. NSx.MetaTags.DataPoints = cellfun(@(x) size(x,2), NSx.Data, 'UniformOutput', true);
  1017. NSx.MetaTags.DataDurationSec = NSx.MetaTags.DataDurationSec(requestedSegments(1):requestedSegments(2));
  1018. NSx.MetaTags.DataDurationSec(1) = NSx.MetaTags.DataDurationSec(1) - (segmentStartDataPoint(requestedSegments(1))-1)/NSx.MetaTags.SamplingFreq;
  1019. NSx.MetaTags.DataDurationSec(end) = NSx.MetaTags.DataPoints(end)/NSx.MetaTags.SamplingFreq;
  1020. end
  1021. % verify amount of data read from disk
  1022. numDataPointsRead = sum(cellfun(@(x)size(x,2),NSx.Data));
  1023. if numDataPointsRead~=numDataPointsRequested
  1024. warning('Expected to read %d data points, but output has %d data points.',numDataPointsRequested,numDataPointsRead);
  1025. end
  1026. end
  1027. catch ME
  1028. fclose(FID);
  1029. rethrow(ME);
  1030. end
  1031. fclose(FID);
  1032. %% Bug fix
  1033. % Fix a bug in 6.03 where data packets with 0 length may be added
  1034. if any(NSx.MetaTags.DataPoints == 0) && flagReadData
  1035. segmentsThatAreZero = find(NSx.MetaTags.DataPoints == 0);
  1036. NSx.MetaTags.DataPoints(segmentsThatAreZero) = [];
  1037. NSx.MetaTags.DataDurationSec(segmentsThatAreZero) = [];
  1038. NSx.MetaTags.Timestamp(segmentsThatAreZero) = [];
  1039. NSx.Data(segmentsThatAreZero) = [];
  1040. end
  1041. %% Remove extra channels that were read, but weren't supposed to be read
  1042. % Commenting this section out since I think that previous code should
  1043. % capture this - DK 20230303
  1044. % channelThatWereRead = min(userRequestedChanRow):max(userRequestedChanRow);
  1045. % if ~isempty(setdiff(channelThatWereRead,userRequestedChanRow))
  1046. % deleteChannels = setdiff(channelThatWereRead, userRequestedChanRow) - min(userRequestedChanRow) + 1;
  1047. % if NSx.RawData.PausedFile
  1048. % for segIDX = 1:size(NSx.Data,2)
  1049. % NSx.Data{segIDX}(deleteChannels,:) = [];
  1050. % end
  1051. % else
  1052. % NSx.Data(deleteChannels,:) = [];
  1053. % end
  1054. % end
  1055. %% Adjust the ChannelID variable to match the read electrodes
  1056. channelIDToDelete = setdiff(1:channelCount, requestedChanRow);
  1057. NSx.MetaTags.ChannelID(channelIDToDelete) = [];
  1058. %% Zero-pad data if requested
  1059. if flagZeroPad
  1060. % only operate on first data segment
  1061. currSegment = 1;
  1062. % compute how many zeros and total number of values to add
  1063. numZerosToAdd = floor(NSx.MetaTags.Timestamp(currSegment) / requestedSkipFactor);
  1064. if flagMultiNSP
  1065. numZerosToAdd = numZerosToAdd + syncShift;
  1066. end
  1067. numValuesToAdd = NSx.MetaTags.ChannelCount * numZerosToAdd;
  1068. % sanity check
  1069. if strcmpi(NSx.MetaTags.FileTypeID, 'BRSMPGRP')
  1070. % calculate how many values and how many bytes
  1071. numValuesOfData = sum(cellfun(@numel,NSx.Data));
  1072. if strcmpi(requestedPrecisionType,'int16')
  1073. numBytesToAdd = numValuesToAdd*2;
  1074. numBytesOfData = numValuesOfData*2;
  1075. elseif strcmpi(requestedPrecisionType,'double')
  1076. numBytesToAdd = numValuesToAdd*8;
  1077. numBytesOfData = numValuesOfData*8;
  1078. end
  1079. % check whether to show the warning
  1080. flagShowZeroPadWarning = 1;
  1081. if flagFoundSettingsManager
  1082. flagShowZeroPadWarning = NPMKSettings.ShowZeroPadWarning;
  1083. end
  1084. % generate warning and ask to continue
  1085. if NSx.MetaTags.Timestamp(1) > 30000 && flagShowZeroPadWarning
  1086. warning('Zero padding would add %d bytes to data which already require %d bytes in memory', numBytesToAdd, numBytesOfData);
  1087. response = input('Do you wish to continue? (y/N) ', 's');
  1088. if ~strcmpi(response, 'y')
  1089. warning('Turned off zero padding by user request');
  1090. flagZeroPad = 0;
  1091. end
  1092. end
  1093. % check on continuing to show this warning
  1094. if flagFoundSettingsManager
  1095. response = input('Do you want NPMK to continue to ask you about this every time? (Y/n) ', 's');
  1096. if ~strcmpi(response, 'n')
  1097. NPMKSettings.ShowZeroPadWarning = 1;
  1098. else
  1099. NPMKSettings.ShowZeroPadWarning = 0;
  1100. end
  1101. settingsManager(NPMKSettings);
  1102. end
  1103. end
  1104. % perform zero padding
  1105. % (flagZeroPad may be set to false above, so need to re-evaluate)
  1106. if requestedStartDataPoint == 1 && flagZeroPad
  1107. % only for the first data segment
  1108. if flagReadData
  1109. NSx.Data{currSegment} = [zeros(NSx.MetaTags.ChannelCount, numZerosToAdd, requestedPrecisionType) NSx.Data{currSegment}];
  1110. end
  1111. % update metadata
  1112. NSx.MetaTags.DataPoints(currSegment) = size(NSx.Data{currSegment},2);
  1113. NSx.MetaTags.Timestamp(currSegment) = 0;
  1114. NSx.MetaTags.DataDurationSec(currSegment) = size(NSx.Data{currSegment},2)/NSx.MetaTags.SamplingFreq;
  1115. end
  1116. end
  1117. %% Adjust for the data's unit.
  1118. if flagConvertToUv
  1119. NSx.Data = cellfun(@(x) bsxfun(@rdivide, x, 1./(double([NSx.ElectrodesInfo.MaxAnalogValue])./double([NSx.ElectrodesInfo.MaxDigiValue]))'),NSx.Data ,'UniformOutput',false);
  1120. else
  1121. flagShowuVWarning = 1;
  1122. if flagFoundSettingsManager
  1123. flagShowuVWarning = NPMKSettings.ShowuVWarning;
  1124. end
  1125. if flagShowuVWarning
  1126. warning('Note that data are in units of 1/4 ??V; see ''uv'' argument.');
  1127. end
  1128. if flagShowuVWarning && flagFoundSettingsManager
  1129. response = input('Do you want NPMK to continue to warn you about this every time? (Y/n) ', 's');
  1130. if ~strcmpi(response, 'n')
  1131. NPMKSettings.ShowuVWarning = 1;
  1132. else
  1133. NPMKSettings.ShowuVWarning = 0;
  1134. end
  1135. settingsManager(NPMKSettings);
  1136. end
  1137. end
  1138. %% Add implementation of samplealign for cases where it is needed
  1139. if flagOneSamplePerPacket && flagAlign
  1140. for ii = 1:length(NSx.Data)
  1141. fileDataLength = file.MetaTags.DataPoints(ii);
  1142. fileDuration = file.MetaTags.DataDurationTimeRes(ii);
  1143. % Calculate the ratio between time gaps and expected time gap
  1144. % based on the sampling rate of the recording. A recording
  1145. % where the claimed sampling rate and true sampling rate based
  1146. % off PTP time are identical will have a ratio of 1;
  1147. samplingRates = fileDuration/fileDataLength/NSx.MetaTags.TimeRes*NSx.MetaTags.SamplingFreq;
  1148. % Calculate the number of samples that should be added or
  1149. % removed
  1150. addedSamples = round((samplingRates-1)*fileDataLength);
  1151. % Establish where the points should be added or removed
  1152. gapIndex = round(fileDataLength/(abs(addedSamples)+1));
  1153. % calculate the portion of samples added/subtracted to the
  1154. % requested data, which may be shorter than the full file
  1155. % use floor because we need addedsamples+1 sections to avoid
  1156. % adding/subtracting samples at the beginning or end of the data.
  1157. addedSamples = floor(addedSamples * NSx.MetaTags.DataPoints(ii)/fileDataLength);
  1158. if addedSamples == 0
  1159. continue;
  1160. end
  1161. % split into cell arrays
  1162. dim1Size = size(NSx.Data{ii},1);
  1163. if gapIndex >= size(NSx.Data{ii},2)
  1164. if abs(addedSamples)>1
  1165. warning('Expected to add or remove only one sample')
  1166. end
  1167. dim2Size = [round(size(NSx.Data{ii},2)/2) size(NSx.Data{ii},2)-round(size(NSx.Data{ii},2)/2)];
  1168. else
  1169. dim2Size = [repmat(gapIndex,1,abs(addedSamples)) size(NSx.Data{ii},2) - gapIndex*abs(addedSamples)];
  1170. end
  1171. NSx.Data{ii} = mat2cell(NSx.Data{ii},dim1Size,dim2Size);
  1172. % add or subtract
  1173. if abs(addedSamples)==1
  1174. sampleString = sprintf('%d sample',abs(addedSamples));
  1175. whereString = 'at midpoint';
  1176. else
  1177. sampleString = sprintf('%d samples',abs(addedSamples));
  1178. whereString = 'evenly spaced';
  1179. end
  1180. if length(NSx.Data)==1
  1181. segmentString = 'the data';
  1182. else
  1183. segmentString = sprintf('data segment %d/%d',ii,length(NSx.Data));
  1184. end
  1185. if addedSamples>0
  1186. NSx.Data{ii}(1:end-1) = cellfun(@(x) [x x(:,end)], NSx.Data{ii}(1:end-1), 'UniformOutput',false);
  1187. warning('Added %s to %s (%s) for clock drift alignment',sampleString,segmentString,whereString)
  1188. elseif addedSamples<0
  1189. NSx.Data{ii}(1:end-1) = cellfun(@(x) x(:,1:end-1), NSx.Data{ii}(1:end-1), 'UniformOutput',false);
  1190. warning('Removed %s from %s (%s) for clock drift alignment',sampleString,segmentString,whereString)
  1191. end
  1192. % combine to form the full data again
  1193. NSx.Data{ii} = cat(2,NSx.Data{ii}{:});
  1194. % recompute some metadata
  1195. NSx.MetaTags.DataPoints(ii) = size(NSx.Data{ii},2);
  1196. NSx.MetaTags.DataDurationSec(ii) = size(NSx.Data{ii},2)/NSx.MetaTags.SamplingFreq;
  1197. end
  1198. end
  1199. % Convert data points in sample to seconds
  1200. NSx.MetaTags.DataPointsSec = double(NSx.MetaTags.DataPoints)/NSx.MetaTags.SamplingFreq;
  1201. % Remove the cell if there is only one recorded segment present
  1202. if iscell(NSx.Data) && length(NSx.Data)==1
  1203. NSx.Data = NSx.Data{1};
  1204. end
  1205. % Display a report of basic file information and the Basic Header.
  1206. if flagReport
  1207. disp( '*** FILE INFO **************************');
  1208. disp(['File Path = ' NSx.MetaTags.FilePath]);
  1209. disp(['File Name = ' NSx.MetaTags.Filename]);
  1210. disp(['File Extension = ' NSx.MetaTags.FileExt]);
  1211. disp(['File Version = ' NSx.MetaTags.FileSpec]);
  1212. disp(['Duration (seconds) = ' num2str(NSx.MetaTags.DataDurationSec)]);
  1213. disp(['Total Datapoints = ' num2str(NSx.MetaTags.DataPoints)]);
  1214. disp(' ');
  1215. disp( '*** BASIC HEADER ***********************');
  1216. disp(['File Type ID = ' NSx.MetaTags.FileTypeID]);
  1217. disp(['Sample Frequency = ' num2str(double(NSx.MetaTags.SamplingFreq))]);
  1218. disp(['Electrodes Read = ' num2str(double(NSx.MetaTags.ChannelCount))]);
  1219. disp(['Datapoints Read = ' num2str(size(NSx.Data,2))]);
  1220. end
  1221. % Create output variable in user workspace even if no output argument
  1222. outputName = ['NS' requestedFileExtension(4)];
  1223. if (nargout == 0)
  1224. assignin('caller', outputName, NSx);
  1225. else
  1226. varargout{1} = NSx;
  1227. end
  1228. % Print the load time
  1229. if flagReport
  1230. disp(['The load time for ' outputName ' file was ' num2str(toc, '%0.1f') ' seconds.']);
  1231. end
  1232. end