spiff: a spike interchange file format

18

Upload: independent

Post on 23-Nov-2023

0 views

Category:

Documents


0 download

TRANSCRIPT

SPIFF: A Spike Interchange File FormatMark Kvale, Steve Lisberger, Purvis Bedenbaugh, Frederic Theunissen,Michael Stryker, Serge Rebrik, Jim Wright, and Gaylord HolderSloan Center for Theoretical NeurobiologyDepartment of Physiology, Box 0444University of California, San Francisco513 Parnassus St., San Francisco, CA 94143June 2, 1997AbstractThis paper describes a speci�cation for a common data format amongall the Keck labs for the acquisition, exchange and analysis of neuralspike data. The proposed format, based loosely on the AIFF design, isself-describing, hierarchical and extensible.

1

1 IntroductionThe purpose of the paper is to describe a �le format , SPIFF, for the interchangeof experimental data within the Keck Center for Integrative Neurosciences atUCSF. SPIFF stands for the SPike Interchange File Format and designed specif-ically for e�cient representation of neural recordings and associated data gen-erated in an experiment.Neural recordings come in various types: intra-cellular or extra-cellularrecordings of single neurons, multi-unit recordings of ensembles of neurons, and�eld potentials. The resulting neural data for each probe can be represented inat least two di�erent ways. The most direct representation is as a raw voltageor current trace: a continuous signal x(t) is sampled at times ti; i = 0; : : : ; N togenerate a sequence of voltages or currents x(ti) � xi. Often, but not always,the sampling times ti are at equally spaced intervals ti � ti�1 = �t and in thiscase, there is a sampling rate r = (�t)�1. A second representation is possiblegenerated after the spikes or events are detected and sorted: the record is sim-ply an ordered list of time-stamps sk; k = 0; : : : ;K, marking when the spikesoccurred.Both these representations, and others that are derived from them (e.g., thesequence of ISI's) share several salient characteristics:� the sequences are one-dimensional� the sequences are temporally ordered� there may be multiple sequences; that is, the data may be multichannelIn these three features, neural data are closely related to audio signals. Therehas been much work done on creating portable audio formats for the exchange ofaudio data in a heterogeneous computing environment and across the Internet.One highly successful format is the Audio Interchange File Format (AIFF) [1].It has several desirable features:� it supports multiple channels of data� new types of data are easily incorporated into the format� A good deal of software already exists, and it is a proven format.In the following sections, a SPIFF based on the structural skeleton of the AIFFis described.In addition to the data of an experiment, there are the meta data of theexperiment: experimental design, initial conditions, and experimental parame-ters. An experimental design usually has a hierarchical structure, in which thewhole experiment splits into trials, each trial splits into subtrials, etc. Trialblocks, described in section 9, encode this hierarchical structure and organizethe SPIFF �le. SPIFF encodes initial conditions and experimental parame-ters via comment blocks, described in section 4 and a parameter dictionary/keysystem associated with trial blocks, described in sections 9 and 10.2

Three additional virtues of an interchange �le format are that 1) it is easyto read, 2) it is easy to write, and 3) it is easy to transport across machines andoperating systems. Easy to read means that the data is self-describing and canbe self-indexing, which is useful for random access and error checking. Easy towrite means that it should be streaming, i.e., one can append to a �le withoutconstantly modifying earlier parts of the �le or rewriting the whole �le. Easyto transport means that it should be easy to handle each of type of data in the�le, regardless of the machine the �le is being processed on. The �rst two pointswill be addressed in section 3, and the third point will be discussed in the nextsection.2 Data typesOne of the unfortunate aspects of computing in a heterogeneous environment isthe plethora of word sizes, byte orderings, etc. So it pays to be clear about thelow level data types. To be concrete, we will refer to C-style data types. Hereis a table of the types used in SPIFF:char 8 bits, signedshort 16 bits, signedlong 32 bits, signedcustom oat 64 bits, 2 longscustom time 64 bits, 2 longsThe �rst three data sizes are similar to those for C on the Macs, PCs, andSuns, but not for C on the 64 bit Alphas. It is up to the programmer to ensurethat the correct data sizes are used for each machine.Although standards have been created for representing real-valued quanti-ties, such as IEEE-754, they su�er from lack of portability across machines. Acustom oat has thus been created to represent real-valued quantities. It con-sists of a long integer containing the mantissa, and a long integer containingthe exponent. A custom oat can be thought of as expressing the number inscienti�c notation: See Appendix B for details.The custom time data type is used to record time stamps of events. For cer-tain experiments, the dynamic range of times recorded may exceed the capacityof a 32-bit quantity, so a 64-bit quantity has been created. The data type isa �xed decimal format, in which the high-order long represents the number ofseconds, and the low order word represents the number of nanoseconds since apredetermined reference time.In order to be able to transport �les from one machine to the next, one mustalso be able to specify, or as least detect, the byte ordering of the �le relative tothe machine. Because the format is designed to be used for acquisition purposes,byte-swapping is deemed to be too expensive to do at write time. Thus, a SPIFF�le will be written out in the endian format native to the machine. When a3

�le is read, the byte-ordering will be detected by examining the IDs in eachblock (blocks will be de�ned later). If the characters are in the right order, nobyte-swapping is needed, but if they are in the wrong order, they will be needto be swapped.3 Overall �le formatEach �le is made up of a number of blocks. Each block consists of headerinformation followed by data:typedef struct {char blockID[4];long blockSize;other header data ...char blockData[blockSize - headerSize];} Generic_hunk;blockID identi�es the data portion of the block. A program will know howto interpret the data by examining the blockID. To facilitate error-checking,the ID should be four uppercase ASCII characters.blockSize is the size in bytes of the whole block, header and data.blockData[blockSize - headerSize] contains the data stored in the block.Easy to read vs. easy to write issues come up with respect to the blockSizevariable. On one hand, having the size for each block would be nice for errorchecking, memory allocation, etc. On the other hand, if one is writing a �le inthe streaming mode, one does not know the size ahead of time. One possibleway to resolve this con ict is to have it both ways! Set blockSize to be -1when writing to denote an unknown size for the data. After one is �nishedwriting data to the �le, then one can go back (using a seek) and insert the trueblockSize > 0. If it is not possible to seek back to the beginning of the blockimmediately after writing the data, one may alternatively keep the block sizesin memory and write them out in an index at the end(see section 12. At alater stage, one may then go back and update all the block sizes with the indexinformation.blockSize is the size in bytes of the whole block; it is set to -1 if the size isunknown.To further facilitate writing or appending blocks to a SPIFF �le, the headersfor each block should be �xed in length. With a �xed length header one alwaysknows where the data portion of the block begins. To update sizes and indices,one only need update that block of the �le which contains the header; the otherblocks are untouched.Each SPIFF �le is a collection of di�erent types of blocks.� The Comment block contains general information about the experimentand data. 4

� The Regular Trace block consists of neural trace sampled at regular inter-vals and associated rate information.� The Irregular Trace block consists of a sampled neural trace sequence andits associated sample times.� There Multi-electrode Trace block consists of multiple neural traces sam-pled at regular intervals and associated rate information.� The Time-stamp block contains a sequence of time stamps, along withtheir associated events.� The Dictionary block contains structured information about the parame-ters of the experiment.� The Trial block organizes the data into a hierarchy, which re ects thedesign of the experiment.� The Index block which indexes the SPIFF �le.Other block types can be added as needed. Some possibilities are� audio blocks in AIFF format - for sound experiments� velocity blocks - velocity sequences for oculomotor experiments� pain blocks - stimuli time-stamps for pain experiments� stimuli sequence blocks - indexing of movie frames for visual experiments,etc.Clearly, one could extend the number of blocks inde�nitely, but for now, wewill stick to the eight basic blocks above. They will be described in subsequentsections.All of theses blocks are grouped together into a Spi� block, which comprisesthe whole SPIFF �le. Its format is#define SPIFF_RECORD 'SPIF'typedef struct {char blockID[4];long blockSize;long headerSize;char version[4];char lab[8]char experimenter[8];char date[6];char time[6];char hasIndex;} Spiff_block; 5

blockID is a useful redundancy that always has the value `SPIF'. This helpsto identify the �le in the event of bad �le names, disk crashes, etc.blockSize is the size in bytes of the whole Spi� block. It is -1 if the size isunknown.headerSize is the size in bytes of the Spi� header.version[4] is the version number of the SPIF format. It is currently \1.00".lab[8] is the name of the lab this data was taken at, in ASCII. This couldbe, e.g., the last name of the lab head, padded with spaces.experimenter[8] is the name of the experimenter. This could be the lastname of the experimenter, padded with spaces.date[6] the date the data was taken. The format is six ASCII charactersin the form MMDDYY.time[6] the time the data was taken. The format is six ASCII charactersin the form HHMMSS.hasIndex is 1 if there is an index, 0 otherwise.blocks[] is a char array that contains all the blocks in the SPIFF �le.At the end of the Spi� block there is an optional index, described in sec-tion 11.Thus the SPIFF is a hierarchical format with two or more levels; the Spi�block and the individual data blocks. The SPIFF �les should have the threeletter extension .spf. For concreteness, the Spi� block is required, but theothers are optional.4 Comment BlockThis block is used to describe the nature of the data collected, experimentalconditions, context of the experiment, etc. It is a free form character array, tobe used at the discretion of the experimenter. The comment block's primary useis to store information which does not vary from trial to trial and miscellaneousinformation about the experiment. For information which varies from trial totrial, see the key-value system in section 10.The Comment block has the simple form#define COMMENT_ID 'COMM'typedef struct {ID blockID;long commentSize;char comment[];} Comment_block;blockID is set to 'COMM'.commentSize is the size of the comment block in bytes.comment[] is the commentary. It should be in human-readable form, i.e., inprintable ASCII, with any numerical values also in ASCII.It is not part of the format, but it is highly recommended that commentsregarding the whole SPIFF �le be put right after the Spi� header, and comments6

concerning the a whole trial (see section 9) should be put right after the trialheader. A useful convention for comment blocks regarding other blocks wouldbe to place them right after the block which they describe.5 Regular Trace BlockThis block contains trace data and assumes that it was taken at a constantsampling rate. It's structure is#define RegularID 'RGTR'typedef struct {char blockID[4];long blockSize;custom time startTime;char channelID[8];char traceType[4];custom float traceUnit;custom float traceOffset;custom float samplingRate;long numSampleFrames;short traceData[numSampleFrames];} Reg_trace_block;blockID is set to 'RGTR'.blockSize is the size in bytes of the whole regular trace block. It is -1 if thesize is unknown.startTime is the starting time of the trace relative to that of the Spi� timestamp.channelID[8] gives the source of the of the trace.traceType is an array of 4 chars denoting the type of the value, e.g., `AMPS'for a current trace and `VOLT' for a voltage trace.traceUnit is the unit of voltage or current used, in volts or amps.traceOffset is the DC o�set of voltage or current, in volts or amps.samplingRate is the sampling rate of the signal in samples per second.numSampleFrames is the number of samples in the trace array. It is -1 if thenumber of sample frames is unknown.traceData[numSampleFrames] is an array of the sample values, in integralformat. To get the real values at index k, multiply traceData[k] by traceUnit.6 Irregular Trace BlockThis block contains data for a neural trace xi sampled at irregular times ti; i =0; : : : ; N . It thus has a time series of sample times, and a series of correspondingvalues: 7

#define IrregularID 'IRTR'typedef struct {char blockID[4];long blockSize;custom time startTime;char channelID[8];char traceType[4];custom float traceUnit;custom float traceOffset;long numSampleFrames;IrrEvent sample[numSampleFrames];} Irr_trace_block;blockID is set to 'IRTR'.blockSize is the size in bytes of the whole irregular trace block. It is -1 ifthe size is unknown.startTime is the starting time of the trace relative to that of the Spi� timestamp.channelID[8] gives the source of the of the trace.traceType is an array of 4 chars denoting the type of the value, e.g., `AMPS'for a current trace and `VOLT' for a voltage trace.traceUnit is the unit of voltage or current used, in volts or amps.traceOffset is the DC o�set of voltage or current, in volts or amps.numSampleFrames is the number of samples. It is -1 if the number of framesin unknown.samples[numSampleFrames] is an array of structures, each structure givinga sample time and a sample value:typedef struct {custom time time;long value;} IrrEvent;7 Multi-electrode Trace BlockThis block contains trace data from multiple electrodes and assumes that eachtrace was taken at the same sampling rate. This block type is useful forstereotrode and tetrode data. For ease of writing the block, it is e�cient togroup the data into blocks of simultaneous events, so that the data streamsfrom each electrode are interleaved:#define MULTI_TRACE_ID 'MLTR'typedef struct {char blockID[4];long blockSize;long startTime;8

char channelID[8];char traceType[4];custom float traceUnit;custom float traceOffset;custom float samplingRate;long numSampleFrames;short numTraces;short traceSet[numTraces];...short traceSet[numTraces];} Multi_trace_block;blockID is set to 'MLTR'.blockSize is the size in bytes of the whole multi-trace block. It is -1 if thesize is unknown.startTime is the starting time of the traces in microseconds, relative to thatof the Spi� time stamp.channelID[8] gives the source of the of the traces.traceType is an array of 4 chars denoting the type of the value, e.g., `AMPS'for a current trace and `VOLT' for a voltage trace.traceUnit is the unit of voltage or current used, in volts or amps.traceOffset is the DC o�set of voltage or current, in volts or amps.samplingRate is the sampling rate of the signals in samples per second.numSampleFrames is the number of samples in each trace array. It is -1 ifthe number of sample frames is unknown.numTraces is the number of electrodes.traceSet[numTraces] contains data from electrode 0, electrode 1, ... elec-trode numTraces-1. To get the real values for electrode k, multiply traceSet[k]by traceUnit.8 Neural Time-stamp BlockThis block contains the times of the neural �rings. This data is typically gen-erated by spike sorting programs. It's structure is#define TIME_STAMP_ID 'TMST'typedef struct {char blockID[4];long blockSize;custom time startTime;long numSampleFrames;StampEvent timeData[numSampleFrames];} Time_stamp_block;blockID is set to 'TMST'.blockSize is the size in bytes of the whole time stamp block. It is -1 if thesize is unknown. 9

startTime is the starting time of the time stamps relative to that of theSpi� time stamp.timeUnit is the unit of time (in seconds) and is used to scale the �ring times,e.g., 10�6 for microseconds.numSampleFrames is the number of samples in the timing array. It is -1 ifthe number of samples is unknown.timeData[numSampleFrames] is an array of structs containing the sampledata. They have the formtypedef struct {custom time time;long stampCode;} StampEvent;The time gives the time of the event and the stampCode describes the type ofevent, in a user-de�ned way.9 Multiple TrialsEach experiment or recording session is often more than a linear set of tracesand time stamps; it has the hierarchical structure of an experimental designand a logical grouping of observations. The trial block is designed to re ectthis structure. Like the Spi� block, it groups multiple blocks, which are relatedto one another, into a single entity. The grouped blocks may be of any type,including other trial blocks. In addition to the usual block information, the trialheader contains space for up to 8 parameters to be recorded, which may containenvironmental conditions, stimulus parameters, etc. See section 10 for details.The structure of a trial block is#define TRIAL_ID 'TRIL'typedef struct {char blockID[4];long blockSize;custom time startTime;ParmEntry parameters[8];} Trial_block;blockID is set to 'TRIL'.blockSize is the size in bytes of the whole trial block. It is -1 if the size isunknown.startTime is the starting time of the trial relative to that of the Spi� timestamp.parameters[8] is an array of structures which contain information aboutthe parameters of that particular trial. Up to eight parameters may be stored,and each ParmEntry has the form 10

typedef struct {short dictNum;short parmValue;} ParmEntry;The dictNum is the numerical position in the list of parameters in the dictionary;0 for the �rst entry, 1 for the second, etc.. dictNum is set to -1 if the currentand subsequent ParmEntries are unused. The parmValue is the value of theparameter.blocks[] is a char array that contains all the di�erent blocks in the Trialblock.As in the Spi� block, it is strongly recommended that any comments aboutthe whole trial should be put into a comment block.10 Key-Value SystemThinking of a trial block as a part of an experiment, one realizes the need torecord ancillary data in addition to the actual observations: environmental vari-ables, stimulus parameters, etc. If these ancillary parameters were recorded in astructured way, then one could easily search for, say, recordings under particularstimulus conditions, or create a database of the of all the di�erent parameterstried over several di�erent experiments. Having structured information aboutthe parameters used may also help in the semi-automatic visualization of databy setting axes properly.To address this need a key-value system has been set up. In each trialblock, up to eight parameters may be stored in ParmEntry structures, describedin the previous section. The dictionary, in turn, stores information about theparameters. The dictionary is a simple list of parameter types and their units.The structure of a dictionary block is#define TRIAL_ID 'DICT'typedef struct {char blockID[4];long blockSize;DictEntry definitions[];} Dict_block;blockID is set to 'DICT'.blockSize is the size in bytes of the whole dictionary block. It is -1 if thesize is unknown.definitions[] is an array of structures which contain information aboutthe parameters. Each entry has the formtypedef struct {short unit;short defaultValue;char description[32];11

} DictEntry;unit is a short which multiplies the parameter value to give the true value ofthe parameter. defaultValue is a short which gives the default value of theparameter, in the case that it is not mentioned int the key-value area of the trialblock header. description[32] is an ASCII string of up to 31 characters (plusa null) which describes the parameter. Examples are \orientation in degrees"or \intensity in dB".Using the key-value system is simple. One �rst decides which parametersare to be included in the dictionary and their appropriate units. Then at thesame hierarchical level and sometime before the �rst trial block using theseparameters, one writes the dictionary block. Finally, in the header of each trialblock one writes ParmEntries for each relevant parameter in that trial. Notethat the default value can be used to set the values of static parameters which donot change value over the course of the trial. This is a way to store experimentalconditions in a more structured fashion than in a Comment block.11 Index BlockThe optional, but recommended, index at the end of the Spi� block allows one tocreate a quick overall view of the �le and to randomly access needed data. It alsoallows one create a �le in a purely streaming manner (described in section 12).The index contains basic information on each block in the Spi� block, excludingthe Spi� block itself. The structure of the index block is#define IndexID 'INDX'typedef struct {char blockID[4];indexEntry blockList[];} Index_block;blockID is set to 'INDX'.blockList[] is an array of structures giving the type of block, the o�set ofthe block from the beginning of the �le, and the size of the block. Note thatblock o�set and block size do not give redundant information, as trial blockscontain other blocks, so their size depends on the hierarchical structure of the�le.typedef struct {char blockID[4];long blockOffset;long blockSize;} indexEntry;The �nal entry in the index is the index block itself. This allows one to 1)look at the Spi� block and determine if the is an index. If so, 2) seek to the endof the �le minus 8 bytes, 3) read in the block o�set, and 4) start reading theindex. 12

12 Reading and Writing SPIFFOne can write SPIFF in at least three modes. In the conversion mode, all theinformation needed for the headers and the data are already available (e.g., asin converting formats). In this case one can write the SPIFF �le in a singlepass, and construct an index at the end, if desired.In the pseudo-streaming mode, one is acquiring data and writing it out to aSPIFF �le. If the time constraints are not great and seeks to earlier positionsin the �le are possible, then writes headers with \unknown" block sizes of -1.At the end of each block, including the trial and Spi� blocks, one does a seekto the beginning of the block and writes the proper block size.In full streaming mode, time constraints do not allow seeks and the �le mustbe written in one pass. In this case, one writes a header with an unknown sizeas before. The di�erence is that at the end of a block, instead of doing a seek,one stores the index information for that block in RAM. At the end of the Spi�block, one then writes the stored information out to the index. One may thenat their leisure write appropriate block sizes using the index information.Appending to an existing �le is also straightforward. In non-streaming mode,blocks are simply appended to the end of the Spi� block and the Spi� size isupdated. Any pre-exiting index will be destroyed in this process, so it may beuseful to read the index into memory �rst.In pseudo-streaming mode, blocks are appended to the end of the Spi� blockand the Spi� size is updated.In full streaming mode, one must be careful to save the index informationinto RAM before appending new blocks.Reading the �le can be accomplished in two ways. The �rst is to simplyread the �le block by block. This only works if the block sizes are all known. Ifall block sizes are known, then the headers are self-describing and the �le canbe read in one pass.The second way is read the index. If the Spi� block size is known, then onecan simply seek to the beginning of the index and start reading. If the Spi�block size is not known (as in the full streaming mode), then one may seek tothe end of the �le, read the index block o�set, seek to the beginning of theindex, and start reading. In addition to permitting full streaming mode, theindex provides at least three bene�ts. First, it allows a program and user toget a quick overall view of the �le. Second, it allows one to randomly accessdesired blocks in the �le. Third, it permits an elementary form of error checkingof block positions with block sizes.If there is neither an index nor known block sizes, reading a SPIFF �lebecomes nondeterministic. There is still hope, however. The \magic cookies",in the form of block IDs, allow one to grep through a �le until one encountersa valid ID and is then able to start reading the block. It is possible for randomdata to emulate an ID, but it is not likely and in any case, one may examinethe rest of the header for validity. 13

13 DiscussionDescribed above were four basic blocks types of neural data. A SPIFF recordwould not be limited to only neural data, however. The Spi� block allows formany di�erent types of blocks to be included, such as experimental stimuli likeaudio input, etc. Thus a SPIFF could contain an entire experimental record.What is the best way to implement these �lters? There are at least twopossibilities: 1) a suite of small programs like those of NetPBM, or 2) an all-in-one application like that of sox. A sox-like program would have an advantagein that users would only have to know one application. The suite of programswould be more Unix like and might be more easy to maintain. Both approacheswould bene�t from a small set standardized subroutines (an API ) that wouldread and write the SPIFF format; this way others could also program SPIFFinto their own applications. I'd suggest C or Perl as the languages of choice forthe implementation; both are portable and good at processing both text andbinary data. C gets the edge on the microcomputers, since it would result insmaller executables, and the edge overall, since it is more widely read.14 AcknowledgmentsThis work was supported by The Sloan Foundation.References[1] Audio Interchange File Format: \AIFF", version 1.3, Apple Computer, Inc.1989

14

15 Appendix AAn alternative index format for the SPIFF �le which would be simpler to pro-gram than the one in my proposal is the one proposed by Sergei and Steve. Inthis new scheme, all the indices would be moved to the end of the SPIFF �le.This allows better streaming and simpler programming. To read a SPIFF �lewhich has no index information in it, it is useful to have each block contain itssize in a �xed location, so that we may easily seek from block to block. Thus,before the closing of each block, a seek must be done back to the header to writethe �nal size of the block. The index is constructed in a separate step.Here we describe the proposed format formally with a BNF (Backus-Naurform) syntax. BNF is a meta-language, i.e. a language for describing languages.The SPIFF format is very simple language, with much �xed structure and onlya little recursion provided by the trial block. Nonetheless, BNF is useful forpreparing programs that read and write SPIFF. The notation is as follows:\::=" means \is de�ned as"\j" means \or"\hi" means a \non-terminal symbol"\symbol" means a \terminal symbol"Each non-terminal symbol has its own production rule, i.e., it is de�ned in termsof other symbols. Each terminal symbol is something concrete, like a constantcharacter string or an integer. Symbols strung together, e.g., \hsym1i hsym2i"means that hsym2i directly follows hsym1i in the �le, with no intervening bytes.For instance, a person's name might have the production ruleshnamei ::= h�rst-namei last-namej h�rst-namei hmiddle-nameilast-nameh�rst-namei ::= name j initial .hmiddle-namei ::= name j initial .The production rules for SPIFF are:hspi�-�lei ::= hspi�i j hspi�i hindexihspi�i ::= hspi�-headi hspi�-dataihspi�-headi ::= SPIF block-size header-size version-numberlab experimenter date time has-indexhspi�-datai ::= hblocki j hblocki hspi�-dataihblocki ::= hcommenti j htriali j hregular-tracei j hirregular-traceij hmulti-tracei j htime-stampi j hdictionaryi15

hcommenti ::= COMM block-size comment-datahtriali ::= htrial-headi htrial-dataihtrial-headi ::= TRIL block-size start-time parametershtrial-datai ::= hblocki j hblocki htrial-dataihregular-tracei ::= RGTR block-size start-time channel-idtrace-type trace-unit trace-o�set sampling-rate num-framesreg-frameshirregular-tracei ::= IRTR block-size start-time channel-idtrace-type trace-unit trace-o�set time-unit num-framesirr-frameshmulti-tracei ::= MLTR block-size start-time channel-idtrace-type trace-unit trace-o�set sampling-rate num-framesnum-traces multi-frameshtime-stampi ::=TMST block-size start-time time-unit num-frames time-frameshdictionaryi ::= DICT block-size hdict-entriesihdict-entriesi ::= hdict-entryi j hdict-entryi hdict-entriesihdict-entryi ::= unit default-value descriptionhindexi ::= INDX num-entries j INDX num-entries hblock-entriesihblock-entriesi ::= hblock-entryi j hblock-entryi hblock-entriesihblock-entryi ::= block-type block-o�set block-sizeNotes:1. The block-size of any of these blocks is the size of the whole block =header + data.2. All times are relative to the time stamp in the Spi� head.3. It is highly recommended that comments regarding the whole SPIFF �lebe put right after the Spi� header, and comments concerning a wholetrial should be put right after the trial header. A useful convention forcomment blocks regarding other blocks would be to place the commentblock right after the block which they describe.16

4. Here are the data types of the terminal symbols used above; for moredetails, see the proposal. The all caps are just the four-letter uppercasechars, andblock-o�set : 32-bit intblock-size : 32-bit intblock-type : array of 4 charschannel-id : 32-bit intcomment-data : array of charsdate : array of 6 charsdefault-value : 16-bit shortdescription : ASCII string in a char[30] arrayexperimenter : array of 8 charshas-index : charheader-size : 32-bit intindex-size : 32-bit intirr-frames : array of structs: f (64-bit custom time) time; (32-bit int) valueglab : array of 8 charsmulti-frames : array of traces; traces are arrays of 16-bit intsmulti-time-frames : set of arrays of 32-bit intsnum-entries : 32-bit intnum-frames : 32-bit intnum-timing-samples : array of 32-bit intsnum-timings : 32-bit intnum-traces : 32-bit intparameters : array of eight ParmEntry structs: f (short) dictNum, (short) parmValue grecursion-level : 32-bit intreg-frames : array of 16-bit intsstamp-type : 32-bit intstart-time : 64-bit custom timetime-frames : array of structs: f (64-bit custom time) time; (32-bit int) valuegtime : array of 6 charstrace-o�set : custom oattrace-type : array of 4 charstrace-unit : custom oatunit : 16-bit intversion-number : array of four chars17

16 Appendix BThis appendix describes the custom oat format in detail. Each oating pointnumber is described in terms of two 32-bit integers: amantissa and an exponent.To calculate the mantissa and exponent, �rst convert the number to the usualscienti�c notation:number = mant� 10exp 1 � mant < 10: (1)Then the mantissa is generated by multiplying mant by 108, and keeping theintegral part. The exponent is simple exp. In the case that number = 0, mantis set to 0 and exp is set to 1. De�ning the custom oats base 10 improveshuman readability and thus error correction.Below is a snippet of perl code which writes a custom oat:# convert from an ordinary floating point to a custom floatsub to_custom{ my($num) = @_;my($sign,$num_exp,$num_mant);if ( $num = 0) { # 0.0 is a special casereturn((0.0,1.0));}$sign = $num < 0 ? -1 : 1;$num = abs($num);if ( $num < 1) {$num_exp = int( log($num) / log(10.0) ) - 1;}else {$num_exp = int( log($num) / log(10.0) );}$num_exp = int( log($num) / log(10.0) );$num_mant = $sign * int( (10**8) * ($num/(10**$num_exp)) );return (($num_mant, $num_exp));}# converts from a custom float to an ordinary floatsub from_custom{ my($mant,$exp) = @_;$numb = $mant * 10**($exp-8);return($numb);} 18