.help QPOE Jun90 "Quick POE Design"
.ce
\fBQuick-POE (Position Ordered Event File) Interface Design\fR
.ce
Doug Tody
.ce
July, 1988

.NH
Introduction

    The POE (Position Ordered Event file) facility is an interface and file
structure used to store and access the event (photon) lists generated
by event counting detectors.  Each event is described by a unique position,
time, energy, and possibly other parameters (e.g., polarization, position in
other coordinate systems, or instrument related parameters).  In the case of
an imaging event counting detector, the "image" generated consists of this list
of discrete events, rather than the regular matrix produced by a conventional
sampling detector.  Both types of detectors are fundamental to astronomy.

The POE interface is a stand alone interface built upon the standard VOS
interfaces DFIO (in a future release), PLIO, SYMTAB, FIO, and other lower
level interfaces.  The POE interface may be called directly by applications
code to create and access POE datafiles, for event file specific processing.
In addition, an IMIO image kernel is provided so that POE files may be
accessed as (read only) images, allowing existing IRAF image tasks to be
used to access POE files.  The main function of the POE image kernel is to
filter and sample the event list in real time, returning a conventional
sampled grid (image matrix) to the high level applications code.
The parameters controlling the filtering and sampling operations may be
specified by the user when the image (POE file) is accessed, making runtime
filtering of events possible in connection with any general image processing
task.

.NH 2
Important Concepts
    
    The primary object dealt with by this interface is the \fIevent file\fR,
consisting of a free format \fIfile header\fR and the main \fIevent list\fR.
The event list is a collection of \fIevent structures\fR, e.g., photons
hitting an imaging detector during the period of observation recorded by the
event file.  Each event is characterized by a standard set of attributes such
as the position of the event in detector, sky, or other coordinates, the time
at which the event was recorded, the energy of the event, and so on, plus
optionally additional instrument dependent attributes (in general the event
structure cannot be fixed, and the nomenclature may vary depending upon the
science being performed).

The events may appear in the event list in any order, but since most access
to image data tends to be spatial in nature, access will be most efficient if
the event list is position ordered.  This is the convention chosen by QPOE
and hence the name \fIpoe\fRfile, or \fIp\fRosition \fIo\fRrdered \fIe\fRvent
file.  An important alternative ordering is time ordering, which preserves
the order in which the events were originally recorded, but which requires
a complete scan of the event list to accumulate the events in a region of
interest during analysis.  There are cases where time ordering might be
preferable to position ordering, e.g., for time series analysis of a long
observation.

Of fundamental importance to the analysis of data from event counting
detectors is the concept of \fIfiltering\fR.  It is in the nature of event
counting detectors that they are often used to observe very faint objects
for very long periods of integration.  The total amount of data (number of
events) may be limited, so one wants to preserve all events, but since the
quality of the data may vary both with time and with position, it is common
to want to reject a portion of the data.  Conversely, the analysis being
performed may require one to examine only a portion of the data, e.g., only
events with a certain range of energies or arrival times, occurring within a
given region of the image.  Often the analysis will be repeated many times
wtih different filters.

Hence, most analysis of event counting data typically involves both
\fIrejection\fR and \fIregion of interest\fR filtering.  Rejection filtering
depends mostly upon the data itself, hence the rejection filter for an image
is a part of the image and should be in effect by default whenever the image
is accessed, although we would like to physically record all data and be able
to change the rejection filter either temporarily or indefinitely if desired.
Region of interest filtering, on the other hand, depends upon the scientific
analysis being performed rather than upon the data, hence is highly variable
and should be controlled by the user, independently of the data.

.NH 2
Interface Requirements

    Given the description of the problem to be solved presented in the previous
section, we can make the following observations regarding the POE interface:
.ls 4 o
A flexible binary file header supporting both scalar and variable length vector
fields is essential.  Examples of vector fields include the aspect and temporal
records (actually arrays of records, or subtables), the processing history
(probably stored as a single variable length text buffer), and the rejection
and region of interest filters (PLIO external format byte sequences stored as
opaque binary arrays).
.le
.ls o
In the general case the details of the event structure depend upon the
instrument for which data is being stored.  The minimum requirement is that
the event structure consist of a set of standard fields (x, y, time, energy)
followed by a variable length, instrument dependent area (hence the size of
the event structure, while fixed for a given datafile, should be allowed to
vary depending upon the data).  Ideally all fields should be named and
accessible for filtering, the names chosen should be variables rather than
constants, and the set of fields used to describe an event should be allowed
to vary depending upon the data.
.le
.ls o
Runtime access to the event list, including event-attribute and spatial
filtering, should be as efficient as possible since this is likely to be
by far the most time consuming part of the interface.  Header access
efficiency is much less important and is not expected to be a problem.
.le
.ls o
For most efficient access the event list should be stored sorted upon some
primary key, with an index maintained by the interface for that key,
and used for efficient retrieval.  The minimum requirement is that the primary
or sort key be the Y coordinate (corresponding to image lines).  Ideally it
should be possible for the sort key to be any field of the event structure,
or any combination of fields (e.g., Y+X, or T).  Ideally the interface itself
should be responsible for maintaining the event list in sort order; this is
not a requirement since writing to event files is much less common than
reading.  Ideally it should be possible for the event list to be unordered,
and it should be possible to transparently access the event list regardless
of the ordering.
.le
.ls o
Rejection filtering is typically required by the data, yet we wish to retain
all the data and be able to override or replace the default rejection mask.
The rejection filter is logically associated with the data and should be
stored with the data.  The minimum requirement is for the interface to be able
to store all the data plus the rejection mask, and be able to return only
the "good" data at runtime.  The interface is not required to perform rejection
filtering at runtime, although it would be desirable to be able to do so if
the efficiency penalty were not too great.  Alternatively, the event list
could be prefiltered, and the "good" and "bad" events stored in different
places in the datafile, requiring that the entire file be rebuilt to change
the rejection filter.
.le
.ls o
Region of interest filtering is a common operation for event data, and can be
difficult to implement efficiently, hence should be supported directly by the
interface.  At a minimum it should be possible to filter events by defining a
range of acceptable or unacceptable values for each of some subset of the event
attributes (e.g., energy or time).  Ideally it should also be possible to
specify an arbitrarily long list of ranges of acceptable values.  Ideally
spatial filtering should be supported as well; this is required for rejection
filtering, but is at most a desirable option for spatial region of interest
filtering (there is nothing about spatial filtering which is unique to event
data, hence it might be more appropriate to implement it at a higher level,
but on the other hand it might be more efficient to implement it at the event
i/o level since the event list is position ordered and can be very large).
.le
.ls o
It should be possible to specify the various filtering options both
transparently to applications programs (via a symbolic expression passed
into the interface by the user as part of the file specification),
or procedurally, via \fIset\fR-parameter calls issued by the client program.
.le

The above issues need to be adequately addressed in order to have a useful
interface.  In the longer term there are many other considerations, e.g.,
it is also desirable for the data format to be machine independent, and the
data format should be flexible, to accommodate the inevitable evolutionary
revisions as well as to accommodate data from a variety of instruments or
projects.  The event files can be very large, hence efficiency is a major
consideration for event i/o and filtering.

.NH 
The QPOE Interface
.NH 2
Implementation Strategy

    A two step operation is planned for implementing the POE interface within
IRAF.  The first step is the so called quick-POE interface.  The objective of
quick-POE is to provide the necessary functionality so that applications
development can proceed immediately, without waiting for the general interface
to be developed.  Once quick-POE is in place development of the fully general
POE interface can proceed at a more leisurely pace consistent with the plan to
provide most of the functionality of the generalized POE interface with other
standard IRAF facilities currently under development, most notably the datafile
i/o interface (DFIO), a general purpose binary file record manager to be used
in the new images structures project and elsewhere in IRAF.

The main facilities provided by POE are for access to general header fields,
access to the variable length aspect and temporal records, event list i/o,
and event filtering.  The POE header is a type of record, and the aspect and
temporal "records" are arrays of records (tables), as is the event list itself.
Any record access problem involving large records commonly involves the
related problems of indexing and selection based on a user supplied predicate
(boolean expression or filter in our case).

In the general case, we would like the POE interface to be able to support
data from a variety of detectors or instruments, not only those used in high
energy astrophysics but those used in optical and radio observatories as well,
hence the details of the data structures must be allowed to vary without
affecting the interface itself.  Ideally the POE files should be maintained
in a machine independent format, and the applications programs using POE
should not be affected by changes to the data format, and indeed should be
usable directly with any of several similar data formats, or with data formats
that evolve over time.

All these observations lead us to conclude that a general implementation of
the POE interface has much in common with the general record access problem,
hence the association of POE with DFIO.  Quick-POE will provide much the same
functionality at the applications level but will be less general, i.e., the
binary record structures as seen by applications will be mapped directly onto
external storage (the main contribution of DFIO is data independence and
flexibility).  This means that initially the applications will be tied to
a specific format datafile, changes to applications structures will require
reformatting the data, and the datafiles will probably be machine dependent.

This approach would be unacceptable in the long run, as the need arises to
support a variety of instruments, but is viable for initial applications
development provided a DFIO based implementation eventually replaces the
initial interface.  There should be little difference in terms of functionality
and efficiency between QPOE and POE; in fact QPOE may have the edge over POE
in terms of efficiency, since it will be less general.  It is likely that
applications developed for QPOE will be usable with POE with few if any
changes, e.g., by reimplementing QPOE as a layer on top of the more general
POE interface.  The main motivation for implementing a DFIO based POE will be
to provide increased data independence, a machine independent binary data
format, and the ability to support a variety of instruments with a single
interface.

Although some throw away code will have to be written to implement the QPOE
interface, most of the complexity of the interface lies in the event filtering
code, which should be reusable in the final interface.  Low level, custom
(non-DFIO) selection code is required for POE due to the unusual requirements
for region and temporal filtering, and the potentially extremely high data
volume (>10**6 event records).  This implies that DFIO itself will have to be
a layered interface, supporting low level access to the packed data records
for applications with unusual efficiency requirements (it will be).  Finally,
it is already evident that the low level file manager required by QPOE has
much in common with the access method code planned for DFIO, hence can serve
as a prototype for the DFIO file manager.

The remainder of this document will deal only with the details of the
QPOE interface.  The DFIO interface has already been specified,
including a sketch of a data definition for a POE file.  See \fIThe IRAF
Datafile I/O Interface\fR, February 1988.  The region filtering
code in POE will make use of the Pixel List I/O (PLIO) interface, described
in \fIThe IRAF Pixel List Package\fR, February 1988.  The latter interface
has already been implemented, as have all other interfaces (e.g., SYMTAB)
required by QPOE.

.NH 2
Architecture

    The architecture of IRAF as it pertains to the QPOE (and POE) interface
is summarized in the figure below.

.ks
.nf
        IMIO
                IKI
                        IK-POE
                                POE
                                        PLIO
                                        [DFIO]
                                        SYMTAB
                                        FIO
.fi
.ke

As indicated in the figure, QPOE depends most heavily on the VOS interfaces
PLIO (pixel list i/o), used for spatial filtering, SYMTAB (the general symbol
table package), used to manage the file header and aspect and temporal records,
and FIO (file i/o), used to access the binary file in
which the QPOE data is stored (low level, unbuffered asynchronous i/o is
used by the QPOE file manager).  The QPOE interface is accessed both directly
by applications code, and by the IMIO interface via an image kernel, shown
as IK-POE in the figure.  IK-POE and QPOE comprise the code to be written to
implement the QPOE interface.

The QPOE interface consists of the POE file itself, a binary data structure
[largely] private to the QPOE interface, and a set of procedures for creating,
writing into, and reading from POE files.  The procedures fall into several
categories, i.e.,
.ls 4
.ls o
General QPOE file management procedures.  These include routines for creating,
deleting, renaming, opening, and closing POE files, plus set/stat routines
for setting and querying the file parameters and interface options.
.le
.ls o
General header access procedures.  These include a conventional set of keyword
driven typed scalar get/put routines, plus get/put routines for accessing
variable length typed and opaque binary arrays (e.g., history records and the
aspect and temporal records).
.le
.ls o
Event i/o procedures.  These are routines for initially preparing and
subsequently reading (sequentially with seek) the main event list, e.g.,
the raw "get next photon" routine.
.le
.ls o
The selection subpackage.  Included are routines for opening and incrementally
compiling a user supplied selection predicate (filter) input as a formatted
text string, and for testing individual event records to see whether they
satisfy the given expression.
.le
.le

All binary data structures other than simple scalar variables, e.g., the
aspect and temporal records and the event structure, are described in QPOE
by compile time bound SPP binary data structure definitions, provided in a
standard interface include file (\fI<qpset.h>\fR) referenced by both QPOE and
selected applications.  When the interface is layered upon DFIO these
structures could continue to be used, since DFIO will have the ability to
define runtime mappings of conventional application defined structures onto
the physical data datafile (POE file) structures.

.NH 2
Interface Specification

    The quick-POE interface (QPOE, package prefix `qp') is a set of procedures
for accessing \fIpoefiles\fR, or position ordered event files.  Each poefile
consists of a \fBheader\fR of arbitrary size and content containing zero or
more named scalar or variable length (opaque, typeless) fields, plus an
\fBevent list\fR consisting of zero or more event structures.
The event structure is fixed at compile time via a conventional SPP structure
declaration in the include file \fB<qpset.h>\fR.

.NH 3
Interface Procedures

    The QPOE procedures fall into three main categories, the primary user
interface procedures (general datafile management, header access, and filtered
event i/o), the low level or raw event i/o procedures, and the low level
selection expression compile and evaluate procedures.

.NH 4
Header Access Procedures

    The routines described in this section are used to create, open, or
otherwise manipulate poefiles, to define new header parameters or query the
existing parameter set, and to read and write the values of both scalar and
vector parameters of various standard and poefile-specific datatypes.
These operators are summarized in the figure below.

The function of most of these procedures should be obvious.
The \fIqp_access\fR, \fIqp_delete\fR, \fIqp_rename\fR, and \fIqp_copy\fR
operators perform the implied operation on the named poefile.
The poefile may be rebuilt with \fIqp_rebuild\fR, recovering any unused
space and rendering storage for the internal data structures (logically)
contiguous in the process (a rebuild is just a copy/rename/delete).

The \fIqp_open\fR procedure must be called to open or create a poefile,
before it can be accessed.  The NEW_FILE and NEW_COPY modes are supported
for creating new files.  If NEW_COPY mode is specified, a reference file
may be specified (via the descriptor \fIo_qp\fR) from which the new file
is to inherit the header but no data (no event list).
The \fIqp_seti\fR and \fIqp_stati\fR procedures are used to set and stat
any parameters affecting QPOE i/o, and \fIqp_sync\fR updates an opened
poefile on disk.

The \fIqp_get\fR and \fIqp_put\fR scalar functions behave as for the other
VOS interfaces, e.g., they will abort if the named parameter does not exist,
or if the implied datatype conversion is illegal.  The \fIqp_add\fR 
procedures are equivalent to the \fIqp_put\fR procedures except that they
will create the named parameter if it does not already exist (see also
\fIqp_addf\fR, discussed below).

.nf
         yes|no = qp_access (poefile, mode)
                    qp_copy (poefile, newfile)
                  qp_rename (poefile, newfile)
                 qp_rebuild (poefile)
                  qp_delete (poefile)

               qp = qp_open (poefile, mode, o_qp)
                    qp_seti (qp, param, ival)
            ival = qp_stati (qp, param)
                    qp_sync (qp)
                   qp_close (qp)
           
     val = qp_get[bcsilrdx] (qp, param)
                    qp_gstr (qp, param, outstr, maxch)
           qp_put[bcsilrdx] (qp, param, val)
                    qp_pstr (qp, param, strval)
           qp_add[bcsilrdx] (qp, param, defval, comment)
                    qp_astr (qp, param, strval, comment)

              fd = qp_popen (qp, param, mode, type)
            nelem = qp_read (qp, param, buf, nelem, first, dtype)
                   qp_write (qp, param, buf, nelem, first, dtype)

        yes|no = qp_accessf (qp, param)
                 qp_deletef (qp, param)
                 qp_renamef (qp, param, newname)
                    qp_addf (qp, param, dtype, maxelem, comment, flags)
	  nelem = qp_queryf (qp, param, dtype, maxelem, comment, flags)

         list = qp_ofnl[su] (qp, template)
          nch|EOF = qp_gnfn (list, outstr, maxch)
                    qp_cfnl (list)
.fi

Array valued parameters may be randomly read with \fIqp_read\fR and
written with \fIqp_write\fR; arrays may be any length, and will be
automatically extended in a write.  The only way to shorten an array
parameter is to copy it and delete the old parameter.  The typed read and
write functions allow automatic type conversions, and external storage of
the data in a machine independent form (should the interface choose to do so).
In addition to the standard SPP types, QPOE supports the special types
TY_EVENT, TY_ASPECT, and TY_TEMPORAL.  Finally, the type TY_OPAQUE denotes
an array of element size SZ_CHAR, which will be copied to and from external
storage without the data being modified in any way (note that opaque data
is machine independent only if the application encodes it that way).

Alternatively, an array valued parameter may be opened as a random access
\fIfile\fR with \fIqp_popen\fR, and then read or written with conventional
FIO calls.  The value of the \fItype\fR parameter must be TEXT_FILE or
BINARY_FILE, as for a conventional file.  If the type is TEXT_FILE then
only text data may be stored in the file, and text data will be byte packed
on disk.  The BINARY_FILE type is equivalent to the QPOE type TY_OPAQUE.
File i/o to a QPOE parameter is equivalent to file i/o to a conventional
binary file in terms of both efficiency and semantics, i.e., the data is not
modified in any way, and the "files" may be any size (the main semantic
difference is that deleting the parameter does not immediately free the space).
A parameter opened as a file with \fIqp_popen\fR is closed with the FIO
\fIclose\fR routine.

Although new parameters may be defined when first written to by calling one of
the typed \fIqp_add\fR functions, the most general procedure for adding new
parameters is \fIqp_addf\fR, which allows the datatype and vector length of
the parameter to be explicitly specified, along with a comment describing the
new parameter.  The procedure \fIqp_accessf\fR tests if the named parameter
exists, and \fIqp_deletef\fR and \fIqp_renamef\fR make it possible to delete
and rename parameters, e.g., for implementing array copy procedures.
The \fIqp_queryf\fR procedure returns the datatype, allocated vector length,
current vector length, and comment field of the named parameter.

The field name list procedures (\fIqp_ofnl[su]\fR etc.) are used to obtain
the names of all header parameters matching the given template; a null or "*"
template returns the names of all header parameters.  This is the only way by
which an application without apriori knowledge of the field names can determine
what is in the header, e.g., to list or copy the header.

.NH 4
Filtered Event I/O Procedures

    The \fBevent i/o\fR subpacke provides sequential i/o facilities for the
main event list of the poefile.  These procedures, known as the QPIO (QPOE
event i/o) package, provide read or write (append) access to the event list,
optionally filtered when reading to select events spatially or by event
attribute.

Under QPOE, an event list is stored as a variable length array (i.e., as a
named header parameter) of type \fIevent\fR.  The QPIO package takes this
basic object and adds additional structure for more efficient i/o, e.g., events
are blocked into large, fixed size \fIbuckets\fR of N events, the first two
events of each bucket containing the minimum and maximum event values for that
bucket.  If the event list is sorted an \fIindex\fR may be maintained for the
list; this index, plus the min/max event values maintained for a bucket, are
used to optimize basic event i/o and filtering.  During event i/o the raw
event list may be filtered spatially or by event attribute.  All this is
transparent to the application, which merely opens the event list parameter
and begins reading (or writing) blocks of events.

Before i/o can take place the named event list parameter is opened with
\fIqpio_open\fR.  The selection filter to be used by QPIO may be specified
via a selection expression passed in by the user at poefile open time (as
part of the poefile name), at QPIO open time (as part of the parameter name),
or in subsequent calls to \fIqp_addfilter\fR (each call incrementally modifies
the current filter) or to \fIqp_setfilter\fR (each call replaces the affected
portion of the current filter).  A region mask may also be specified with
\fIqp_setmask\fR; if no mask is specified, the default rejection mask is used
(or more precisely, its inverse).

.nf
           qpio = qpio_open (qp, param, mode)
               qpio_mkindex (qpio, key, nelem)
              qpio_setrange (qpio, vs, ve, ndim)
       qpio_[add|set]filter (qpio, selexpr)
    nchars = qpio_getfilter (qpio, outstr, maxch)
               qpio_setmask (qpio, pl)
          pl = qpio_getmask (qpio)
   nev|EOF = qpio_getevents (qpio, ev, maskval, maxevents)
             qpio_putevents (qpio, ev, nevents)
               qpio_readpix (qpio, obuf, vs, ve, dxim, xblock, yblock)
                 qpio_close (qpio)
.fi

Events are read sequentially with \fIqp_getevents\fR, which fills in the
pointer array \fIev[maxevents]\fR with one or more pointers to event structs,
returning the number of events read as the function value, or EOF when the
event list is exhausted.  Events are returned in the order in which they are
stored in the main event list.  If a region mask is used for spatial
filtering, the mask value associated with the output events is returned in 
\fImaskval\fR.  Filtering and subranges are supported only for reading;
\fIqp_putevents\fR may only be used to append to the output poefile.
At present, poefiles are not randomly updatable, as this would require
runtime editing of the compressed event lists and it is not clear how useful
such a feature would be.

If less then the entire image is to be accessed then \fIqp_setrange\fR
may be called to specify the region of the poe image from which events are
to be read (the vector coordinates \fIvs\fR and \fIve\fR are specified
relative to the predefined primary event coordinate system, i.e., the PO
coordinates).  Repeated calls to \fIqp_setrange\fR may be made to access
multiple regions of the image, or to rewind the i/o pointer for a region.

An alternative to event i/o is provided by \fIqp_readpix\fR, which samples
the event list using the current filters and blocking factor,
generating \fInpix\fR pixels beginning with the pixel at the image coordinates
specified by the vector \fIv\fR.  This is the routine used by the POE IMIO
image kernel to read from a poefile.  Only integer pixels are supported.
On output, each pixel value is a count of the number of filtered events
mapping into that pixel.  A region mask may be used to filter the event list,
but the ability to discriminate between different regions by the mask value
is lost.

A poefile may contain any number of event list parameters, although most
files are expected to store only the main event list.
As an alternative to QPIO, event-array parameters may be accessed directly
via the normal header access parameters, e.g., \fIqp_read\fR, but i/o may be
somewhat less efficient (due to the copyout), the bucket structure will be
visible, and no filtering is possible.  In short, the raw event list will
be accessed as an array, returning the min/max events in each bucket along
with the data events.

In the current implementation, the event structure is a fixed, predefined
binary structure, and all event i/o is expressed in terms of pointers to event
structures.  The event structure is defined in the include file
\fB<qpset.h>\fR, discussed in the appendix.

.NH 4
Selection Subsystem Procedures

    The \fBselection subsystem\fR is a facility used to perform runtime
filtering of the event list, returning to the calling program only those
events satisfying some user defined selection criteria.  The selection
subsystem is driven by a selection expression provided by the user as a
formatted string, normally at image (poefile) open time.  The selection
expression syntax itself is independent of the procedural interface and is
described separately in section 2.3.2.  The selection procedures are
summarized in the figure below.

.nf
	     ex = qpex_open (qp, expr)
    ok|err = qpex_modfilter (ex, exprlist)
    nchars = qpex_getfilter (ex, outstr, maxch)
	nev = qpex_evaluate (ex, i_ev, o_ev, nev)
		 qpex_close (ex)
.fi

A selection expression, input as the string \fIexpr\fR, is compiled with
\fIqpex_open\fR, which returns a pointer to the runtime descriptor (filter)
for the given compiled selection expression.  If \fIexpr\fR is the null
string the filter returned will pass all events.

An active filter may be modified with \fIqpex_modfilter\fR, which combines
the expression \fIexpr\fR into the current filter, allowing complex
filters to be built up in several calls, or allowing an application to modify
a base filter without knowledge of the base filter.  The expression consists
of a list of comma delimited "attribute = exprlist" terms.  If the assignment
is specified as "=" the term for the given attribute is replaced; if the
assignment is "+=" the term for the given attribute is further qualified.
A text representation for the current filter may be obtained at any time with
\fIqpex_getfilter\fR.  The boolean function \fIqpex_evaluate\fR is called to
test whether a specific event meets the selection criteria, i.e., to test
whether or not \fIexpr\fR is true for the given event.

Selection is normally performed transparently to the application by the QPIO
interface, which calls the routines described in this section to create and
apply event-attribute filters.

.NH 3
Spatial and Event Attribute Filtering
.NH 4
Selection Syntax

    Selection expressions are used to construct \fIfilters\fR to specify the
rejection mask and region of interest filters before reading the event list
via QPIO.  Due to the complexity of event attribute selection, selection
predicates are specified syntactically, i.e., as an expression input as a
text string.  These filters may be input by the user as part of the image
or poefile specification (object name), transparently to applications code,
or they may be constructed by the application via calls to the QPIO or
selection routines.  Complex or frequently referenced filters may be stored
in text files and referenced by filename if desired.  It does not matter
whether a filter is input all at once, or compiled incrementally.

An event attribute filter consists of a set of filters for each event
attribute.  By default, i.e., if no filter is specified, all attribute values
are passed.  If a filter is specified for an attribute, the filter specifies
either a bitwise mask value, or a list of acceptable values or ranges of values
for the attribute.  An event is passed if and only if all event attribute
filters pass the event.  If a list of acceptable values are specified, the
list may be any length, with little impact on filtering efficiency.

The basic event attribute filter syntax consists of a list of attribute
filters, e.g.:

        attribute = values [, attribute = values ...]

where \fIattribute\fR is the attribute name, e.g., a position attribute,
time, energy, and so on, and \fIvalues\fR is a mask or list of values.
Mask values are integers prefixed by `%', e.g.,

        attribute = %1003B

Note that the mask may be specified in decimal (the default), octal (`b' or
`B' suffix), or hex (`x' or `X' suffix), in accord with the usual IRAF
conventions.  The meta-characters used in selection expressions have been
selected to avoid or at least minimize the need to quote such expressions
in CL commands.

A specific value or list of values may be specified as a
simple integer constant, or comma delimited list of constants, e.g.:

.nf
        attribute = 3
or
        attribute = 3, 5, 20X
.fi

Ranges are specified using the `:' notation, e.g.,

        attribute = 3, 5, 8:11

A `!' may be prepended to indicate the opposite, i.e., "everything but":

.nf
        attribute = !%14B
or
        attribute = 3, !1:10
.fi

Open ended ranges may be used to indicate that the range includes all values
less than or equal to or greater than or equal to the given value, e.g.,

        attribute = :100

denotes all values less than or equal to 100.

File inclusion or \fImacro expansion\fR is denoted by a C-like function call
notation, e.g.,

.nf
        macro()
or
        macro(a,b)
.fi

Any arguments are expanded via string substitution when the text of the macro
or include file is expanded.  Include files should have the extension ".qpm".
Macros are permitted only if the variable \fBqpinit\fR is defined in the user's
environment, the string value consisting if the filename of the user's QPOE
macro file.  In a reference to a macro \fImacro\fR, QPOE will look first in
the macro definitions file pointed to by \fIqpinit\fR for the named macro,
then it will look for the file "\fImacro\fR.qpm" in the current directory.

Parenthesis are optional and may be included to, for example, make attribute
value lists more easily identifiable.  If a line ends in a comma or backslash
continuation is assumed; blank lines and comment lines are ignored.
The syntax for attributes with floating point values is identical to that
for integers except that mask values are not allowed.

.NH 4
Region Specification

    QPOE does not itself contain any syntax-level support for specifying
region masks, e.g., via a list of include and exclude circles and other
shapes.  The reason for this is that it is too difficult to come up with
a sufficiently general scheme at the level of an interface like QPOE;
there are too many ways to specify regions, hence in general such region
specification must be done at the applications level.  QPOE does however
include very general and efficient support for region analysis provided the
region mask is input already encoded into a PLIO binary mask.  Since PLIO
includes high level primitives for defining masks in terms of include and
exclude circles, boxes, lines, polygons, etc., it is easy to extend QPOE at
the applications level to include support for a region specification language
tailored to the specific application.

While QPOE cannot itself process a user defined region description to create
new region masks, it is possible to \fIselect\fR from any number of region
masks if these are prepared in advance using other systems facilities or
applications programs.  PLIO region masks may be stored in the QPOE header
as named parameters of type opaque binary array, or they may be stored in
external binary files.  The region mask to be used may be specified by
including an assignment of the form

        mask=[\fIparam\fR|\fIfile\fR.pl]

in the selection expression.  Unless otherwise specified, this region mask 
will be combined with the default rejection mask for the poefile (the final
mask will be the region mask \fIand\fR-ed with the \fInot\fR of the rejection
mask).

.NH 4
Predefined Selection Keywords

    While the syntax of a selection expression is an inherent part of the
QPOE interface, the names of the event attributes used in selection
expressions are logically part of the event structure, and ideally should
be stored with the data and used by the interface only to determine the
attribute datatypes and offsets into the event structure when the selection
expression is compiled at runtime.  We should not really be documenting the
specifics of the POE external data structures here, but in QPOE these data
structures are wired into the interface, so it is appropriate to do so.

The following \fIstandard event attributes\fR are defined.  Minimum match
abbreviations are of course permitted.  The keyword \fIpi\fR is an acceptable
alias for \fIenergy\fR (\fIpi\fR and \fIpha\fR are examples of discipline
dependent terminology which should be associated with the data and not the
interface).

.nf
        X               short           range in X (PO coords)
        Y               short           range in Y (PO coords)
        TIME            real            time event was recorded
        ENERGY,PI       int             energy of event
        PHA             int             pulse height
.fi

The event attributes may also be referred to using the generic notation
[\fIsir\fR]\fIN\fR, which refers to each attribute by its datatype and
struct offset (byte units, zero indexed) rather than by name.  For example,
if the event attributes shown above are assumed to be shown in the order
in which the fields are stored in the event struct, the \fItime\fR field
could also be referred to as \fIR4\fR, and \fIpha\fR as \fII10\fR.  This
crude but effective technique may be used to reference any private
(nonstandard) fields of the event struct in selection expressions.

The following additional, non-event keywords are defined:

.nf
        BLOCK           int             \fIqp_readpix\fR blocking factor
        MASK            string          region mask to be used
        FILTER          string          region filter to be used
        REJMASK         string          rejection mask to be used
        REJFILTER       string          rejection filter to be used
.fi

The default values for these parameters are taken from datafile header
parameters of the same name, if such are found.  Masks are specified either
by the name of a header parameter of type opaque binary array (containing
an encoded PLIO mask), or by the name of a PLIO mask file, extension ".pl".
Named filters are specified by the name of a header parameter of type char
array, or by the name of a text file (extension ".qpf"), where in either
case the named object contains the selection expression text.

.NH 3
Interface Set/Stat Parameters

    The internal parameters for the QPOE interface, and all user accessible
data structures, e.g., the event and other structures, are defined in the
global system include file \fB<qpset.h>\fR.  This file should be referred to
for up to date documentation on these definitions and structures; the
discussion which follows may not be kept up to date.

The following interface parameters may be accessed via the \fIqp_seti\fR and
\fIqp_stati\fR procedures:

.nf
        QP_XRESOLUTION          resolution of an event x-coordinate
        QP_YRESOLUTION          resolution of an event y-coordinate
        QP_LENEVENT             length of an event structure
        QP_LENINDEX             resolution of the event list index
        QP_BUCKETSIZE           event list bucket size, nevents
        QP_PAGESIZE             datafile page size, bytes
        QP_CACHESIZE            number of buffers in data buffer cache
        QP_MAXFILES             max lfiles in datafile (fixed)
        QP_NFILES               query number of lfiles in datafile
        QP_NPAGES               query number of pages in datafile
        QP_FREEPAGES            query number of free pages in datafile
.fi

The parameters shown above may be set only at datafile creation time.
The X and Y resolution parameters define the range of event x,y coordinates.
The resolution of the event list index is set by QP_LENINDEX, and may be less
than the full resolution of the event pixel Y-coordinate.
The remaining parameters control how storage is physically allocated in the
datafile and in any event lists.

.NH 2
Detailed Design
.NH 3
Event Attribute Filtering

    The point of event-attribute filtering is to test an event to see if it
satisfies a user defined event selection expression.  A selection expression
may be decomposed into a list of simple, independent expressions for the
individual event attributes; the event satisfies the full expression only
if the value of each event attribute satisfies the associated attribute
expression.  Currently, attribute expressions are limited to bitmasks, lists
of acceptable values, or lists of ranges (inclusive) of acceptable values.

        attr1=expr, attr2=expr, ...

The highly constrained nature of event-attribute expressions makes expression
evaluation straightforward and fast.  Expression evaluation is implemented
by logically negating each attribute expression and testing each in turn;
expression evaluation ends either when an attribute test fails, in which case
the event is rejected, or when the end of the attribute expression list is
reached, in which case the event is passed.

The obvious way to implement such an expression evaluator is with a simple
interpreter.  The expression is parsed and compiled to produce a simple
interpreter program, using the instructions shown in the figure below.

.ks
.nf
        \fIinstruction     arguments\fR

        MSK[sir]        offset  maskval         bitwise mask test
        EQL[sir]        offset  value           equality test
        LEQ[sir]        offset  value           less than or equal
        GEQ[sir]        offset  value           greater than or equal
        RNG[sir]        offset  lowval highval  range test (inclusive)
        LUT[sir]        offset  lut             lookup table
        NOT                                     invert test
        RET                                     return, pass event
.fi
.ke

An interpreter program consists of a series of these instructions.  The
\fIoffset\fR argument gives the offset of the event attribute (field) to
be tested; the datatype of this field must match that of the instruction
and of the data argument or arguments, if any.  Only event attributes for
which restricted values were specified are tested, hence the cost of the
evaluator depends only upon the complexity of the expression to be evaluated.
An interpreter of this type can be coded very efficiently as a switch-case
statement (jump table) within an optimized DO-loop.

Simple attribute value tests are most efficiently coded as several \fIMSK\fR,
\fIEQL\fR, etc., instructions.  The only case of any complexity is where the
attribute has a long list of acceptable values or ranges of values.  This is
most efficiently coded using a lookup table, using the \fILUT\fR instruction
shown in the figure.  A lookup table test may be preceded by a range test to
limit the size of the lookup table required.  For an integer or short integer
attribute, the lookup table will be a \fIboolean\fR table containing one entry
for each possible value of the attribute in the range spanned by the table.

Use of a lookup table for floating point attributes is more difficult since
an enormous lookup table might be required to preserve the resolution of
the floating point numbers used to define ranges.  The solution is to employ
an \fIinteger\fR (rather than boolean) lookup table of \fIreduced resolution\fR.
The floating point value of the attribute to be tested is mapped into a bin of
the lookup table.  The integer value of the table entry has one of the
following values:

.ks
.nf
        0               Reject all FP numbers mapping to this bin.
        1               Accept all FP numbers mapping to this bin.

        N               Some of the FP numbers mapping to this bin are
                        legal, and some are not.  The value N is the
                        address of a segment of interpreter code to be
                        executed to test a FP value mapped to this bin.
.fi
.ke

The performance of this algorithm for floating point table lookup depends
upon the frequency with which 0 or 1 is encountered as the table value during
lookup; if 0 or 1 is encountered most of the time, then a floating point LUT
test is comparable in expense to an integer LUT test.  But since we already
have to map a floating point number into an integer space of reduced
resolution, we can easily vary the resolution of the lookup table,
increasing the resolution of the table until the desired level of efficiency
is reached (the interpreter execution time for case N is pretty fast in any
case, so this is not critical).

In summary, the expense of event-attribute filtering is directly proportional
to the number of attribute tests to be performed.  An arbitrary number of
values or ranges of values may be specified for an attribute with little if
any affect on performance, even for floating point attributes (e.g., for
time-tagged quality filtering).

.NH 3
Region Filtering

    Region filtering is implemented in QPOE by the PLIO interface, which is
documented elsewhere.  PLIO permits regions of arbitrary complexity to be
described and used for event filtering, with little overhead beyond that
already present for i/o on a large event list with no region filtering.
This assumes only that the event list is position ordered, and that the
region mask is specified in the PO (position ordered) coordinate system.
This makes it possible for QPIO to use the mask to reduce the number of
events to be examined; event attribute filtering is performed only on those
events read through the region mask (this is similar to masked image i/o,
i.e., the MIO package).

If the event structure supports multiple coordinate systems and the region
mask refers to a non-PO coordinate system, then the only approach is to first
perform event-attribute filtering on the non-positional event attributes,
then for each event passing the event-attribute filter, fetch the mask value
corresponding to the x,y coordinates of the event.  This is still an efficient
technique since only mask pixel lookup is required (no complicated region
list traversal is involved), but it will be significantly less efficient than
PO region filtering since we cannot take advantage of position ordering to
reduce the number of events to be examined, and the overhead of accessing
the region mask will be greater.

.NH 3
Datafile Layout and Access
.NH 4
File Structure

    The QPOE file structure is private to the QPOE interface and is discussed
here only for the purpose of detailing (and documenting) the design of the
interface.  The QPOE file is a random access, dynamically extendable, binary
file.  Under QPOE these files will be partially, but not completely, machine
independent, hence file sharing by machines of different architectures will
not be provided initially.  This will be rectified when management of the
datafile is later turned over to DFIO.

To provide a reasonable degree of flexibility, QPOE contains many
variable length data structures, e.g., there may be any number of header
parameters, including array valued parameters of arbitrary size.  New header
parameters may be added at any time, and new data may be appended to array
parameters at any time.  This flexibility places certain demands upon the
low level file manager used to maintain these data structures in the datafile.

All access to the physical datafile is via a low level binary
\fIfile manager\fR.  The purpose of the file manager is to implement a
restricted implementation of the binary file abstraction upon a single host
level binary file.  This provides the "lightweight" binary file mechanism we
need for QPOE.  Since the file manager is a low level facility, it is
implemented using only the low level asynchronous i/o facilities provided
by FIO to read and write file pages, once the file has been opened.

The file manager provides routines for creating new datafiles, and for
creating, deleting, etc., \fIlightweight files\fR (lfiles) within a datafile.
Storage for lfiles is allocated in units of datafile \fIpages\fR.  For each
data page in the datafile there is an entry in the datafile \fIpage table\fR.
The page table itself is stored as an lfile (\fIlfile zero\fR) in the data
pages.  Files at the file manager level are known only by their file number;
association of these file numbers with file names is left up to the higher
level code, and in the case of QPOE is done with the symbol table (which is
also stored as an lfile).

.ks
.nf
         +-----------------+
           datafile header              fixed size
         +-----------------+
              file table                fixed size
         +-----------------+
                  |
              data pages                data and page table pages
                  |                        (arbitrarily large)
                  v
.fi
.ke

The page table is a vector mapping datafile pages by file offset onto lfile
file numbers; the value of each page table entry is the file number of the
lfile to which the page is assigned.  When the file manager opens an lfile
it scans the page table, extracting the page numbers of the pages assigned
to the lfile, to form a vector mapping lfile page offsets directly onto
datafile page offsets.  New pages are always allocated at the end of the
datafile, and new lfiles are always allocated at the end of the file table,
hence lfile deletion will leave "holes" (unused storage) in both the datafile
pages and file table.  A \fIrebuild\fR operation is required to reclaim the
space occupied by these holes.  Deleted files are recoverable by merely
revalidating their file table entries.

Every variable size object managed by QPOE is stored in the datafile as a
distinct lfile.  Since storage for lfiles is allocated in units of file pages,
the minimum amount of storage used by a variable length object is 0 or 1 page.
Examples of variable size objects are the SYMTAB symbol table
used to describe the contents of the datafile header (and any other symbols
used by QPOE), the static data storage area (used to store the values of
scalar and static array valued header parameters), and individual variable
length arrays.  Note that each variable length array is stored in the datafile
as a separate lfile; if the maximum size of an array is less then the page
size, it will be more efficient to store it as a static array.

The most important example of a variable length array is the main event list
of the poefile.  To improve i/o efficiency and speed selection, the event
structs stored in an event list are grouped together into \fIbuckets\fR,
as discussed earlier in section 2.3.1.2.  Each bucket will always occupy an
integral number of file pages.  Storage for buckets is allocated contiguously
in the datafile, and buckets are always read and written to disk in a single
i/o transfer.

The most important physical datafile parameters are hence the page size and
the bucket size.  A larger page size can improve i/o efficiency and reduce the
size of the page table, but can lead to significant wasted space if there are
many variable length arrays.  Since the i/o system will move entire large
blocks of pages to and from disk whenever possible, use of a small page is
normally preferred.  A large bucket size improves i/o efficiency for event
lists, but if the bucket size is too large then bucket searching takes longer,
and selection efficiency may decrease.

.NH 4
File Manager

    The function of the file manager is to map a set of lfiles onto a single
random access host binary file.  The file manager must keep track of the size
and type of each file, and whether or not it has been deleted.
In addition, the file manager must maintain a page table for the entire
datafile, noting the lfile to which each page is assigned.  While an lfile
is open the file manager must maintain the page vector for that lfile so that
lfile offsets may be mapped directly onto datafile offsets.

The number of lfiles is fixed at datafile creation time, and lfiles are
referred to by file number.  File number zero is the datafile page table;
the first user lfile is number one.  A datafile with a max file count of one
would actually contain two lfiles, counting the page table.

The file manager interface is summarized in the figure below.  A new datafile
may be created or an existing datafile opened with \fIfm_open\fR.
If a new datafile is being created the page size and max file count may be
changed from their default values with calls to \fIfm_seti\fR, and the values
of these and other parameters may be queried at any time with \fIfm_stati\fR.  
An opened datafile may be copied with \fIfm_copyo\fR, omitting deleted lfiles
and rendering file segments contiguous.  The page size and max file count
may be changed in a copy operation if desired.

The \fIfm_access\fR, \fIfm_rename\fR, and \fIfm_delete\fR routines perform
the indicated operation upon the named datafile.  The \fIfm_rebuild\fR
routine rebuilds a datafile, discarding deleted structures and coalescing
storage for objects.  This routine, as well as \fIfm_copy\fR, are built upon
on the lower level routine \fIfm_copyo\fR,  which does the real work, and
which allows the structural attributes of the new datafile to be specified
in \fIfm_seti\fR cals.

All i/o to lfiles is via the six routines beginning with \fIfm_lfopen\fR in
the figure below.  These routines constitute a FIO binary file driver for
lfiles, and may be called directly, or passed to the FIO routine \fIfopnbf\fR
to open an lfile as a binary file (\fIfm_lfname\fR should be called first
to construct a pseudo-filename for the lfile so that \fIfm_lfopen\fR can
reconstruct the file manager descriptor, lfile number, and lfile type).
Note that the lfile driver routines are unbuffered and (potentially)
asynchronous, and that i/o must be in units of datafile pages.
(See the buffer cache routines described in the next section for a higher
level facility for i/o to lfiles).

.nf
        yes|no = fm_access (datafile, mode)
                  fm_rename (datafile, newname)
                    fm_copy (datafile, newname)
                  fm_delete (datafile)
                 fm_rebuild (datafile)

               fm = fm_open (datafile, mode)
                    fm_seti (fm, param, ival)
            ival = fm_stati (fm, param)
		   fm_debug (fm, out, what)
                   fm_copyo (fm, fm_to)
                    fm_sync (fm)
                   fm_close (fm)

       lfile = fm_nextlfile (fm)
                  fm_lfname (fm, lfile, type, lfname, maxch)

                  fm_lfopen (lfname, mode, lf)
                 fm_lfstati (lf, param, ival)
                 fm_lfaread (lf, buf, nbytes, offset, status)
                fm_lfawrite (lf, buf, nbytes, offset, status)
                 fm_lfawait (lf, status)
                 fm_lfclose (lf, status)

                  fm_lfstat (fm, lfile, statbuf)
                fm_lfdelete (fm, lfile)
              fm_lfundelete (fm, lfile)
.fi

In a sense, all lfiles exist as zero length files when the datafile is
created, since the lfile descriptors are preallocated and the files are
known only by number.  Lfiles become interesting when they are opened as
files with \fIfm_lfopen\fR, and data is written into the file.  An lfile
may be deleted with \fIfm_lfstat\fR.  All this does is set the delete bit
in the lfile descriptor, hence a deleted lfile may later be undeleted with
\fIfm_lfundelete\fR.  The data in a deleted lfile is not lost until the lfile
is again opened and written into, or the datafile is rebuilt.  Information
on a specific lfile (size, type, etc.) may be obtained with \fIfm_lfstat\fR.

There is nothing about the file manager which is specific to QPOE, so it is
implemented as a separate, standalone facility, and may be used in applications
other than QPOE.

.NH 4
Buffer Cache

    For reasons of efficiency, QPOE maintains portions of the datafile in
memory buffers while a datafile is open.  The main QPOE descriptor, symbol
table, and file manager descriptor and page table are maintained in special
runtime data structures internal to the respective interfaces.  All other
data is stored in lfiles and accessed only upon demand.  In particular,
storage for all static (non variable length) QPOE header parameters is
maintained in a single lfile, and storage for each variable length parameter
is allocated in a separate lfile.

Since most access to QPOE header parameters is via simple gets and puts to
named parameters, lfile access is handled by QPOE transparently to the client
applications program.  To avoid excessive disk i/o when randomly accessing
the datafile, it is desirable for QPOE to maintain a cache of several lfile
data buffers, e.g., so that accesses to a series of static parameters or
repeated accesses to read or write different parts of an array parameter
should incur minimal disk accesses.  This buffer cache is implemented in
QPOE by simply opening each lfile as a file under FIO, leaving it up to FIO to
manage the file buffer, and maintaining a LRU cache of open lfiles in QPOE.
The number of buffers (open lfiles) is controlled by the QP_CACHESIZE
parameter.  Since the lfile buffer cache is a general datafile related
facility, it is implemented by the file manager.

.ks
.nf
              fd = fm_getfd (fm, lfile, mode, type)
                   fm_retfd (fm, lfile)
                 fm_lockout (fm, lfile)
                 fm_debugfd (fm, out)
.fi
.ke

The \fIfm_getfd\fR routine maps an lfile onto a file descriptor.  A file
descriptor is opened on the lfile only when necessary.  Once opened, an lfile
remains in the cache until forced out by the LRU replacement algorithm,
or the datafile is closed.  Removal of an lfile from the cache (closing the
associated file descriptor) is permitted only after a call to \fIfm_retfd\fR;
calling this routine does not immediately close the file, it only permits it
to be closed.  Most calls to \fIfm_getfd\fR should return a file descriptor
immediately, with very little overhead, with an already active file buffer,
hence repeated calls to the cache manager and FIO may be made without
incurring any disk accesses.

Note that lfiles may be opened on file descriptors via direct calls to the
file manager, regardless of whether these lfiles are already open in the
buffer cache.  This allows two or more independent file buffers to be
simultaneously active on the same lfile, but opens the possibility of loss
of data if the buffers overlap.  If this is a problem, the routine
\fIfm_lockoutfd\fR may be called to prevent inadvertent use of an lfile by
the cache.  This should be followed by a call to \fIfm_retfd\fR to clear the
lockout bit once the reason for the lockout (usually a noncached lfile open)
is gone.  The routine \fIfm_debugfd\fR will print information on \fIout\fR
describing the contents of the buffer cache.

.tp 24
.NH 3
Interface Structure
.NH 4 1
Header Access Package (QP Routines)

    The structure of the general QPOE routines (mostly header access) is
illustrated in the figure below.

.ks
.nf
                                +--------+
                                |   QP   |
                                +--------+
                                 /      \
                                /        \
                        +--------+      +--------+
                        | SYMTAB |      | BCACHE |
                        +--------+      +--------+
                                             |
                                      +--------------+
                                      | FILE MANAGER |  
                                      +--------------+

              Figure 1.  Structure of the Header Access Routines
.fi
.ke

To fulfill a get or put header access, QPOE will access the symbol table
(SYMTAB) to lookup the symbol name and determine the symbol datatype, nelem,
lfile number, and lfile file offset where the value is stored.  The buffer
cache (BCACHE) and FIO are then called to access the value of the parameter
in the datafile.

.NH 4
Filtered Event I/O Package (QPIO)

    The structure of the filtered event i/o package (QPIO) is illustrated in
the figure below.

.ks
.nf
                                +--------+
                                |  QPIO  |
                                +--------+
                                 /   |  \       
                      __________/    |   \__________
                     /               |              \
                +--------+      +--------+      +--------+
                |  PLIO  |      |  QPEX  |      | BCACHE |
                +--------+      +--------+      +--------+
                                     |               |
                                +--------+    +--------------+
                                | SYMTAB |    | FILE MANAGER |  
                                +--------+    +--------------+

                     Figure 2.  Structure of QPIO Routines
.fi
.ke

In the typical \fIgetevents\fR call, QPIO will call PLIO to determine the
next region of the stored image (event list) to access, then if the event
data is not already in a data buffer, FIO is called to read the data (bucket),
using the event list index, an integer array valued parameter, to determine
what bucket to read.  The events in the bucket are then examined and optionally
filtered via calls to QPEX, returning pointers to the passed events in an
output argument.  This process terminates when either the mask value changes
or at least one event has been returned and a new bucket is required to 
continue reading.
