123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 |
- classdef AxisFile < handle
- %AXISFILE: Class that holds all Data for a loaded Axion 1.X File
- %
- % Calling A = AxisFile('filename.raw') or AxisFile('filename.spk') will
- % create an AxisFile object which contains the data set corresponding
- % to file 'y', as well as the notes and file name.
- %
- % A = AxisFile('filename.raw') - loads file header information
- %
- % From here, the command 'z = A.DataSets(1)' stores a copy of the handle
- % to the first "BlockVectorSet" object in the file, which acts as a data
- % map for the user.
- %
- % y = z.LoadData() of z, loads specific data regions of the dataset
- % to y without reloading the entire file each time. For example:
- %
- % a1data = z.LoadData('A1', 11)
- %
- % will load all A1_11 information in the file and store it in a1Data.
- %
- % y = x.DataSets.LoadData(well, electrode, timespan, dimensions) - loads
- % channel data into array
- %
- % dimension 1 -> by plate, vector with 1 waveform per signal in
- % the plate
- % dimension 3 -> by well, cell array of vectors 1 waveform per
- % signal with size (well rows, well columns)
- % dimension 5 -> by electrode, reference electrodes by y{well row,
- % well column, electrode column, electrode row}
- %
- % The LoadData() function loads data into instances of the class
- % 'Waveform', where the voltage and time vector data can be accessed via
- % the methods GetTimeVector() and GetVoltageVector(). See Also: Waveform.m
- %
- % y{wr, wc, ec, er}.GetTimeVector - returns time vector based on start
- % time, length of data, and Sampling Frequency
- % in units of Seconds
- % y{wr, wc, ec, er}.GetVoltageVector - returns the voltage vector
- % with units of Volts
- %
- % Properties of AxisFile include:
- %
- % FileName: Path to file that this instance points to
- %
- % PrimaryDataType: The type of the Original / most relevant data
- % in this file. See Also: BlockVectorDataType.m
- %
- % HeaderVersionMajor: Major version number of the loaded file.
- % (modern files should be 1)
- %
- % HeaderVersionMinor: Minor version number of the loaded file.
- %
- % Notes: User Notes on this file See Also: Note.m
- %
- % DataSets Objects with access to the data and meta-data
- % contained by this file See Also: BlockVectorSet.m
- %
- % See Also: BlockVectorSet, Waveform, BlockVectorSet,
- % BlockVectorDataType, Note
-
-
-
- properties (Constant = true, GetAccess = private)
- %The following are contants used in the opening of Axis Files
- MAGIC_WORD = 'AxionBio'; % Preface to all modern Axis files
- MAGIC_BETA_FILE = 64; % Preface to Some legacy Axis files
- EXPECTED_NOTES_LENGTH_FIELD = 600; % Number used as a validity check in Axis 1.0 file headers
-
- %Header CRC32 calculation constants
- CRC_POLYNOMIAL = hex2dec('edb88320');
- CRC_SEED = hex2dec('ffffffff');
-
- %Header Size Constants, see documentation for details
- PRIMARY_HEADER_CRCSIZE = 1018;
- SUBHEADER_CRCSIZE = 1016;
- PRIMARY_HEADER_MAXENTRIES = 123;
- SUBHEADER_MAXENTRIES = 126;
- end
-
- properties (SetAccess = private, GetAccess = private)
- FileID; % File Handle for file Access (fread, ftell, etc...)
- NotesStart; % Location in file (in Number of bytes from the beginning) of the primary notes field
- EntriesStart; % Staring byte of file entries
- end
-
- properties (SetAccess = private, GetAccess = public)
- %Basic File Data
- FileName;
- PrimaryDataType;
-
- %Version Number: Current version is (1.0)
- HeaderVersionMajor;
- HeaderVersionMinor;
-
- %Contained File Data
- Notes;
- DataSets;
- end
-
- methods
- function this = AxisFile(varargin)
- %AxisFile Opens a new handle to an Axis File
- % Required arguments:
- % filename Pathname of the file to load
- % Note: Calling with mulitple file names results
- % in vector of AxisFile objects corresponding to
- % the input argument file names
-
-
- if(nargin == 0)
- this.FileID = [];
- return;
- end
- if(nargin > 1)
- this(nargin) = AxisFile;
- for i = 1: nargin
- this(i) = AxisFile(varargin{i});
- end
- return;
- end
- fFilename = varargin{1};
- this.FileName = fFilename;
- this.FileID = fopen(fFilename,'r');
-
- fSetMap = containers.Map('KeyType', 'int64', 'ValueType', 'any');
- this.Notes = Note.empty(0,0);
-
- if (this.FileID <= 0)
- error(['AxisFile: ' this.FileName ' not found.']);
- end
- % Make sure that this is a format that we understand
- versionOk = false;
- versionWarn = false;
-
- % Check for the "magic word" sequence
- fMagicRead = fread(this.FileID, length(AxisFile.MAGIC_WORD), '*char').';
- if ~strcmp(AxisFile.MAGIC_WORD, fMagicRead)
-
- % Magic phrase not found -- check to see if this is an old-style file
- if ~isempty(fMagicRead) && uint8(fMagicRead(1)) == AxisFile.MAGIC_BETA_FILE
- % This looks like a deprecated beta file
- 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']);
-
- [fType, fData, fChannelMapping, fHeader] = LegacySupport.GenerateRolstonEntries(this.FileID, fFilename((end-3):end));
-
- this.HeaderVersionMajor = 0;
- this.HeaderVersionMinor = 0;
- this.PrimaryDataType = fType;
- this.DataSets = BlockVectorSet(this, fData, fChannelMapping, fHeader);
-
- return;
- else
- fclose(this.FileID);
- error('File format not recognized: %s', fFilename);
- end
-
- else
-
- this.PrimaryDataType = fread(this.FileID, 1, 'uint16=>uint16');
- this.HeaderVersionMajor = fread(this.FileID, 1, 'uint16=>uint16');
- this.HeaderVersionMinor = fread(this.FileID, 1, 'uint16=>uint16');
- this.NotesStart = fread(this.FileID, 1, 'uint64=>uint64');
- fNotesLength = fread(this.FileID, 1, 'uint32=>uint32');
-
- if(fNotesLength ~= AxisFile.EXPECTED_NOTES_LENGTH_FIELD)
- error('Incorrect legacy notes length field');
- end
-
- if this.HeaderVersionMajor == 0
- if this.HeaderVersionMinor == 1
- versionOk = true;
- elseif this.HeaderVersionMinor == 2
- versionOk = true;
- end
-
- this.EntriesStart = int64(this.NotesStart);
- fEntryRecords = LegacySupport.GenerateEntries(this.FileID, this.EntriesStart);
-
- elseif this.HeaderVersionMajor == 1
- versionOk = true;
-
- this.EntriesStart = fread(this.FileID, 1, 'int64=>int64');
- fEntrySlots = fread(this.FileID, AxisFile.PRIMARY_HEADER_MAXENTRIES, 'uint64=>uint64');
- fEntryRecords = EntryRecord.FromUint64(fEntrySlots);
-
- % Check CRC
- fseek(this.FileID, 0, 'bof');
- fCRCBytes = fread(this.FileID, AxisFile.PRIMARY_HEADER_CRCSIZE, 'uint8');
- fReadCRC = fread(this.FileID, 1, 'uint32');
- fCalcCRC = CRC32(AxisFile.CRC_POLYNOMIAL, AxisFile.CRC_SEED).Compute(fCRCBytes);
-
- if(fReadCRC ~= fCalcCRC)
- error('File header checksum was incorrect: %s', fFilename);
- end
-
- if this.HeaderVersionMinor > 0
- versionWarn = true;
- end
- end
-
- end
-
- if ~versionOk
- error('Unsupported file version %u.%u', ...
- this.HeaderVersionMajor, ...
- this.HeaderVersionMinor);
- end
-
- % Version > 1.0 triggers a warning.
- if versionWarn
- warning(...
- 'AxisFile:versionCheck', ...
- 'Unsupported file version %u.%u , Not all data for this may be shown, Please contact support@axion-biosystems.com for more information', ...
- this.HeaderVersionMajor, ...
- this.HeaderVersionMinor);
- end
- % Start Reading Entries
- fseek(this.FileID, this.EntriesStart, 'bof');
-
- fTerminated = false;
-
- % Load file entries from the header
- while(~fTerminated)
-
- for entryRecord = fEntryRecords
- switch(entryRecord.Type)
-
- case EntryRecordID.Terminate
- fTerminated = true;
- break
-
- case EntryRecordID.ChannelArray
- fChannelArray = ChannelArray(entryRecord, this.FileID);
- if(~isa(fCurrentBlockVectorSet.ChannelArray , 'ChannelArray'))
- fCurrentBlockVectorSet = fCurrentBlockVectorSet.Clone(fChannelArray);
- fSetMap(int64(fCurrentHeader.FirstBlock)) = fCurrentBlockVectorSet;
- else
- error('AxisFile: Only one ChannelArray per BlockVectorSet');
- end
-
- case EntryRecordID.BlockVectorHeader
- fCurrentHeader = BlockVectorHeader(entryRecord, this.FileID);
-
- fCurrentBlockVectorSet = BlockVectorSet(this, fCurrentHeader);
-
- fKey = int64(fCurrentHeader.FirstBlock);
- fSetMap(fKey) = fCurrentBlockVectorSet;
-
- case EntryRecordID.BlockVectorHeaderExtension
- if(~isempty(fCurrentBlockVectorSet.HeaderExtension) || ...
- isa(fCurrentBlockVectorSet.HeaderExtension, 'BlockVectorHeaderExtension'))
- error('AxisFile: Only one BlockVectorHeaderExtension per BlockVectorSet');
- end
- fCurrentBlockVectorSet = fCurrentBlockVectorSet.Clone(BlockVectorHeaderExtension(entryRecord, this.FileID));
- fSetMap(fCurrentBlockVectorSet.Header.FirstBlock) = fCurrentBlockVectorSet;
-
- case EntryRecordID.BlockVectorData
- fData = BlockVectorData(entryRecord, this.FileID);
- if(~isempty(fCurrentBlockVectorSet.Data) || ...
- isa(fCurrentBlockVectorSet.Data, 'BlockVectorData'))
- error('AxisFile: Only one BlockVectorData per BlockVectorSet');
- end
- fTargetSet = fSetMap(int64(fData.Start));
- if (~isa(fTargetSet, 'BlockVectorSet'))
- error('AxisFile: No header to match to data');
- end
- fTargetSet = fTargetSet.Clone(fData);
- fSetMap(fData.Start) = fTargetSet;
-
- case EntryRecordID.NotesArray
- this.Notes = [this.Notes ; Note.ParseArray(entryRecord, this.FileID)];
-
- otherwise
- fSkipSpace = double(entryRecord.Length);
- if(0 ~= fseek(this.FileID, fSkipSpace, 'cof'))
- error(ferror(this.FileID));
- end
-
- end
- end
-
- if(~fTerminated)
-
- %Check Magic Bytes
- fMagicRead = fread(this.FileID, length(AxisFile.MAGIC_WORD), '*char').';
- if ~strcmp(AxisFile.MAGIC_WORD, fMagicRead)
- error('Bad sub header magic numbers: %s', fFilename);
- end
-
- %Read Entry Records
- fEntrySlots = fread(this.FileID, AxisFile.SUBHEADER_MAXENTRIES, 'uint64=>uint64');
- fEntryRecords = EntryRecord.FromUint64(fEntrySlots);
-
- %Check CRC of subheader
- fseek(this.FileID,( -1 * length(AxisFile.MAGIC_WORD)) - (8 * AxisFile.SUBHEADER_MAXENTRIES),'cof');
- fCRCBytes = fread(this.FileID, AxisFile.SUBHEADER_CRCSIZE, 'uint8');
- fReadCRC = fread(this.FileID, 1, 'uint32');
- fCalcCRC = CRC32(AxisFile.CRC_POLYNOMIAL, AxisFile.CRC_SEED).Compute(fCRCBytes);
- if(fReadCRC ~= fCalcCRC)
- error('Bad sub header checksum : %s', fFilename);
- end
-
- %skip 4 reserved bytes
- fseek(this.FileID, 4,'cof');
- end
-
- end
-
- fValueSet = fSetMap.values;
-
- %Record Final Data Sets
- this.DataSets = BlockVectorSet.empty(0,length(fSetMap));
- for i = 1 : length(fValueSet)
- this.DataSets(i) = fValueSet{i};
- end
-
- %Sort Notes
- [~,idx]=sort([this.Notes.Revision]);
- this.Notes = this.Notes(idx);
-
- end
-
-
- function delete(this)
- %DELETE is the destructor for the class, ensures that the file
- %stream is closed as the file reference is cleared from the
- %workspace
-
- if ~isempty(this.FileID)
- fclose(this.FileID);
- end
- end
-
- end
-
- end
|