BlockVectorSet.m 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. classdef BlockVectorSet < handle
  2. %BLOCKVECTORSET is a grouping of data and metadata for a series of data contained within an AxisFile.
  3. % this class is composed of 4 major parts:
  4. %
  5. % ChannelArray: The channel array (See ChannelArray.m) is a listing of
  6. % all of the channels that were recorded in this
  7. % loaded set.
  8. %
  9. % Header: The header of the data (See BlockVectorHeader.m) contains the basic infomation
  10. % that is used in loading and using the data in this
  11. % set (e.g. Sampling Frequency, Voltage Scale, etc...)
  12. %
  13. % HeaderExtension: The header extension (See BlockVectorHeaderExtension.m)
  14. % contains metadata about the data capture / Reprocessing.
  15. %
  16. % Data: The data in this file (See BlockVectorData.m)
  17. % contains the methods for loading the sample data
  18. % from this set.
  19. properties (SetAccess = private, GetAccess = private)
  20. sourceFile
  21. end
  22. properties(GetAccess = public, SetAccess = private)
  23. ChannelArray
  24. Header
  25. HeaderExtension
  26. Data
  27. end
  28. methods
  29. function this = BlockVectorSet(varargin)
  30. this.setval(varargin{:});
  31. end
  32. function clone = Clone(this, varargin)
  33. clone = BlockVectorSet();
  34. if(isa(this.ChannelArray,'ChannelArray'))
  35. clone.ChannelArray = this.ChannelArray;
  36. end
  37. if(isa(this.Header,'BlockVectorHeader'))
  38. clone.Header = this.Header;
  39. end
  40. if(isa(this.HeaderExtension,'BlockVectorHeaderExtension'))
  41. clone.HeaderExtension = this.HeaderExtension;
  42. end
  43. if(isa(this.Data,'BlockVectorData'))
  44. clone.Data = this.Data;
  45. end
  46. if(isa(this.sourceFile,'AxisFile'))
  47. clone.sourceFile = this.sourceFile;
  48. end
  49. clone.setval(varargin{:});
  50. end
  51. end
  52. methods(Access = private)
  53. function setval(this, varargin)
  54. for i = 1:length(varargin)
  55. arg = varargin{i};
  56. if(isa(arg,'ChannelArray'))
  57. this.ChannelArray = arg;
  58. elseif(isa(arg,'BlockVectorHeader'))
  59. this.Header = arg;
  60. elseif(isa(arg,'BlockVectorHeaderExtension'))
  61. this.HeaderExtension = arg;
  62. elseif(isa(arg,'BlockVectorData'))
  63. this.Data = arg;
  64. elseif(isa(arg,'AxisFile'))
  65. this.sourceFile = arg;
  66. else
  67. error('Unknown member type');
  68. end
  69. end
  70. end
  71. end
  72. methods (Access = public)
  73. function aData = LoadData(this, varargin)
  74. % LoadData loads a Dataset, creaing a data structure similar
  75. % to the one created by load_AxIS_file (Deprecated)
  76. %
  77. % Legal forms:
  78. % data = LoadData();
  79. % data = LoadData(well);
  80. % data = LoadData(electrode);
  81. % data = LoadData(well, electrode);
  82. % data = LoadData(timespan);
  83. % data = LoadData(well, timespan);
  84. % data = LoadData(electrode, timespan);
  85. % data = LoadData(well, electrode, timespan);
  86. % data = LoadData(dimensions);
  87. % data = LoadData(well, dimensions);
  88. % data = LoadData(electrode, dimensions);
  89. % data = LoadData(well, electrode, dimensions);
  90. % data = LoadData(timespan, dimensions);
  91. % data = LoadData(well, timespan, dimensions);
  92. % data = LoadData(electrode, timespan, dimensions);
  93. % data = LoadData(well, electrode, timespan, dimensions);
  94. %
  95. % Optional arguments:
  96. % well String listing which wells (in a multiwell file) to load.
  97. % Format is a comma-delimited string with whitespace ignored, e.g.
  98. % 'A1, B2,C3' limits the data loaded to wells A1, B2, and C3.
  99. % Also acceptable: 'all' to load all wells.
  100. % If this parameter is omitted, all wells are loaded.
  101. % For a single-well file, this parameter is ignored.
  102. %
  103. % electrode Which electrodes to load. Format is either a comma-delimited string
  104. % with whitespace ignored (e.g. '11, 22,33') or a single channel number;
  105. % that is, a number, not part of a string.
  106. % Also acceptable: 'all' to load all channels and 'none', '-1', or -1
  107. % to load no data (returns only header information).
  108. % If this parameter is omitted, all channels are loaded.
  109. %
  110. % timespan Span of time, in seconds, over which to load data. Format is a two-element
  111. % array, [t0 t1], where t0 is the start time and t1 is the end time and both
  112. % are in seconds after the first sample in the file. Samples returned are ones
  113. % that were taken at time >= t0 and <= t1. The beginning of the file
  114. % is at 0 seconds.
  115. % If this parameter is omitted, the data is not filtered based on time.
  116. %
  117. % dimensions Preferred number of dimensions to report the waveforms in.
  118. % Value must be a whole number scalar: 1, 3, or 5 (Other values are ignored):
  119. %
  120. % dimensions = 1 -> ByPlate: returns a vector of Waveform objects, 1 Waveform
  121. % per signal in the plate
  122. %
  123. % dimensions = 3 -> ByWell: Cell Array of vectors of waveform 1 Waveform per signal
  124. % in the electrode with size (well Rows) x (well Columns)
  125. %
  126. % dimensions = 5 -> ByElectrode: Cell Array of vectors of waveform 1 Waveform per .
  127. % signal in the electrode with size (well Rows) x (well Columns) x
  128. % (electrode Columns) x (electrode Rows)
  129. %
  130. % NOTE: The default loading dimensions for
  131. % continous raw data is 5 and the default for
  132. % spike data is 3.
  133. fLoadArgs = LoadArgs(varargin);
  134. fTargetWell = fLoadArgs.Well;
  135. fTargetElectrode = fLoadArgs.Electrode;
  136. fChannelsToLoad = BlockVectorSet.get_channels_to_load(this.ChannelArray, fTargetWell, fTargetElectrode);
  137. if(isempty(this.HeaderExtension))
  138. fDataType = this.sourceFile.PrimaryDataType;
  139. else
  140. fDataType = this.HeaderExtension.DataType;
  141. end
  142. if(fDataType == BlockVectorDataType.Raw_v1)
  143. fDimensions = fLoadArgs.Dimensions;
  144. if(isempty(fDimensions))
  145. fDimensions = LoadArgs.ByElectrodeDimensions;
  146. end
  147. aData = this.Data.GetRawV1ContinuousWaveforms( ...
  148. this, ...
  149. fChannelsToLoad, ...
  150. fLoadArgs.Timespan, ...
  151. fDimensions);
  152. elseif(fDataType == BlockVectorDataType.Spike_v1)
  153. fDimensions = fLoadArgs.Dimensions;
  154. if(isempty(fDimensions))
  155. fDimensions = LoadArgs.ByElectrodeDimensions;
  156. end
  157. aData = this.Data.GetSpikeV1Waveforms( ...
  158. this, ...
  159. fChannelsToLoad, ...
  160. fLoadArgs.Timespan, ...
  161. fDimensions);
  162. end
  163. end
  164. function aData = load_as_legacy_struct(this, varargin)
  165. % load_AsLegacyStruct loads a dataset, creating a data structure similar
  166. % to the one created by load_AxIS_file (Deprecated)
  167. %
  168. % Legal forms:
  169. % data = load_as_legacy_struct();
  170. % data = load_as_legacy_struct(well);
  171. % data = load_as_legacy_struct(electrode);
  172. % data = load_as_legacy_struct(well, electrode);
  173. % data = load_as_legacy_struct(timespan);
  174. % data = load_as_legacy_struct(well, timespan);
  175. % data = load_as_legacy_struct(electrode, timespan);
  176. % data = load_as_legacy_struct(well, electrode, timespan);
  177. %
  178. %
  179. % Optional arguments:
  180. % well String listing which wells (in a multiwell file) to load.
  181. % Format is a comma-delimited string with whitespace ignored, e.g.
  182. % 'A1, B2,C3' limits the data loaded to wells A1, B2, and C3.
  183. % Also acceptable: 'all' to load all wells.
  184. % If this parameter is omitted, all wells are loaded.
  185. % For a single-well file, this parameter is ignored.
  186. %
  187. % electrode Which electrodes to load. Format is either a comma-delimited string
  188. % with whitespace ignored (e.g. '11, 22,33') or a single channel number;
  189. % that is, a number, not part of a string.
  190. % Also acceptable: 'all' to load all channels and 'none', '-1', or -1
  191. % to load no data (returns only header information).
  192. % If this parameter is omitted, all channels are loaded.
  193. %
  194. % timespan Span of time, in seconds, over which to load data. Format is a two-element
  195. % array, [t0 t1], where t0 is the start time and t1 is the end time and both
  196. % are in seconds after the first sample in the file. Samples returned are ones
  197. % that were taken at time >= t0 and <= t1. The beginning of the file
  198. % is at 0 seconds.
  199. % If this parameter is omitted, the data is not filtered based on time.
  200. %
  201. fLoadArgs = LoadArgs(varargin{:});
  202. fTargetWell = fLoadArgs.Well;
  203. fTargetElectrode = fLoadArgs.Electrode;
  204. fTimeRange = fLoadArgs.Timespan;
  205. aData = [];
  206. aData.header = [];
  207. %Check for Required ource file
  208. fSourceFile = this.sourceFile;
  209. if(~isa(fSourceFile,'AxisFile'))
  210. error('BlockVectorSet: Source file not specified');
  211. end
  212. %Check for optional BlockVectorHeaderExtension and Notes
  213. if(~isempty(fSourceFile.Notes))
  214. [~,idx]=max([fSourceFile.Notes.Revision]);
  215. fNotes = fSourceFile.Notes(idx);
  216. aData.notes = [];
  217. aData.notes.investigator = fNotes.Investigator;
  218. aData.notes.experimentId = fNotes.ExperimentID;
  219. aData.notes.notes = fNotes.Description;
  220. end
  221. aData.header.fileTypeNumber = fSourceFile.PrimaryDataType; %Default incase we don't have a header extension
  222. aData.header.headerVersionMajor = fSourceFile.HeaderVersionMajor;
  223. aData.header.headerVersionMinor = fSourceFile.HeaderVersionMinor;
  224. %Check for Required Entries
  225. fChannelArray = this.ChannelArray;
  226. if(~isa(fChannelArray,'ChannelArray'))
  227. error('BlockVectorSet: Channel array was not specified for this file.');
  228. end
  229. fHeader = this.Header;
  230. if(~isa(fHeader,'BlockVectorHeader'))
  231. error('BlockVectorSet: BlockVectorHeader was not specified for this file.');
  232. end
  233. fData = this.Data;
  234. if(~isa(fData,'BlockVectorData'))
  235. error('BlockVectorSet: No Data found');
  236. end
  237. %Check for optional BlockVectorHeaderExtension
  238. fHeaderExtension = this.HeaderExtension;
  239. if(isa(fHeaderExtension,'BlockVectorHeaderExtension'))
  240. aData.header.fileTypeNumber = fHeaderExtension.DataType;
  241. end
  242. %Handle Channel Array Data
  243. aData.channelArray = [];
  244. aData.channelArray.plateType = fChannelArray.PlateType;
  245. aData.channelArray.numChannels = length(fChannelArray.Channels);
  246. aData.channelArray.channel = [];
  247. for i = 1:aData.channelArray.numChannels
  248. aData.channelArray.channel(i).wellColumn = fChannelArray.Channels(i).WellColumn;
  249. aData.channelArray.channel(i).wellRow = fChannelArray.Channels(i).WellRow;
  250. aData.channelArray.channel(i).electrodeColumn = fChannelArray.Channels(i).ElectrodeColumn;
  251. aData.channelArray.channel(i).electrodeRow = fChannelArray.Channels(i).ElectrodeRow;
  252. aData.channelArray.channel(i).channelAchk = fChannelArray.Channels(i).ChannelAchk;
  253. aData.channelArray.channel(i).channelIndex = fChannelArray.Channels(i).ChannelIndex;
  254. aData.channelArray.channel(i).auxData = fChannelArray.Channels(i).AuxData;
  255. end
  256. %Handle BlockVectorHeaders Data
  257. aData.samplingFrequency = fHeader.SamplingFrequency;
  258. aData.voltageScale = fHeader.VoltageScale;
  259. aData.header.numChannelsPerBlock = fHeader.NumChannelsPerBlock;
  260. aData.header.numSamplesPerBlock = fHeader.NumSamplesPerBlock;
  261. aData.header.blockHeaderSize = fHeader.BlockHeaderSize;
  262. if(~isempty(fHeader.FileStartTime))
  263. aData.fileStartTime = [];
  264. aData.fileStartTime.year = fHeader.FileStartTime.Year;
  265. aData.fileStartTime.month = fHeader.FileStartTime.Month;
  266. aData.fileStartTime.day = fHeader.FileStartTime.Day;
  267. aData.fileStartTime.hour = fHeader.FileStartTime.Hour;
  268. aData.fileStartTime.minute = fHeader.FileStartTime.Minute;
  269. aData.fileStartTime.second = fHeader.FileStartTime.Second;
  270. aData.fileStartTime.millisecond = fHeader.FileStartTime.Millisecond;
  271. end
  272. if(~isempty(fHeader.ExperimentStartTime))
  273. aData.experimentStartTime = [];
  274. aData.experimentStartTime.year = fHeader.ExperimentStartTime.Year;
  275. aData.experimentStartTime.month = fHeader.ExperimentStartTime.Month;
  276. aData.experimentStartTime.day = fHeader.ExperimentStartTime.Day;
  277. aData.experimentStartTime.hour = fHeader.ExperimentStartTime.Hour;
  278. aData.experimentStartTime.minute = fHeader.ExperimentStartTime.Minute;
  279. aData.experimentStartTime.second = fHeader.ExperimentStartTime.Second;
  280. aData.experimentStartTime.millisecond = fHeader.ExperimentStartTime.Millisecond;
  281. end
  282. fChannelsToLoad = BlockVectorSet.get_channels_to_load(fChannelArray, fTargetWell, fTargetElectrode);
  283. aData.loadedChannels = fChannelsToLoad;
  284. %Handle Data
  285. if aData.header.fileTypeNumber == BlockVectorDataType.Raw_v1
  286. aData.fileType = 'raw';
  287. aData = BlockVectorLegacyLoader.Legacy_Load_Raw_v1(fData, aData, fTimeRange);
  288. elseif aData.header.fileTypeNumber == BlockVectorDataType.Spike_v1
  289. aData.fileType = 'spike';
  290. aData = BlockVectorLegacyLoader.Legacy_Load_spike_v1(fData, aData, fTimeRange);
  291. else
  292. error('BlockVectorSet: Unsupported file type number %u', aData.header.fileTypeNumber);
  293. end
  294. aData = rmfield(aData, 'header');
  295. end
  296. end
  297. methods(Static, Access = private)
  298. function ChannelListOut = get_channels_to_load(aChannelArray, aTargetWells, aTargetElectrodes)
  299. % Decode the aTargetWells string
  300. if strcmp(aTargetWells, 'all')
  301. % User has requested all wells - figure out what those
  302. % are from the channel array
  303. fTargetWells = BlockVectorSet.all_wells_electrodes([aChannelArray.Channels.WellColumn], ...
  304. [aChannelArray.Channels.WellRow]);
  305. else
  306. fTargetWells = aTargetWells;
  307. end
  308. % Decode the aTargetElectrodes string
  309. if strcmp(aTargetElectrodes, 'all')
  310. % User has requested all electrodes - figure out what those
  311. % are from the channel array
  312. fTargetElectrodes = BlockVectorSet.all_wells_electrodes([aChannelArray.Channels.ElectrodeColumn], ...
  313. [aChannelArray.Channels.ElectrodeRow]);
  314. elseif strcmp(aTargetElectrodes, 'none')
  315. % User has requested no electrodes
  316. fTargetElectrodes = [];
  317. else
  318. fTargetElectrodes = aTargetElectrodes;
  319. end
  320. ChannelListOut = zeros(1, size(fTargetWells, 1) * size(fTargetElectrodes, 1));
  321. if ~isempty(ChannelListOut)
  322. for fChannelArrayIndex = 1:length(aChannelArray.Channels)
  323. fCurrentChannel = aChannelArray.Channels(fChannelArrayIndex);
  324. [fFoundWell, fIdxWell] = ismember( [fCurrentChannel.WellColumn fCurrentChannel.WellRow], ...
  325. fTargetWells, 'rows');
  326. if ~any(fFoundWell)
  327. continue;
  328. end
  329. [fFoundElectrode, fIdxElectrode ] = ismember( [fCurrentChannel.ElectrodeColumn fCurrentChannel.ElectrodeRow], ...
  330. fTargetElectrodes, 'rows');
  331. if ~any(fFoundElectrode)
  332. continue;
  333. end
  334. ChannelListOut( (fIdxWell - 1) * size(fTargetElectrodes, 1) + fIdxElectrode ) = fChannelArrayIndex;
  335. end
  336. % Notify the user of any requested channels that weren't found in the channel array.
  337. % This is not necessarily an error; for example, if a whole well is requested, and
  338. % some channels in that well weren't recorded, we should return the well without
  339. % the "missing" channel.
  340. fChannelIdxZeros = find(ChannelListOut == 0);
  341. for i=1:length(fChannelIdxZeros)
  342. fIdxNotFound = fChannelIdxZeros(i);
  343. fMissingWell = floor((fIdxNotFound-1) / size(fTargetElectrodes, 1)) + 1;
  344. fMissingElectrode = mod(fIdxNotFound-1, size(fTargetElectrodes, 1)) + 1;
  345. warning('get_channels_to_load:invalidWellElectrode', ...
  346. sprintf('Well/electrode %d %d / %d %d not recorded in file', ...
  347. fTargetWells(fMissingWell, 1), fTargetWells(fMissingWell, 2), ...
  348. fTargetElectrodes(fMissingElectrode, 1), fTargetElectrodes(fMissingElectrode, 2)));
  349. end
  350. % Strip out any zeros from aChannelListOut, because these correspond to channels that weren't in
  351. % the loaded channel array, and therefore won't be loaded.
  352. ChannelListOut = ChannelListOut( ChannelListOut ~= 0 );
  353. end
  354. end % end function
  355. % Subfunction to expand an 'all' well or electrode list
  356. function fOutput = all_wells_electrodes(aColumns, aRows)
  357. fOutput = [];
  358. aColumns = unique(aColumns); % sort ascending and dedup
  359. aRows = unique(aRows);
  360. for fiRow = 1:length(aRows)
  361. for fiCol = 1:length(aColumns)
  362. fOutput = [ fOutput ; aColumns(fiCol) aRows(fiRow) ];
  363. end
  364. end
  365. end
  366. % Subfunction to help with channel array search
  367. function aMatch = match_well_electrode(aChannelStruct, aWellElectrode)
  368. if aChannelStruct.wellColumn == aWellElectrode(1) && ...
  369. aChannelStruct.wellRow == aWellElectrode(2) && ...
  370. aChannelStruct.electrodeColumn == aWellElectrode(3) && ...
  371. aChannelStruct.electrodeRow == aWellElectrode(4)
  372. aMatch = 1;
  373. else
  374. aMatch = 0;
  375. end
  376. end
  377. end
  378. end