AxisFile.m 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. classdef AxisFile < handle
  2. %AXISFILE: Class that holds all Data for a loaded Axion 1.X File
  3. %
  4. % Calling A = AxisFile('filename.raw') or AxisFile('filename.spk') will
  5. % create an AxisFile object which contains the data set corresponding
  6. % to file 'y', as well as the notes and file name.
  7. %
  8. % A = AxisFile('filename.raw') - loads file header information
  9. %
  10. % From here, the command 'z = A.DataSets(1)' stores a copy of the handle
  11. % to the first "BlockVectorSet" object in the file, which acts as a data
  12. % map for the user.
  13. %
  14. % y = z.LoadData() of z, loads specific data regions of the dataset
  15. % to y without reloading the entire file each time. For example:
  16. %
  17. % a1data = z.LoadData('A1', 11)
  18. %
  19. % will load all A1_11 information in the file and store it in a1Data.
  20. %
  21. % y = x.DataSets.LoadData(well, electrode, timespan, dimensions) - loads
  22. % channel data into array
  23. %
  24. % dimension 1 -> by plate, vector with 1 waveform per signal in
  25. % the plate
  26. % dimension 3 -> by well, cell array of vectors 1 waveform per
  27. % signal with size (well rows, well columns)
  28. % dimension 5 -> by electrode, reference electrodes by y{well row,
  29. % well column, electrode column, electrode row}
  30. %
  31. % The LoadData() function loads data into instances of the class
  32. % 'Waveform', where the voltage and time vector data can be accessed via
  33. % the methods GetTimeVector() and GetVoltageVector(). See Also: Waveform.m
  34. %
  35. % y{wr, wc, ec, er}.GetTimeVector - returns time vector based on start
  36. % time, length of data, and Sampling Frequency
  37. % in units of Seconds
  38. % y{wr, wc, ec, er}.GetVoltageVector - returns the voltage vector
  39. % with units of Volts
  40. %
  41. % Properties of AxisFile include:
  42. %
  43. % FileName: Path to file that this instance points to
  44. %
  45. % PrimaryDataType: The type of the Original / most relevant data
  46. % in this file. See Also: BlockVectorDataType.m
  47. %
  48. % HeaderVersionMajor: Major version number of the loaded file.
  49. % (modern files should be 1)
  50. %
  51. % HeaderVersionMinor: Minor version number of the loaded file.
  52. %
  53. % Notes: User Notes on this file See Also: Note.m
  54. %
  55. % DataSets Objects with access to the data and meta-data
  56. % contained by this file See Also: BlockVectorSet.m
  57. %
  58. % See Also: BlockVectorSet, Waveform, BlockVectorSet,
  59. % BlockVectorDataType, Note
  60. properties (Constant = true, GetAccess = private)
  61. %The following are contants used in the opening of Axis Files
  62. MAGIC_WORD = 'AxionBio'; % Preface to all modern Axis files
  63. MAGIC_BETA_FILE = 64; % Preface to Some legacy Axis files
  64. EXPECTED_NOTES_LENGTH_FIELD = 600; % Number used as a validity check in Axis 1.0 file headers
  65. %Header CRC32 calculation constants
  66. CRC_POLYNOMIAL = hex2dec('edb88320');
  67. CRC_SEED = hex2dec('ffffffff');
  68. %Header Size Constants, see documentation for details
  69. PRIMARY_HEADER_CRCSIZE = 1018;
  70. SUBHEADER_CRCSIZE = 1016;
  71. PRIMARY_HEADER_MAXENTRIES = 123;
  72. SUBHEADER_MAXENTRIES = 126;
  73. end
  74. properties (SetAccess = private, GetAccess = private)
  75. FileID; % File Handle for file Access (fread, ftell, etc...)
  76. NotesStart; % Location in file (in Number of bytes from the beginning) of the primary notes field
  77. EntriesStart; % Staring byte of file entries
  78. end
  79. properties (SetAccess = private, GetAccess = public)
  80. %Basic File Data
  81. FileName;
  82. PrimaryDataType;
  83. %Version Number: Current version is (1.0)
  84. HeaderVersionMajor;
  85. HeaderVersionMinor;
  86. %Contained File Data
  87. Notes;
  88. DataSets;
  89. end
  90. methods
  91. function this = AxisFile(varargin)
  92. %AxisFile Opens a new handle to an Axis File
  93. % Required arguments:
  94. % filename Pathname of the file to load
  95. % Note: Calling with mulitple file names results
  96. % in vector of AxisFile objects corresponding to
  97. % the input argument file names
  98. if(nargin == 0)
  99. this.FileID = [];
  100. return;
  101. end
  102. if(nargin > 1)
  103. this(nargin) = AxisFile;
  104. for i = 1: nargin
  105. this(i) = AxisFile(varargin{i});
  106. end
  107. return;
  108. end
  109. fFilename = varargin{1};
  110. this.FileName = fFilename;
  111. this.FileID = fopen(fFilename,'r');
  112. fSetMap = containers.Map('KeyType', 'int64', 'ValueType', 'any');
  113. this.Notes = Note.empty(0,0);
  114. if (this.FileID <= 0)
  115. error(['AxisFile: ' this.FileName ' not found.']);
  116. end
  117. % Make sure that this is a format that we understand
  118. versionOk = false;
  119. versionWarn = false;
  120. % Check for the "magic word" sequence
  121. fMagicRead = fread(this.FileID, length(AxisFile.MAGIC_WORD), '*char').';
  122. if ~strcmp(AxisFile.MAGIC_WORD, fMagicRead)
  123. % Magic phrase not found -- check to see if this is an old-style file
  124. if ~isempty(fMagicRead) && uint8(fMagicRead(1)) == AxisFile.MAGIC_BETA_FILE
  125. % This looks like a deprecated beta file
  126. warning('AxisFile:versionCheck', ['File ' fFilename ' looks like a deprecated AxIS v0.0 file format, Please Re-record it in Axis to update the header data']);
  127. [fType, fData, fChannelMapping, fHeader] = LegacySupport.GenerateRolstonEntries(this.FileID, fFilename((end-3):end));
  128. this.HeaderVersionMajor = 0;
  129. this.HeaderVersionMinor = 0;
  130. this.PrimaryDataType = fType;
  131. this.DataSets = BlockVectorSet(this, fData, fChannelMapping, fHeader);
  132. return;
  133. else
  134. fclose(this.FileID);
  135. error('File format not recognized: %s', fFilename);
  136. end
  137. else
  138. this.PrimaryDataType = fread(this.FileID, 1, 'uint16=>uint16');
  139. this.HeaderVersionMajor = fread(this.FileID, 1, 'uint16=>uint16');
  140. this.HeaderVersionMinor = fread(this.FileID, 1, 'uint16=>uint16');
  141. this.NotesStart = fread(this.FileID, 1, 'uint64=>uint64');
  142. fNotesLength = fread(this.FileID, 1, 'uint32=>uint32');
  143. if(fNotesLength ~= AxisFile.EXPECTED_NOTES_LENGTH_FIELD)
  144. error('Incorrect legacy notes length field');
  145. end
  146. if this.HeaderVersionMajor == 0
  147. if this.HeaderVersionMinor == 1
  148. versionOk = true;
  149. elseif this.HeaderVersionMinor == 2
  150. versionOk = true;
  151. end
  152. this.EntriesStart = int64(this.NotesStart);
  153. fEntryRecords = LegacySupport.GenerateEntries(this.FileID, this.EntriesStart);
  154. elseif this.HeaderVersionMajor == 1
  155. versionOk = true;
  156. this.EntriesStart = fread(this.FileID, 1, 'int64=>int64');
  157. fEntrySlots = fread(this.FileID, AxisFile.PRIMARY_HEADER_MAXENTRIES, 'uint64=>uint64');
  158. fEntryRecords = EntryRecord.FromUint64(fEntrySlots);
  159. % Check CRC
  160. fseek(this.FileID, 0, 'bof');
  161. fCRCBytes = fread(this.FileID, AxisFile.PRIMARY_HEADER_CRCSIZE, 'uint8');
  162. fReadCRC = fread(this.FileID, 1, 'uint32');
  163. fCalcCRC = CRC32(AxisFile.CRC_POLYNOMIAL, AxisFile.CRC_SEED).Compute(fCRCBytes);
  164. if(fReadCRC ~= fCalcCRC)
  165. error('File header checksum was incorrect: %s', fFilename);
  166. end
  167. if this.HeaderVersionMinor > 0
  168. versionWarn = true;
  169. end
  170. end
  171. end
  172. if ~versionOk
  173. error('Unsupported file version %u.%u', ...
  174. this.HeaderVersionMajor, ...
  175. this.HeaderVersionMinor);
  176. end
  177. % Version > 1.0 triggers a warning.
  178. if versionWarn
  179. warning(...
  180. 'AxisFile:versionCheck', ...
  181. 'Unsupported file version %u.%u , Not all data for this may be shown, Please contact support@axion-biosystems.com for more information', ...
  182. this.HeaderVersionMajor, ...
  183. this.HeaderVersionMinor);
  184. end
  185. % Start Reading Entries
  186. fseek(this.FileID, this.EntriesStart, 'bof');
  187. fTerminated = false;
  188. % Load file entries from the header
  189. while(~fTerminated)
  190. for entryRecord = fEntryRecords
  191. switch(entryRecord.Type)
  192. case EntryRecordID.Terminate
  193. fTerminated = true;
  194. break
  195. case EntryRecordID.ChannelArray
  196. fChannelArray = ChannelArray(entryRecord, this.FileID);
  197. if(~isa(fCurrentBlockVectorSet.ChannelArray , 'ChannelArray'))
  198. fCurrentBlockVectorSet = fCurrentBlockVectorSet.Clone(fChannelArray);
  199. fSetMap(int64(fCurrentHeader.FirstBlock)) = fCurrentBlockVectorSet;
  200. else
  201. error('AxisFile: Only one ChannelArray per BlockVectorSet');
  202. end
  203. case EntryRecordID.BlockVectorHeader
  204. fCurrentHeader = BlockVectorHeader(entryRecord, this.FileID);
  205. fCurrentBlockVectorSet = BlockVectorSet(this, fCurrentHeader);
  206. fKey = int64(fCurrentHeader.FirstBlock);
  207. fSetMap(fKey) = fCurrentBlockVectorSet;
  208. case EntryRecordID.BlockVectorHeaderExtension
  209. if(~isempty(fCurrentBlockVectorSet.HeaderExtension) || ...
  210. isa(fCurrentBlockVectorSet.HeaderExtension, 'BlockVectorHeaderExtension'))
  211. error('AxisFile: Only one BlockVectorHeaderExtension per BlockVectorSet');
  212. end
  213. fCurrentBlockVectorSet = fCurrentBlockVectorSet.Clone(BlockVectorHeaderExtension(entryRecord, this.FileID));
  214. fSetMap(fCurrentBlockVectorSet.Header.FirstBlock) = fCurrentBlockVectorSet;
  215. case EntryRecordID.BlockVectorData
  216. fData = BlockVectorData(entryRecord, this.FileID);
  217. if(~isempty(fCurrentBlockVectorSet.Data) || ...
  218. isa(fCurrentBlockVectorSet.Data, 'BlockVectorData'))
  219. error('AxisFile: Only one BlockVectorData per BlockVectorSet');
  220. end
  221. fTargetSet = fSetMap(int64(fData.Start));
  222. if (~isa(fTargetSet, 'BlockVectorSet'))
  223. error('AxisFile: No header to match to data');
  224. end
  225. fTargetSet = fTargetSet.Clone(fData);
  226. fSetMap(fData.Start) = fTargetSet;
  227. case EntryRecordID.NotesArray
  228. this.Notes = [this.Notes ; Note.ParseArray(entryRecord, this.FileID)];
  229. otherwise
  230. fSkipSpace = double(entryRecord.Length);
  231. if(0 ~= fseek(this.FileID, fSkipSpace, 'cof'))
  232. error(ferror(this.FileID));
  233. end
  234. end
  235. end
  236. if(~fTerminated)
  237. %Check Magic Bytes
  238. fMagicRead = fread(this.FileID, length(AxisFile.MAGIC_WORD), '*char').';
  239. if ~strcmp(AxisFile.MAGIC_WORD, fMagicRead)
  240. error('Bad sub header magic numbers: %s', fFilename);
  241. end
  242. %Read Entry Records
  243. fEntrySlots = fread(this.FileID, AxisFile.SUBHEADER_MAXENTRIES, 'uint64=>uint64');
  244. fEntryRecords = EntryRecord.FromUint64(fEntrySlots);
  245. %Check CRC of subheader
  246. fseek(this.FileID,( -1 * length(AxisFile.MAGIC_WORD)) - (8 * AxisFile.SUBHEADER_MAXENTRIES),'cof');
  247. fCRCBytes = fread(this.FileID, AxisFile.SUBHEADER_CRCSIZE, 'uint8');
  248. fReadCRC = fread(this.FileID, 1, 'uint32');
  249. fCalcCRC = CRC32(AxisFile.CRC_POLYNOMIAL, AxisFile.CRC_SEED).Compute(fCRCBytes);
  250. if(fReadCRC ~= fCalcCRC)
  251. error('Bad sub header checksum : %s', fFilename);
  252. end
  253. %skip 4 reserved bytes
  254. fseek(this.FileID, 4,'cof');
  255. end
  256. end
  257. fValueSet = fSetMap.values;
  258. %Record Final Data Sets
  259. this.DataSets = BlockVectorSet.empty(0,length(fSetMap));
  260. for i = 1 : length(fValueSet)
  261. this.DataSets(i) = fValueSet{i};
  262. end
  263. %Sort Notes
  264. [~,idx]=sort([this.Notes.Revision]);
  265. this.Notes = this.Notes(idx);
  266. end
  267. function delete(this)
  268. %DELETE is the destructor for the class, ensures that the file
  269. %stream is closed as the file reference is cleared from the
  270. %workspace
  271. if ~isempty(this.FileID)
  272. fclose(this.FileID);
  273. end
  274. end
  275. end
  276. end