BlockVectorLegacyLoader.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. classdef BlockVectorLegacyLoader < BlockVectorData
  2. %BLOCKVECTORLEGACYLOADER Static class with resources to load file data
  3. % to legacy style structures.
  4. properties(GetAccess = private, Constant = true)
  5. % Fixed by file format - there's only 1 byte for each
  6. MAX_CHANNEL_ARTICHOKE = 256;
  7. MAX_CHANNEL_INDEX = 256;
  8. end
  9. methods (Static = true)
  10. function aData = Legacy_Load_Raw_v1(aSourceData, aHeader, aTimeRange)
  11. % Check to make sure the file format makes sense for a raw file
  12. if aHeader.header.numSamplesPerBlock ~= 1
  13. error('load_AxIS_raw:argNumSamplesPerBlock', ...
  14. 'Invalid header for RAW file: incorrect samples per block');
  15. end
  16. if aHeader.header.blockHeaderSize ~= 0
  17. error('load_AxIS_raw:argBlockHeaderSize', ...
  18. 'Invalid header for RAW file: incorrect block header size');
  19. end
  20. fseek(aSourceData.FileID, int64(aSourceData.Start), 'bof');
  21. % Copy input structure to output; then add information to output
  22. aData = aHeader;
  23. aData.numChannels = aData.header.numChannelsPerBlock;
  24. if aData.numChannels ~= aData.channelArray.numChannels
  25. error('load_AxIS_raw:mismatchNumChannels', ...
  26. 'Invalid RAW file: mismatched number of channels');
  27. end
  28. if isempty(aHeader.loadedChannels)
  29. % No channels requested - don't read any data
  30. return;
  31. end
  32. % Read data
  33. fSampleFreq = int64(aData.samplingFrequency);
  34. fChannelCount = int64(aData.numChannels);
  35. fBytesPerSecond = fSampleFreq * fChannelCount * int64(2);
  36. fMaxTime = int64(aSourceData.EntryRecord.Length / fBytesPerSecond);
  37. if ~strcmp(aTimeRange, 'all')
  38. fStart = aTimeRange(1);
  39. fEnd =aTimeRange(2);
  40. if(fStart >= fEnd)
  41. warning('Invalid timespan argument: end time < start time. No valid waveform can be returned.');
  42. return;
  43. end
  44. if(fStart > fMaxTime)
  45. warning( 'DataSet only contains %d Seconds of data (%d Seconds was the requested start time). No waveform will be returned.', fMaxTime, fStart);
  46. return;
  47. end
  48. if(fEnd > fMaxTime)
  49. fEnd = fMaxTime;
  50. warning('DataSet only contains %d Seconds of data. Returned waveforms will be shorter than requested (%d Seconds).', fMaxTime, fEnd - fStart);
  51. end
  52. fSkipInitialSamples = fStart * fSampleFreq;
  53. fSkipInitialBytes = fSkipInitialSamples * fChannelCount * int64(2);
  54. fNumSamples = int64((fEnd - fStart) * fSampleFreq);
  55. fseek(aSourceData.FileID, fSkipInitialBytes, 'cof');
  56. else
  57. fNumSamples = int64(fMaxTime * fSampleFreq);
  58. end
  59. if length(aHeader.loadedChannels) == 1
  60. % We're only reading one channel. For efficiency, take advantage of fread's
  61. % 'skip' argument.
  62. % First, read and throw away other channels at the beginning of the data
  63. fread(aSourceData.FileID, aHeader.loadedChannels - 1, 'int16=>int16');
  64. % Read the data for the given channel, skipping from one to the next
  65. aData.channelData = fread(aSourceData.FileID, fNumSamples, 'int16=>int16', 2*(aData.numChannels - 1));
  66. else
  67. % We're reading multiple channels. There's no easy way to translate this into a
  68. % single fread, so we read all of the channels in the sample range in question,
  69. % then remap, throwing away what we didn't want.
  70. % First, read everything within the time range
  71. fNumSamples = fNumSamples * int64(aData.numChannels);
  72. fTempChannelData = fread(aSourceData.FileID, fNumSamples, 'int16=>int16');
  73. % This test (which can fail only when we read the the end of the file)
  74. % makes sure that we didn't get a number of samples that's not divisible
  75. % by the number of channels.
  76. fRemainderCount = mod(length(fTempChannelData), aData.numChannels);
  77. if fRemainderCount ~= 0
  78. warning('load_AXiS_raw:remainderCheck', ...
  79. 'File %s has wrong number of samples for %u channels', ...
  80. aSourceData.FileID, aData.numChannels);
  81. end
  82. % Convert the 1D array to a 2D array, with channel as the second dimension
  83. % (starts as first dimension and then is transposed)
  84. fTempChannelData = reshape(fTempChannelData(:), aData.numChannels, []);
  85. % Remap
  86. aData.channelData = fTempChannelData(aData.loadedChannels, :);
  87. aData.channelData = aData.channelData';
  88. end
  89. end
  90. function aData = Legacy_Load_spike_v1(aSourceData, aHeader, aTimeRange)
  91. % Check to make sure the file format makes sense for a spike file
  92. if aHeader.header.numChannelsPerBlock ~= 1
  93. error('Load_spike_v1:argNumChannelsPerBlock', ...
  94. 'Invalid header for SPIKE file: incorrect channels per block');
  95. end
  96. if aHeader.header.blockHeaderSize < Spike_v1.LOADED_HEADER_SIZE
  97. error('Load_spike_v1:argBlockHeaderSize', ...
  98. 'Invalid header for SPIKE file: block header size too small');
  99. end
  100. if aHeader.header.numSamplesPerBlock < 1
  101. error('load_AxIS_spike:argNumSamplesPerBlock', ...
  102. 'Invalid header for SPIKE file: number of samples per block < 1');
  103. end
  104. % Copy input structure to output; then add information to output
  105. aData = aHeader;
  106. if isempty(aHeader.loadedChannels)
  107. % No channels requested - don't read any data
  108. return;
  109. end
  110. if ischar(aTimeRange) && strcmp(aTimeRange, 'all')
  111. fFirstSample = 0;
  112. fLastSample = Inf;
  113. else
  114. fFirstSample = aTimeRange(1) * aData.samplingFrequency;
  115. fLastSample = aTimeRange(2) * aData.samplingFrequency;
  116. end
  117. fSparseChannelIndex = sparse(...
  118. double([aData.channelArray.channel.channelAchk] + 1) , ...
  119. double([aData.channelArray.channel.channelIndex] + 1), ...
  120. double(1:aData.channelArray.numChannels), ...
  121. BlockVectorLegacyLoader.MAX_CHANNEL_ARTICHOKE,...
  122. BlockVectorLegacyLoader.MAX_CHANNEL_INDEX);
  123. spikeIndex = 1;
  124. % Manage the spike array to avoid "growing" it
  125. fSpikeArrayCapacity = 0;
  126. fseek(aSourceData.FileID, int64(aSourceData.Start), 'bof');
  127. fStop = aSourceData.Start + aSourceData.EntryRecord.Length;
  128. while ftell(aSourceData.FileID) < fStop
  129. % Load the block header for this spike
  130. fCurrentSpike = [];
  131. fCurrentSpike.startingSample = fread(aSourceData.FileID, 1, 'int64'); %Changed uint64 to int64
  132. if feof(aSourceData.FileID)
  133. % we were at the end of the file
  134. break;
  135. end
  136. fHardwareChannelIndex = fread(aSourceData.FileID, 1, 'uint8');
  137. fHardwareChannelAchk = fread(aSourceData.FileID, 1, 'uint8');
  138. fCurrentSpike.triggerSampleOffset = fread(aSourceData.FileID, 1, 'uint32');
  139. fCurrentSpike.stDev = fread(aSourceData.FileID, 1, 'double');
  140. fCurrentSpike.threshold = fread(aSourceData.FileID, 1, 'double');
  141. % Get well and electrode row and column
  142. fCurrentSpike.channelArrayIndex = full(fSparseChannelIndex(fHardwareChannelAchk + 1, fHardwareChannelIndex + 1));
  143. if fCurrentSpike.channelArrayIndex == 0
  144. error('load_AxIS_spike:invalidSpikeChannel', ...
  145. ['Spike has invalid Artichoke/Channel number ' num2str(fHardwareChannelAchk) ...
  146. ' / ' num2str(fHardwareChannelIndex)]);
  147. end
  148. fCurrentSpike.channelInfo = aData.channelArray.channel(fCurrentSpike.channelArrayIndex);
  149. % Historical channel number -- XY, where X = electrodeColumn and Y = electrodeRow
  150. % This field is deprecated, since it doesn't contain multiwell information.
  151. fCurrentSpike.channel = 10*fCurrentSpike.channelInfo.electrodeColumn + ...
  152. fCurrentSpike.channelInfo.electrodeRow;
  153. % Seek forward to the end of the header.
  154. % It might seem cleaner to do this by saving the current position
  155. % before we read the header and seeking forward from there.
  156. % However, that method will not handle files larger than 4GB if fseek
  157. % and ftell are not 64-bit clean.
  158. fseek(aSourceData.FileID, aData.header.blockHeaderSize - Spike_v1.LOADED_HEADER_SIZE, 'cof');
  159. if ftell(aSourceData.FileID) >= fStop
  160. % file ended before the start of the data -- looks like a truncated file
  161. warning('load_AxIS_spike:headerTruncated', ...
  162. 'Spike header truncated at end of file');
  163. break;
  164. end
  165. % Read the data for this spike
  166. fCurrentSpike.waveform = fread(aSourceData.FileID, aData.header.numSamplesPerBlock, 'int16=>int16');
  167. if length(fCurrentSpike.waveform) < aData.header.numSamplesPerBlock
  168. % file ended in the middle of the data
  169. warning('load_AxIS_spike:dataTruncated', ...
  170. 'Spike data truncated at end of file');
  171. break;
  172. end
  173. % Does this spike match the channel filter? If not, skip it.
  174. if ~ismember(fCurrentSpike.channelArrayIndex, aData.loadedChannels)
  175. continue;
  176. end
  177. % Does this spike match the time filter?
  178. if fCurrentSpike.startingSample < fFirstSample
  179. % Too early
  180. continue;
  181. elseif fCurrentSpike.startingSample >= fLastSample
  182. % Too late. Since spikes are in chronological order, we
  183. % can stop reading now.
  184. break;
  185. end
  186. % Check for capacity in the spike array
  187. if spikeIndex > fSpikeArrayCapacity
  188. if fSpikeArrayCapacity == 0
  189. % Let it grow on its own
  190. fSpikeArrayCapacity = 1;
  191. else
  192. % Double the capacity
  193. fSpikeArrayCapacity = fSpikeArrayCapacity * 2;
  194. aData.spikes(fSpikeArrayCapacity).startingSample = -1;
  195. end
  196. end
  197. % save the spike
  198. aData.spikes(spikeIndex) = fCurrentSpike;
  199. spikeIndex = spikeIndex + 1;
  200. end
  201. % trim down the spike array
  202. if spikeIndex > 1
  203. aData.spikes = aData.spikes(1:spikeIndex-1);
  204. else
  205. aData.spikes = [];
  206. end
  207. end
  208. end
  209. end