% -*-TeX-mode-*-
%--------------------------------------------------------
%  UNIVERSAL_PLANE_FORMAT.TEX - Write up of a "universal" LLVS plane format
%  Robert Heller Created on Mon Jul 10 15:02:47 1989
%   Last mod - 
%	4-30-1993 - add in full copyright notice RPH
%--------------------------------------------------------
%  Contents:
%--------------------------------------------------------
% (c) Copyright 1989 by The University of Massachusetts
%--------------------------------------------------------
%
%
%           Copyright 1989,1993 University of Massachusetts.
%                         All rights reserved.
%
%     Permission to copy and modify this software and its documen-
%     tation only for internal use in your organization is hereby
%     granted, provided that this notice is retained thereon and
%     on all copies.  UMASS makes no representations as to the sui-
%     tability and operability of this software for any purpose.
%     It is provided "as is" without express or implied warranty.
%
%     UMASS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
%     INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FIT-
%     NESS.  IN NO EVENT SHALL UMASS BE LIABLE FOR ANY SPECIAL,
%     INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY OTHER DAMAGES WHAT-
%     SOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
%     IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
%     ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PER-
%     FORMANCE OF THIS SOFTWARE.
%
%     No other rights, including, for example, the right to redis-
%     tribute this software and its documentation or the right to
%     prepare derivative works, are granted unless specifically
%     provided in a separate license agreement.
%
%     Copyright 1989,1993, University of Massachusetts. All rights
%     reserved.
%
%
%
%
\documentstyle[12pt]{article}
\setcounter{tocdepth}{4}
\begin{document}
\title{Universal LLVS Plane File Format}
\author{Robert Heller}
\date{\today}

\maketitle
\newcount\savetolerance
\savetolerance=\tolerance 

\tolerance=3500

{\small
\centerline{Copyright \copyright 1989,1993 by the University of Massachusetts}
\centerline{All rights reserved.}

\vskip 12pt

Permission to copy and modify this software and its documentation
only for internal use in your organization is hereby
granted, provided that this notice is retained thereon and
on all copies.  UMASS makes no representations as to the suitability
and operability of this software for any purpose.
It is provided "as is" without express or implied warranty.

\vskip 12pt

{\bf UMASS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
IN NO EVENT SHALL UMASS BE LIABLE FOR ANY SPECIAL,
INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY OTHER DAMAGES WHATSOEVER
RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
OF THIS SOFTWARE.}

\vskip 12pt

No other rights, including, for example, the right to redistribute
this software and its documentation or the right to
prepare derivative works, are granted unless specifically
provided in a separate license agreement.

}

\begin{abstract} 


This document describes the format of LLVS plane files in a multi-architecture
environment.  The plane files support different byte orders and different floating
point formats.  A given machine will write planes using its ``native'' format and read
planes written by other machines, doing whatever converesions needed to produce an
in-core representation of the data in native form.
\end{abstract}

\tolerance=\savetolerance


\clearpage




\multiply \baselineskip by 2

\section{Introduction}

This manual describes the format used by the VISIONS group to store image data. Image
data is stored as ``plane'' files, each of which holds one or more image planes.
Image planes are two dimensional arrays of picture elements, know as ``pixels''. There
are five types of planes: bit (one bit per pixel), unsigned byte (8 bits per pixel),
short integers (16-bits, signed, per pixel), integer (32-bits, signed, per pixel), and
floating (32-bit floating point).  All of the pixels in a given plane are of the same
type.  Two data structures are used to hold information about a plane while it is in
main memory:  a {\tt PLANE} structure (which points at the pixels themselves) and a
{\tt PLANE\_INFO} structure which describes the plane.  These structures are defined
as follows:\footnote{From ``llvs\_per\_plane.h''}

\begin{verbatim}
/*------------------------------------------------------
 *
 *  PLANE
 *    This structure describes the format of a plane passed
 *  to C. Each plane is passed to the C routine as a separate
 *  argument of type PLANE.
 *
 *-------------------------------------------------------
 */

typedef struct {
    int plane_base[1];       /* plane data starts here */
    } PLANE;

/*----------------------------------------------------------
 *
 *  PLANE_INFO
 *   Other information is available for each plane. This
 *  information is passed in an array of PLANE_INFO[MAXPLANE].
 *  The Lisp function BUILD-PLANE-INFO-VECTOR-C may be used to
 *  build the array. The background-value is passed as a C 
 *  float value if the plane is a floating point plane, C int
 *  type otherwise.
 *
 *----------------------------------------------------------
 */

/* plane info struct */
typedef struct {
    long int datatype;      /* data type */
#define LLVS_BIT   0        /* bit type */
#define LLVS_BYTE  1        /* unsigned byte type */
#define LLVS_SHORT 2        /* signed 16 bit type */
#define LLVS_INT   3        /* integer type */
#define LLVS_FLOAT 4        /* float type */
#define FLOAT 4             /* float type */
    long int level;         /* plane level */
    long int row_location;  /* row location */
    long int column_location; /* column location */
    long int row_dimension; /* row dimension */
    long int column_dimension; /* column dimension */
    union {
        long int fixnum;    /* background value */
        float flonum;       /* dito, but as a flonum */
    } background;
    } PLANE_INFO;

\end{verbatim}

Additional information about planes are stored in a Common LISP association
list\footnote{{\sl Common LISP The language}, Guy L. Steele Jr., pp 279-281}.  The
association list is used to store information such as the plane statisics (minimum
value, maximum value, etc.) and other user-defined information.

When image planes are written to disk files, this information needs to be preserved in
a form that can be read on many different systems, since the one thing all of the
various machines have in common is a file system.  Planes are written to disk files as
four logical records:  a plane file header record, an association list record, a
plane size record, and a plane data record.  The plane file header record is a
fixed-length record containing 32 bytes.  It defines the plane's type and contains
information about how the plane was written (byte sex\footnote{byte storage order}, 
floating point format used), the plane's size, its location, its relative resolution,
etc.  The association list record contains the plane's association list as ASCII text.
The plane size record defines the plane's dimensions.  And finally, the plane data
record contains the pixel values themselves.

\section{The Plane File Header}

This structure is defined in the file {\tt llvs\_plane.h}.

\begin{verbatim}

/* some basic types: */

typedef unsigned char llvs_ubyte;    /* unsigned bytes
                                       (8-bits) */
typedef long int llvs_integer;       /* long integer
                                        (32-bits) */
typedef float llvs_single_float;     /* 32-bit floating
                                        point */
typedef short int llvs_half_integer; /* short integer
                                        (16-bits) */

/* primary header record: LLVS Plane files start with this */

typedef struct {
    llvs_ubyte ptype;           /* datatype of plane */
#define LLVS_PLF_BIT   0        /* bit type */
#define LLVS_PLF_BYTE  1        /* unsigned byte type */
#define LLVS_PLF_SHORT 2        /* signed 16 bit type */
#define LLVS_PLF_INT   3        /* integer type */
#define LLVS_PLF_FLOAT 4        /* float type */
    llvs_ubyte bsex;            /* byte sex (byte order) */
#define LLVS_LOW_BYTE_FIRST 0   /* low byte first 
                                   (VAX, 80x86) */
#define LLVS_HIGH_BYTE_FIRST 1  /* high byte first
                                  (680x0, Spark) */
    llvs_ubyte floatfmt;        /* floating point format */
#define LLVS_DEC_SINGLE_FLOAT 0 /* DEC 32-bit floating point
                                   format (PDP-11, VAX) */
#define LLVS_IEEE_SINGLE_FLOAT 1 /* IEEE 32-bit floating 
                                    point format (Sun, 
                                    Sequent, Spark, TI
                                    LISPM) */
    llvs_ubyte reserved;        /* reserved byte - must be
                                   ZERO at present */
/* the remaining fields are in the format indicated above */
    llvs_integer pl_level;      /* plane level */
    llvs_integer row_location;  /* row offset of plane */
    llvs_integer col_location;  /* column offset of plane */
    union {
        llvs_integer iback;     /* integer background value
                                 */
        llvs_single_float fback; /* floating point background
                                    value */
        } background;      /* plane background value */
    llvs_integer alist_length;  /* length of alist record */
    llvs_integer data_length;   /* length of data + size 
                                   header */
    llvs_integer multi_plane_flag; /* Multi-plane flag - if 0
                                      this is the last/only
                                      plane otherwise, 
                                      additional planes are 
                                      stored after this one.
                                    */
    } LLVS_PLANE_FILE_HEADER;

\end{verbatim}

The first three bytes of this header record describe the format of the plane data:
its data type, its ``byte sex'' (byte ordering of multi-byte numerical values), and
the floating point number format used for floating point numbers.  

\section{The Association List Record}

Following the plane file header record is a variable-length record containing the
plane's association list. This record contains ASCII text which is the printed
representation of a Common LISP association list. This record is not bounded - code
that reads plane files should make sure that the buffer used is large enough, as
indicated by the {\tt alist\_length} field on the header record. 

\section{The Plane Size Record}

Following the association list record is the plane size record:

\begin{verbatim}

typedef struct {
   llvs_integer datatype_again; /* plane data type (redundant) */
   llvs_integer row_dimension;  /* plane row dimension */
   llvs_integer col_dimension;  /* plane col dimension */
   } LLVS_PLANE_SIZE_HEADER;

\end{verbatim}

This record describes the size of the plane in {\em elements}, giving the plane
dimensions.  The plane data is actually just a two-dimensional array of numerical
values, each element of which represents a single picture element (called a
``pixel'').

\section{The Plane Pixels}

After the size record, comes the plane data itself. This is also a variable length
record. Its size is 12 bytes less than the value of the {\tt data\_length} field
in the plane file header record. (The {\tt data\_length} field in the plane file
header record includes the size of the plane size record.) The data is stored in
``scan'' order: top-to-bottom, left-to-right. The first element is the top-left
picture element, the next element is the next picture element to the right, etc., that
is the data is stored by rows, starting with the top row of the image.

\section{Multi-Plane Files}

Some image data consists of a group of related planes, such as the red, green, and
blue planes of a color image or the left and right views of a stereo pair, etc. For
ease of transport and handling, it is posible to store multiple planes in one file.
The {\tt multi\_plane\_flag} field in the plane file header is used to indicate
when multiple planes are stored in a given file. If this field is equal to zero, then
only one plane is stored in the file. If this field is not equal to zero, then another
plane file header record, association list record, plane size record, and plane data
record follow after the current plane data record. The last plane has a plane file
header record with its {\tt multi\_plane\_flag} field set to zero.  The value
stored in this field is the number of remaining planes in the file.  That is, if the
file contains three planes, the value in the first plane's {\tt multi\_plane\_flag}
header field is 2, the value in the second planes' {\tt multi\_plane\_flag} header
field is 1, and the value in the third (and last) {\tt multi\_plane\_flag} header
field is 0.

\section{Summary}

A LLVS plane file looks like this:

\vspace{12pt}

\begin{tabular}{|c|c|} \hline
Plane File Header Record & 32 bytes \\ \hline
Association List Record & {\tt alist\_length} bytes \\ \hline
Plane Size Record & 12 bytes \\ \hline
Plane Data Record & $({\tt data\_length}-12)$ bytes \\ \hline \end{tabular}

\vspace{12pt}

This structure is repeated if the {\tt multi\_plane\_flag} field in the Plane
File Header Record is not equal to zero.  The last (or only) Plane File Header Record
has its {\tt multi\_plane\_flag} field set to zero.

\appendix

\clearpage

\section{Actual C code to read in a single plane}

This is the actual C code\footnote{From ``read\_write\_plane.c''.} to read in a plane
file. The function {\sl read\_plane} takes as arguments a pointer to a pointer to a
{\tt PLANE} structure, a pointer to a pointer to a {\tt PLANE\_INFO} structure, a
pointer to a string pointer, and a string pointer. This function allocates space (via
{\tt malloc()} and {\tt calloc()}) for the {\tt PLANE}, the {\tt PLANE\_INFO}, and the
associations list and it side-affects its first three arguments, which should be
either addresses of variables or array or structure slots. {\sl read\_plane} returns
{\tt -1} on error and the value of the {\tt multi\_plane\_flag} on success.

\begin{verbatim}

#define BLOCKSIZE 512 /* max number of bytes to read at a time */

 /*
  * read_plane(plane,plane_info,associations,filename) - read
  * in a plane file. plane is a pointer to a pointer to a 
  * PLANE object, plane_info is a pointer to a pointer to a 
  * PLANE_INFO object, associations is a pointer to a pointer
  * to char, and filename is a pointer to a char.
  * plane, plane_info, and associations are set to malloc'ed
  * space, so should be addresses of cloberable pointers (i.e.
  * addresses of variables or structure fields, etc.).
  */

read_plane(plane,plane_info,associations,filename)
PLANE **plane;
PLANE_INFO **plane_info;
char **associations;
char *filename;
{
\end{verbatim}

First we declare local variables:

\begin{verbatim}
    /* plane file header record structure */
    static LLVS_PLANE_FILE_HEADER header;
    /* plane size header record structure */
    static LLVS_PLANE_SIZE_HEADER size_header;
    int plsize;                 /* # bytes in plane */
    FILE *plfile;               /* plane file */
    llvs_ubyte *data_pointer; /* pointer to data buffer */
    int rbytes, bytesleft;      /* I/O byte counters */
    int need_swap, need_cvt_float; /* flags to indicate if
                                      conversions needed */

\end{verbatim}

Now we open the plane file and read in the plane file header record:

\begin{verbatim}

    /* open file.  abort if open failure */
    plfile = fopen(filename,"r");
    if (plfile == NULL) return(-1);
    /* read header record */
    if (fread(&header,32,1,plfile) != 1) return(-1);
\end{verbatim}

Next we generate the conversion flags.  {\tt LLVS\_NATIVE\_BYTE\_SEX} and {\tt
LLVS\_NATIVE\_FLOATFMT} are macros defined in {\tt llvs\_plane.h} under control of
contitional compilation macros defining the machine type (i.e. VAX, SUN, or SEQUENT,
etc.).

\begin{verbatim}
    need_swap = header.bsex != LLVS_NATIVE_BYTE_SEX;
    need_cvt_float = header.floatfmt != LLVS_NATIVE_FLOATFMT;

\end{verbatim}

Now we convert the remainder of the header to native internal storage format:

\begin{verbatim}
    if (need_swap) swap_longs(&header.pl_level,7);
    if (header.ptype == LLVS_PLF_FLOAT && need_cvt_float) 
        cvt_floats(&header.background,1,header.floatfmt,
                   LLVS_NATIVE_FLOATFMT);
\end{verbatim}

Now we compute the size of the plane in bytes, allocate the {\tt PLANE\_INFO}
structure, fill in the {\tt PLANE\_INFO} structure, and allocate memory for the plane
itself.

\begin{verbatim}
    /* compute plane size */
    plsize = header.data_length - 12;
    /* allocate plane info struct */
    *plane_info = (PLANE_INFO *) malloc(sizeof(PLANE_INFO));
    if (*plane_info == NULL) return(-1);
    /* fill in plane info slots */
    (*plane_info)->datatype = header.ptype;
    (*plane_info)->level = header.pl_level;
    (*plane_info)->row_location = header.row_location;
    (*plane_info)->column_location = header.col_location;
    (*plane_info)->background.fixnum = 
        header.background.iback;
    /* allocate plane it self */
    *plane = (PLANE *) malloc(plsize);
    if (*plane == NULL) return(-1);
\end{verbatim}

Now we allocate space for the associations list and read in the associations list
record. This C function does not actually do anything with the associations list.  In
the Common LISP based system, we pass this string to the Common LISP function {\tt
READ-FROM-STRING} and can then access the association list with Common LISP
association list access functions.

\begin{verbatim}
    /* allocate space for association list */
    *associations = calloc(header.alist_length+1,
                           sizeof(char));
    if (*associations == NULL) return(-1);
    /* read in association list */
    data_pointer = (llvs_ubyte *) *associations; /* start of
                                                    buffer */
    bytesleft = header.alist_length; /* number of bytes to
                                        read */
    while (bytesleft > 0) {
        rbytes = bytesleft;
        if (rbytes > BLOCKSIZE) rbytes = BLOCKSIZE;
        if (fread(data_pointer,rbytes,1,plfile) != 1) 
            return(-1);
        data_pointer += rbytes;
        bytesleft -= rbytes;
        }
\end{verbatim}

Now we read in and convert the size header and fill in the plane dimension info.

\begin{verbatim}
    /* read size header */
    if (fread(&size_header,12,1,plfile) != 1) return(-1);
    if (need_swap) swap_longs(&size_header,3);
    /* fill in addition slots in plane info */
    (*plane_info)->row_dimension = size_header.row_dimension;
    (*plane_info)->column_dimension = 
        size_header.col_dimension;
\end{verbatim}

Finally, we read in the plane data itself, converting the data as we go.

\begin{verbatim}
    /* read in plane */
    data_pointer = (unsigned char *) (*plane)->plane_base;
    bytesleft = plsize;
    while (bytesleft > 0) {
        rbytes = bytesleft;
        if (rbytes > BLOCKSIZE) rbytes = BLOCKSIZE;
        if (fread(data_pointer,rbytes,1,plfile) != 1) 
            return(-1);
        if (need_swap && header.ptype == LLVS_PLF_SHORT) 
            swap_words(data_pointer,rbytes >> 1);
        if (need_swap && (header.ptype == LLVS_PLF_INT || 
                          header.ptype == LLVS_PLF_FLOAT))
            swap_longs(data_pointer,rbytes >> 2);
        if (need_cvt_float && header.ptype == LLVS_PLF_FLOAT)
            cvt_floats(data_pointer,rbytes >> 2,
                       header.floatfmt,LLVS_NATIVE_FLOATFMT);
        data_pointer += rbytes;
        bytesleft -= rbytes;
        }
\end{verbatim}

When we are done, we close the file.  {\tt Read\_plane} returns the value of the
multi-plane flag.  This will be zero if this file contained only one plane or a
positive non-zero value indicating the number of additional planes in the file.  {\tt
Read\_plane} returns -1 when it encountes a system error (I/O error or a memory
allocation failure).

\begin{verbatim}
    /* close file */
    fclose(plfile);
    return(header.multi_plane_flag);
    }
\end{verbatim}

\tolerance=5000

There are 3 conversion functions used by this function: {\tt swap\_longs()}, {\tt
swap\_words()}, and {\tt cvt\_floats()}.  These three functions are defined in {\tt
read\_write\_plane.c}.  The first two functions do byte swaping on long ints and short
ints and the third function converts floating point formats.  All three functions
modify the memory pointed at by their first argument.  The second argument is the
count of elelemts to convert.  {\tt cvt\_floats} has two additional arguments:  the
current format of the floating point numbers and the desired (i.e. native) floating
point format.

\tolerance=\savetolerance

\section{Actual C code to write out a single plane}


This is the actual C code\footnote{From ``read\_write\_plane.c''.} to write out a
plane file. The function {\sl write\_plane} takes as arguments, pointers to a {\tt
PLANE} structure, a {\tt PLANE\_INFO} structure, a string containing the associations
list, and a string containing the name of the file to write. If the associations list
string is a {\tt NULL} pointer, the string {\tt "NIL"} is written to the file as the
associations list record.

\begin{verbatim}
  /*
   * write_plane(plane,plane_info,associations,filename) - 
   * write out a plane.
   */

write_plane(plane,plane_info,associations,filename)
PLANE *plane;
PLANE_INFO *plane_info;
char *associations;
char *filename;
{
\end{verbatim}

First we declare local variables:

\begin{verbatim}
    static LLVS_PLANE_FILE_HEADER header;
    /* plane size header record structure */
    static LLVS_PLANE_SIZE_HEADER size_header;
    int plsize;                 /* # bytes in plane */
    FILE *plfile;               /* plane file */
    unsigned char *data_pointer; /* pointer to data buffer */
    int rbytes, bytesleft;      /* I/O byte counters */

\end{verbatim}

Now we open the plane file and setup the plane file header record.  The byte sex and
float format fields are set to the values ``native'' to the current machine.  {\sl
write\_plane} does not convert the data to any particular format.

\begin{verbatim}
    /* open file.  abort if open failure */
#ifdef VMS
    plfile = fopen(filename,"w", "rfm=var");
#else
    plfile = fopen(filename,"w");
#endif
    if (plfile == NULL) return(-1);
    /* fill in plane file header record */
    header.ptype = plane_info->datatype;
    header.bsex = LLVS_NATIVE_BYTE_SEX;
    header.floatfmt = LLVS_NATIVE_FLOATFMT;
    header.reserved = 0;
    header.pl_level = plane_info->level;
    header.row_location = plane_info->row_location;
    header.col_location = plane_info->column_location;
    header.background.iback = plane_info->background.fixnum;
    header.data_length = plane_size(plane_info) + 12;
    if (associations == NULL) associations = "NIL";
    header.alist_length = strlen(associations);
    header.multi_plane_flag = 0;

\end{verbatim}

Now write the header record:

\begin{verbatim}
    /* write header record */
    if (fwrite(&header,32,1,plfile) != 1) return(-1);
    /* compute plane size (for use later) */
    plsize = header.data_length - 12;
\end{verbatim}

Now write out the associations list:

\begin{verbatim}
    /* get pointer to associaions list */
    data_pointer = (llvs_ubyte *) associations;
    /* and length */
    bytesleft = header.alist_length;
    /* write out associations list */
    while (bytesleft > 0) {
        rbytes = bytesleft;
        if (rbytes > BLOCKSIZE) rbytes = BLOCKSIZE;
        if (fwrite(data_pointer,rbytes,1,plfile) != 1) 
            return(-1);
        data_pointer += rbytes;
        bytesleft -= rbytes;
        }
\end{verbatim}

Now fill in and write out the size header record:

\begin{verbatim}
    /* fill in size header */
    size_header.datatype_again = header.ptype;
    size_header.row_dimension = plane_info->row_dimension;
    size_header.col_dimension = plane_info->column_dimension;

    /* write out size header */
    if (fwrite(&size_header,12,1,plfile) != 1) return(-1);
\end{verbatim}

Finally write out the plane's pixels:

\begin{verbatim}
    /* get pointer & size of plane data */
    data_pointer = (unsigned char *) plane->plane_base;
    bytesleft = plsize;
    /* write plane data */
    while (bytesleft > 0) {
        rbytes = bytesleft;
        if (rbytes > BLOCKSIZE) rbytes = BLOCKSIZE;
        if (fwrite(data_pointer,rbytes,1,plfile) != 1) 
            return(-1);
        data_pointer += rbytes;
        bytesleft -= rbytes;
        }
\end{verbatim}

And close the file when we are done.

\begin{verbatim}
    /* close file */
    fclose(plfile);
    return(0);
    }
\end{verbatim}

This little function is used to compute the size of the plane in bytes.

\begin{verbatim}
 /* compute plane size from plane info */

plane_size(plinfo)
PLANE_INFO *plinfo;
{
    int elements;               /* element count */

    elements = plinfo->row_dimension * 
               plinfo->column_dimension;
    switch (plinfo->datatype) {
        case LLVS_BIT: return( (elements + 7) / 8);
        case LLVS_BYTE:
            return( elements * sizeof(unsigned char));
        case LLVS_SHORT: 
            return( elements * sizeof(short int));
        case LLVS_INT: 
            return( elements * sizeof(long int));
        case LLVS_FLOAT: 
            return( elements * sizeof(float));
        }
    }
\end{verbatim}

\end{document}
