/*****************************************************************************

  File:  obj_l2.c

  Description: This file contains about half of the C routines making up the
               Object Layer of the PDS Object Access Library.  The other
               half are in obj_l1.c.  The routines in this file are:

               OaOpenImage
               OaOpenOutputFile
               OaReadArray
               OaReadHistogram
               OaReadHistory
               OaReadImage
               OaReadImagePixels
               OaReadImageFromQube
               OaReadSpectrumFromImage
               OaReadSpectrumFromQube
               OaParseLabelFile
               OaReadObject
               OaReadPartialImage
               OaReadSubTable
               OaReadTable
               OaReportFileAttributes
               OaTransposeTable
               OaUnravelContainer
               OaWriteObject

  Author:  Steve Monk, University of Colorado LASP

  Creation Date:   1 Sept  1994
  Last Modified:  16 Mar   1998

  History:

    Creation - Most of these routines were part of the Alpha Release of
               the OA library.  Several new routines were added in the Beta
               Release.
    11/27/95 - Added OaReadImageFromQube and OaReadSpectrumFromQube. SM
    12/06/95 - Moved OaRealloc to oamalloc.c.  SM
    12/06/95 - Replaced malloc() by OaMalloc() throughout.  SM
    12/11/95 - Added error codes.  SM
    11/06/96 - Redesigned OaWriteObject.  SM
    12/11/96 - Added support for multiband images; added new argument to
               OaOpenImage, OaReadImage; added new routine
               OaReadSpectrumFromImage  SM
    02/02/98 - Fixed OalSeek bug in HFD part of OaReadImagePixels.  SM
    03/09/98 - Qube routines: added BIL and suffix plane support.  SM
    03/16/98 - Added call OaCreateODLTreeFromGif in OaParseLabelFile when
               OalOpenStream detects a GIF file.  SM

*****************************************************************************/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "oal.h"



/*****************************************************************************

  Routine:  OaOpenImage

  Description:  OaOpenImage sets up for reading a partial image from a file.
                OaOpenImage is the first call in the sequence: OaOpenImage,
                OaReadImagePixels (multiple calls) or OaReadPartialImage
                (multiple calls), OaCloseImage. 
                It initializes a stream descriptor, opens the file, and
                positions the file pointer to the start of the image object. 
                It creates the image handle oa_object and initializes its ODL
                tree.

  Author:  Steve Monk, University of Colorado LASP

  Creation Date:  29 Sept  1994
  Last Modified:  11 Dec   1996

  History:

    Creation - This routine was part of the Beta Release of the OA
               library.
    12/11/96 - Added support for multiband images.

  Input: 
         input_node - A pointer to an ODL tree node of class IMAGE.
                      A node somewhere above the input_node must have keywords
                      specifying the data file.  If the image is HFD
                      compressed, the input_node must have a sibling
                      HISTOGRAM node identifiable as the encoding histogram
                      for HFD decompression.

         band       - For multiband images, this is the band to return.
                      1 <= band <= BANDS  Ignored for monochrome images.

         The global variable Oa_profile is set to valid values.

  Output:  If successful, the routine returns a pointer to an OA_Object
           structure, which contains a pointer to the image_handle in
           oa_object->appl1.  The oa_object's ODL tree describes an empty
           partial image.

  Notes: 
  1) Current limitations:
     a) This routine can currently handle Previous Pixel and
        Huffman First Difference compressed images, and uncompressed images.
        For a Clementine-JPEG compressed image, use OaReadImage.
     b) If the image is HFD encoded, it must be in a variable-length record
        file. 
     c) Uncompressed and Previous Pixel compressed images must be in
        fixed-length record files;  variable-length record files are only
        supported for HFD images.
     d) Previous Pixel encoded images may not have prefix or suffix bytes.

  2) For multiband images, the SDT and algorithm are as follows:
     (assume 10 bands, 1 byte/pixel, with input band = 3)

     a) BAND_INTERLEAVED:
        IMAGE             (single node with total_repetitions = LINE_SAMPLES)
        OaOpenImage positions stream_id->fp to the beginning of the 3rd band.
        OaReadImagePixels positions stream_id->fp to desired line.
        OalReadStream reads a total of LINE_SAMPLES bytes.

     b) LINE_INTERLEAVED:
        IMAGE             (single node with total_repetitions = LINE_SAMPLES)
        OaOpenImage positions stream_id->fp to the beginning of the image.
        OaReadImagePixels positions stream_id->fp to desired line AND desired
          band; no prefix or suffix nodes are used.
        OalReadStream reads a total of LINE_SAMPLES bytes.
       
     c) SAMPLE_INTERLEAVED:
        IMAGE node's total_repetitions = LINE_SAMPLES
        IMAGE------COLUMN (prefix SPARE, src.size=2
                ---COLUMN (image line,   src.size=1)
                ---COLUMN (suffix SPARE, src.size=7)
        OaOpenImage positions stream_id->fp to beginning of the image.
        OaReadImagePixels positions stream_id->fp to desired line.
        OalReadStream reads a total of LINE_SAMPLES * BANDS bytes.

*****************************************************************************/

#ifdef _NO_PROTO

OA_OBJECT OaOpenImage( input_node, band)
ODLTREE input_node;
int band;

#else

OA_OBJECT OaOpenImage( ODLTREE input_node, int band)

#endif
{

static char *proc_name = "OaOpenImage";
long file_offset, record_bytes, file_records, bytes, lines;
long line_samples, sample_bits, bands, line_prefix_bytes, line_suffix_bytes;
int record_type, src_interchange_format, encoding_type, band_storage_type;
char *label_filename, *data_filename, *str;

OA_OBJECT oa_object, histogram_object;
ODLTREE sdt, histogram_node, image_node, tmp_node;
SDTNODE image_node_sdt_ptr, sdt_node_ptr;
struct OaStreamStruct *stream_id;
struct oa_image_handle *image_handle;
short endian_indicator=1;       /* Initialized to 1 to determine the value */
                                /* of *platform_is_little_endian below.    */
char *platform_is_little_endian = (char *) &endian_indicator;
/* If platform is little-endian (lsb first), then the 1 initialized in
   endian_indicator will be in the first byte, and *platform_is_little_endian
   will have the value TRUE (1), otherwise it will be FALSE (0).  A test is
   done on *platform_is_little_endian in the Previous Pixel section below to
   decide whether or not change the SAMPLE_TYPE keyword in the output
   oa_object's ODL tree to LSB_INTEGER.  */


if (OaCheckODLTree( input_node) != 0)
  return(NULL);  /* Error message already issued and oa_errno set.  */

if (OaGetImageKeywords( input_node, &lines, &line_samples, &sample_bits,
                        &str, &bands, &band_storage_type, &line_prefix_bytes,
                        &line_suffix_bytes, &encoding_type) != 0)
  return(NULL);

if ((bands > 1) && (band_storage_type == OA_UNKNOWN_BAND_STORAGE_TYPE))
  return(NULL);    /* Error message already issued by OaGetImageKeywords */
if (encoding_type == OA_UNKNOWN_ENCODING_TYPE) {
  oa_errno = 531;  /* Error message already issued by OaGetImageKeywords. */
  return(NULL);
}

if (OaGetFileKeywords( input_node, &label_filename, &data_filename,
                       &record_type, &record_bytes, &file_records,
                       &file_offset, &src_interchange_format) != 0) {
  return(NULL);  /* Error message already issued.  */
}

#ifdef OA_DEBUG
OaReportFileAttributes( label_filename, data_filename, record_type,
                        record_bytes, file_offset, src_interchange_format);
#endif

/* Reject cases not currently supported (see limitations above). */

if (((encoding_type == OA_UNCOMPRESSED) ||
    (encoding_type == OA_PREVIOUS_PIXEL)) &&
    (record_type == OA_VARIABLE_LENGTH)) {
  sprintf( error_string, "%s does not support variable-length record files ",
           proc_name);
  strcat( error_string, " except for HFD compressed images.");
  oa_errno = 520;
  OaReportError( error_string);
  return(NULL);
}
if ((encoding_type == OA_HUFFMAN_FIRST_DIFFERENCE) &&
    (record_type != OA_VARIABLE_LENGTH)) {
  sprintf( error_string, "%s: HFD compressed images are only supported",
           proc_name);
  strcat( error_string, " in variable-length record files.");
  oa_errno = 520;
  OaReportError( error_string);
  return(NULL);
}
if ((encoding_type == OA_PREVIOUS_PIXEL) || (bands > 1)) {
  if ((line_prefix_bytes != 0) || (line_suffix_bytes != 0)) {
    sprintf( error_string,
             "%s: LINE_PREFIX_BYTES and LINE_SUFFIX_BYTES are not supported ",
               proc_name);
  strcat( error_string, "for multiband or Previous Pixel compressed images.");
    oa_errno = 520;
    OaReportError( error_string);
    return(NULL);
  }
}
if ((encoding_type != OA_UNCOMPRESSED) && (bands > 1)) {
  sprintf( error_string, "%s Compressed multi-band images not supported!",
           proc_name);
  oa_errno = 520;
  OaReportError( error_string);
  return(NULL);
}

/* Allocate an oa_image_handle structure. */

if ((image_handle = (struct oa_image_handle *)
                     OaMalloc( sizeof( struct oa_image_handle))) == NULL) {
  sprintf( error_string,
           "%s: OaMalloc failed to allocate space for image_handle.",
           proc_name);
  strcat( error_string, " Out of memory!");
  oa_errno = 720;
  OaReportError( error_string);
  exit(1);
  /*NOTREACHED*/
}
image_handle->image_start_offset  = file_offset;
image_handle->compression_type    = encoding_type;
image_handle->next_line           = 1;
image_handle->next_sample         = 1;
image_handle->buf_samples         = 0;
image_handle->source_lines        = lines;
image_handle->source_line_samples = line_samples;
image_handle->sample_bytes        = sample_bits/8;
image_handle->bands               = bands;
image_handle->band                = band;
image_handle->band_storage_type   = band_storage_type;

/* If image is HFD compressed, initialize the HFD decomp structure.  */

if (encoding_type == OA_HUFFMAN_FIRST_DIFFERENCE) {

  /* Call OaFindEncodingHistogram to find the node in the tree describing
     the encoding histogram needed for HFD decompression, then call
     OaReadHistogram to read the histogram data into memory.  */

  if ((histogram_node = OaFindEncodingHistogram( input_node)) == NULL) {
    sprintf( error_string, "%s: couldn't find encoding histogram for HFD",
             proc_name);
    strcat( error_string, " compressed image.");
    oa_errno = 530;
    OaReportError( error_string);
    return( NULL);
  }

  if ((histogram_object = OaReadHistogram( histogram_node)) == NULL) {
    sprintf( error_string, "%s: couldn't read encoding histogram for HFD",
             proc_name);
    strcat( error_string, " compressed image.");
    OaReportError( error_string);
    return( NULL);
  }

  /* Build the HFD decoding tree and attach it to the image handle. Note:
     image_handle->decomp.HFD.line_buffer is initialized after building
     the SDT.  */

  if ((image_handle->decomp.HFD.decoding_tree =
         OalCreateHFDTree( histogram_object->data_ptr)) == NULL) {
    sprintf( error_string, "%s: OalCreateHFDTree returned NULL.",
             proc_name);
    OaReportError( error_string);
    return( NULL);
  }
  OaDeleteObject( histogram_object);
}  /* end if HFD compressed */

/* If image is PP compressed, initialize the PP decomp structure.  */

if (encoding_type == OA_PREVIOUS_PIXEL) {
  image_handle->decomp.PP.found_255 = FALSE;
  image_handle->decomp.PP.previous_byte_exists  = FALSE;
}

/* Copy the sub-tree of the input ODL tree starting at the image node.  */

image_node = OaCopyTree( input_node, OA_STRIP_COMMENTS | OA_STRIP_SDT_NODES);

/* Initialize the keywords in the ODL tree which describe where the samples
   in OaReadImagePixels' buffer came from.
   LINES               = 1
   LINE_SAMPLES        = 0
   FIRST_LINE          = 1
   FIRST_SAMPLE        = 1
   SOURCE_LINES        = <original LINES>
   SOURCE_LINE_SAMPLES = <original LINE_SAMPLES>
*/

OaLongtoKwdValue( "LINES", image_node, (long) 1);
OaLongtoKwdValue( "LINE_SAMPLES", image_node, (long) 0);
OaLongtoKwdValue( "FIRST_LINE", image_node, (long) 1);
OaLongtoKwdValue( "FIRST_SAMPLE", image_node, (long) 1);
OaLongtoKwdValue( "SOURCE_LINES", image_node, 
                   (long) image_handle->source_lines);
OaLongtoKwdValue( "SOURCE_LINE_SAMPLES", image_node,
                   (long) image_handle->source_line_samples);

/* Unless compression_type is PREVIOUS_PIXEL, call OalCreateSDT to create a
   SDT (Stream Decomposition Tree) specifying one line's worth of samples as
   the source size.  The code temporarily changes the LINE_SAMPLES keyword
   back to image_handle->source_line_samples so OalCreateSDT will work right.
   OaReadImagePixels will read one line each call, and use the SDT to convert
   the samples to native format and/or strip off prefix and suffix bytes. 

   An SDT is not used for PREVIOUS_PIXEL compressed images because they are
   always 2-byte MSB_INTEGERs, and the decompression algorithm necessarily has
   to do the conversion to native 2-byte integers itself;  this means the
   caller gets the pixels in native format, regardless of what Oa_profile is
   set to...  */

if (encoding_type != OA_PREVIOUS_PIXEL) {
 
  OaLongtoKwdValue( "LINE_SAMPLES", image_node,
                     (long) image_handle->source_line_samples);

  /* Delete keywords for BANDS, BAND_STORAGE_TYPE and BAND_SEQUENCE since
     these will no longer be applicable for the in-memory monochromatic
     image.  */

  OaDeleteKwd( "BANDS",             image_node);
  OaDeleteKwd( "BAND_STORAGE_TYPE", image_node);
  OaDeleteKwd( "BAND_SEQUENCE",     image_node);

  if (bands > 1) {
    switch( band_storage_type) {

      case OA_LINE_INTERLEAVED:
        if (band > 1)
          OaLongtoKwdValue( "LINE_PREFIX_BYTES", image_node,
                            line_samples * sample_bits/8 * (band - 1));
        if (band < bands)
          OaLongtoKwdValue( "LINE_SUFFIX_BYTES", image_node,
                            line_samples * sample_bits/8 * (bands - band));
      break;

      case OA_SAMPLE_INTERLEAVED:
        if (band > 1)
          OaLongtoKwdValue( "LINE_PREFIX_BYTES", image_node,
                            sample_bits/8 * (band - 1));
        if (band < bands)
          OaLongtoKwdValue( "LINE_SUFFIX_BYTES", image_node,
                            sample_bits/8 * (bands - band));

        /* Trick OalCreateSDT into creating the desired SDT by making it think
           the image line is transposed into an 2-dimensional image, where
           the desired band's pixels are in a column, with undesired band data
           to the left and right of the column, represented by prefix and
           suffix bytes.  The number of lines in this image is equal to the
           number of pixels in the real image, and there's only one sample.  */

        OaLongtoKwdValue( "LINES", image_node, line_samples);
        OaLongtoKwdValue( "LINE_SAMPLES", image_node, (long) 1);
      break;
    }
  }

  if ((sdt = OalCreateSDT( image_node, src_interchange_format)) == NULL) {
    sprintf( error_string, "%s: CreateSDT returned error.",
             proc_name);
    OaReportError( error_string);
    OalFreeSDT( image_node);
    LemmeGo( image_handle);
    return(NULL);
  }
  image_node = sdt;

  /* Undo the keyword changes made above which tricked OalCreateSDT into
     creating the right kind of SDT.  */

  OaLongtoKwdValue( "LINE_SAMPLES", image_node, (long) 0);
  if (band_storage_type == OA_SAMPLE_INTERLEAVED)
    OaLongtoKwdValue( "LINES", image_node, 1);

  image_node_sdt_ptr = (SDT_node *) image_node->appl1;

  /* Set the SAMPLE_TYPE keyword value to the dst->PDS_data_type of the pixels.
     The node with the pixels' dst->PDS_data_type is image_node, if the image
     node has no children (SPARE nodes representing line prefix or suffix
     bytes), otherwise it's the node with non-zero dst.size (the node with
     NAME = IMAGE_LINE).  */

  if ((tmp_node = LeftmostChild( image_node)) == NULL)
    OaStrtoKwdValue(  "SAMPLE_TYPE", image_node, OaPDSDataTypetoStr(
                      image_node_sdt_ptr->dst.PDS_data_type));
  else {
    sdt_node_ptr = (SDT_node *) tmp_node->appl1; 
    if (sdt_node_ptr->dst.size == 0) {    /* prefix bytes node */
      tmp_node = RightSibling( tmp_node);
      sdt_node_ptr = (SDT_node *) tmp_node->appl1;
    }
    OaStrtoKwdValue(  "SAMPLE_TYPE", image_node, OaPDSDataTypetoStr(
                      sdt_node_ptr->dst.PDS_data_type));
  }
} else { /* PP compressed image */

  /* If on an LSB integer platform, change SAMPLE_TYPE keyword value from
     MSB_INTEGER to LSB_INTEGER.  */

  if (*platform_is_little_endian == TRUE)
    OaStrtoKwdValue( "SAMPLE_TYPE", image_node, "LSB_INTEGER");

  /* Left out code to use the profile and convert the PP decompressed pixels;
     I assume it's very rare that the user doesn't want in them in native
     2-byte binary integer format. */
}


/* Delete keywords for ENCODING_TYPE, LINE_PREFIX_BYTES and LINE_SUFFIX_BYTES,
   since these will no longer be applicable for the in-memory image, which is
   decompressed, and has its prefix and/or suffix bytes removed.  */

OaDeleteKwd( "ENCODING_TYPE",     image_node);
OaDeleteKwd( "LINE*PREFIX*BYTES", image_node);
OaDeleteKwd( "LINE*SUFFIX*BYTES", image_node);
OaDeleteKwd( "^LINE*PREFIX*STRUCTURE", image_node);
OaDeleteKwd( "^LINE*SUFFIX*STRUCTURE", image_node);

if (encoding_type == OA_HUFFMAN_FIRST_DIFFERENCE) {

  /* Allocate a buffer big enough to store a decompressed image line.  If
     there are prefix and/or suffix bytes, they are assumed to be compressed
     along with each image line.  */

  if (LeftmostChild( image_node) == NULL) /* No prefix or suffix bytes. */
    bytes = image_node_sdt_ptr->src.size * image_handle->source_line_samples;
  else
    bytes = image_node_sdt_ptr->src.size;
  image_handle->decomp.HFD.line_buffer_bytes = bytes;
  if ((image_handle->decomp.HFD.line_buffer = (char *) OaMalloc( 
                                                  (size_t) bytes)) == NULL) {
    sprintf( error_string,
             "%s: OaMalloc failed to allocate space for HFD line_buffer.",
             proc_name);
    strcat( error_string, " Out of memory!");
    oa_errno = 720;
    OaReportError( error_string);
    exit(1);
    /*NOTREACHED*/
  }
}

if ((stream_id = OalOpenStream( data_filename, record_type, record_bytes,
                                file_records, "r")) == NULL) {
  sprintf( error_string, "%s: OalOpenStream returned error.",
           proc_name);
  OaReportError( error_string);
  LemmeGo( image_handle);
  OdlFreeTree( image_node);
  return(NULL);
}

/* Position the file pointer to the beginning of the image, or in the case of
   a BAND_SEQUENTIAL image, to the start of the desired band. */

if (band_storage_type == OA_BAND_SEQUENTIAL)
  file_offset += (band - 1) * sample_bits/8 * line_samples * lines;

if (OalSeek( stream_id, file_offset) != 0) {
  sprintf( error_string, "%s: OalSeek returned error, aborting.",
           proc_name);
  OaReportError( error_string);
  OalCloseStream( stream_id);
  LemmeGo( image_handle);
  return(NULL);
}

/* Create the output oa_object and initialize it.  */

oa_object = OaNewOaObject();
oa_object->odltree = image_node;
oa_object->data_ptr = NULL;
oa_object->size = 0;
oa_object->is_in_memory = FALSE;
oa_object->stream_id = stream_id;
oa_object->appl1 = image_handle;

/* Determine the size of, and allocate space for internal buffer which stores
   the latest samples which have been read, decompressed and converted.  */

if (encoding_type == OA_PREVIOUS_PIXEL)
  image_handle->buf_siz = stream_id->buf_siz * 2; /* Max compression ratio. */
else
  image_handle->buf_siz = image_node_sdt_ptr->dst.size *
                          image_node_sdt_ptr->total_repetitions;

if ((image_handle->buf = OaMalloc( (size_t) image_handle->buf_siz)) == NULL) {
  sprintf( error_string,
           "%s: OaMalloc failed to allocate space for image_handle->buf.",
           proc_name);
  strcat( error_string, " Out of memory!");
  oa_errno = 720;
  OaReportError( error_string);
  exit(1);
  /*NOTREACHED*/
}

#ifdef OA_DEBUG
  sprintf( error_string, "%s: image_handle is:\n", proc_name);
  sprintf( error_string + strlen( error_string),
           "image_start_offset  = %ld\n", image_handle->image_start_offset);
  sprintf( error_string + strlen( error_string),
           "buf_siz             = %ld\n", image_handle->buf_siz);
  sprintf( error_string + strlen( error_string),
           "next_line           = %ld\n", image_handle->next_line);
  sprintf( error_string + strlen( error_string),
           "next_sample         = %ld\n", image_handle->next_sample);
  sprintf( error_string + strlen( error_string),
           "buf_samples         = %ld\n", image_handle->buf_samples);
  sprintf( error_string + strlen( error_string),
           "source_lines        = %ld\n", image_handle->source_lines);
  sprintf( error_string + strlen( error_string),
           "source_line_samples = %ld\n", image_handle->source_line_samples);
  sprintf( error_string + strlen( error_string),
           "bands               = %d\n", image_handle->bands);
  sprintf( error_string + strlen( error_string),
           "band                = %d\n", image_handle->band);
  sprintf( error_string + strlen( error_string),
           "sample_bytes        = %d\n", (int) image_handle->sample_bytes);
  oa_errno = 950;
  OaReportError( error_string);
#endif

return( oa_object);
}



/*****************************************************************************

  Routine:  OaOpenOutputFile

  Description:  This routine sets up for writing objects to an output file.
                It is the first call in the sequence:  OaOpenOutputFile,
                OaWriteObject (multiple calls), OaCloseOutputFile.
                It calls OalOpenStream to open the data file and initialize a
                stream descriptor, then builds a FILE-class ODL tree node to
                describe the file.  After calling OaOpenOutputFile, the caller
                can call OaWriteObject to write an object to the file and add
                new nodes and ^POINTERs to the file object's ODL tree.

  Author:  Steve Monk, University of Colorado LASP

  Creation Date:   1 Sept  1994
  Last Modified:  11 Nov   1996

  History:

    Creation - This routine was part of the Alpha Release of the OA library.
    11/11/96 - Added support for UNDEFINED record type. SM

  Input:   
          data_filename - A file pathname suitable for direct use by fopen().
                          It must be valid on the platform being run on, and
                          the process running OA must have write permission.
          record_type   - OA_FIXED_LENGTH, OA_STREAM, OA_VARIABLE_LENGTH or
                          OA_UNDEFINED_RECORD_TYPE.
          record_bytes  - Must be greater than 0 for OA_FIXED_LENGTH or
                          OA_VARIABLE_LENGTH, and is ignored for OA_STREAM
                          and OA_UNDEFINED_RECORD_TYPE.
        
  Output:  If successful, the routine returns a pointer to an Oa_Object
           containing a pointer to a stream descriptor, which describes the
           opened data file, and the ODL tree attached to the Oa_Object has a
           single FILE node with appropriate keywords set.

  Notes:   Haven't tested what happens with bad pathnames, or if the file
           in data_filename already exists, or if don't have write permission.

*****************************************************************************/

#ifdef _NO_PROTO

OA_OBJECT OaOpenOutputFile( data_filename, record_type, record_bytes)
char *data_filename;
int  record_type;
long record_bytes;

#else

OA_OBJECT OaOpenOutputFile( char *data_filename, int record_type,
                            long record_bytes)

#endif
{

static char *proc_name = "OaOpenOutputFile";
OA_OBJECT oa_object;

/* Check inputs for validity. */

if (data_filename == NULL) {
  sprintf( error_string, "%s: data_filename is NULL.", proc_name);
  oa_errno = 501;
  OaReportError( error_string);
  return(NULL);
}

switch( record_type) {
  case OA_FIXED_LENGTH:
  case OA_VARIABLE_LENGTH:
    if (record_bytes < 1) {
      sprintf( error_string, "%s: record bytes must be greater than 0 ",
               proc_name);
      strcat(  error_string,
              "for a FIXED_LENGTH or VARIABLE_LENGTH record file.");
      oa_errno = 502;
      OaReportError( error_string);
      return(NULL);
    }
  break;
  case OA_STREAM:
  case OA_UNDEFINED_RECORD_TYPE:
  break;
  default:
    sprintf( error_string, "%s: invalid record type: %d.",
             proc_name, record_type);
    oa_errno = 502;
    OaReportError( error_string);
    return( NULL);
    /*NOTREACHED*/
  break;
}

/* Create a new Oa_Object structure and initialize it. */

oa_object = OaNewOaObject();
oa_object->is_in_memory = FALSE;
oa_object->size = 0;

/* Open the output file. */

if ((oa_object->stream_id = OalOpenStream( data_filename,
                                           record_type,
                                           record_bytes,
                                           (long) 0,
                                           "w")) == NULL) {
  LemmeGo( oa_object);
  return( NULL);
}

/* Create the ODL tree node to describe the file.  */

oa_object->odltree = OdlNewObjDesc( "FILE", NULL, NULL, NULL, NULL, NULL,
                                     (short) 0, (long) 0);

/* Add PDS_VERSION_ID, RECORD_TYPE, RECORD_BYTES and FILE_RECORDS keywords to
   the file node, as appropriate for the specified record type.  */

OaStrtoKwdValue( "PDS_VERSION_ID", oa_object->odltree, "PDS3");

switch( record_type) {
  case OA_FIXED_LENGTH:
    OaStrtoKwdValue( "RECORD_TYPE", oa_object->odltree, "FIXED_LENGTH");
  break;
  case OA_VARIABLE_LENGTH:
    OaStrtoKwdValue( "RECORD_TYPE", oa_object->odltree, "VARIABLE_LENGTH");
  break;
  case OA_STREAM:
    OaStrtoKwdValue( "RECORD_TYPE", oa_object->odltree, "STREAM");
  break;
  case OA_UNDEFINED_RECORD_TYPE:
    OaStrtoKwdValue( "RECORD_TYPE", oa_object->odltree, "UNDEFINED");
  break;
}

if ((record_type != OA_STREAM) &&
    (record_type != OA_UNDEFINED_RECORD_TYPE)) {
  OaLongtoKwdValue( "RECORD_BYTES", oa_object->odltree, record_bytes);
  OaLongtoKwdValue( "FILE_RECORDS", oa_object->odltree, (long) 0);
}
return( oa_object);
}



/*****************************************************************************

  Routine:  OaReadArray

  Description:  OaReadArray opens a data file and reads an ARRAY object into
                memory.

  Author:  Steve Monk, University of Colorado LASP

  Creation Date:   1 Sept  1994
  Last Modified:   1 Sept  1994

  History:

    Creation - This routine was part of the Alpha Release of the OA library.

  Input: 
         input_node - A pointer to an ODL tree node of class ARRAY.
                      The tree above the input_node should have keywords
                      to specify the data file.
      
         The global variable Oa_profile is set to valid values.

  Output:  If successful, the routine returns a pointer to an OA_Object
           structure, which contains an ODL tree and a pointer to the array
           data.  If unsuccessful it returns NULL.

  Notes: 

*****************************************************************************/

#ifdef _NO_PROTO

OA_OBJECT OaReadArray( input_node)
ODLTREE input_node;

#else

OA_OBJECT OaReadArray( ODLTREE input_node)

#endif
{

static char *proc_name = "OaReadArray";
long file_offset, record_bytes, file_records;
int record_type, src_interchange_format, dst_interchange_format;
char *label_filename, *data_filename;
OA_OBJECT oa_object;
ODLTREE current_node, sdt, array_node, compressed_SDT;
SDTNODE sdt_node_ptr;
int object_class;
char *buf = NULL, c;
#ifdef IBM_PC
long i;
#endif
PTR data_ptr;
struct OaStreamStruct *stream_id;
long bytes_read, object_size;

object_class = OaGetObjectClass( input_node);
switch( object_class) {
 case OA_ARRAY:
  break;
  default:
    sprintf( error_string, "%s: input ODLTREE node is not an ARRAY object.",
             proc_name);
    oa_errno = 530;
    OaReportError( error_string);
    return(NULL);
}

if (OaGetFileKeywords( input_node, &label_filename, &data_filename,
                       &record_type, &record_bytes, &file_records,
                       &file_offset, &src_interchange_format) != 0) {
  return(NULL);  /* Error message already issued.  */
}
if (src_interchange_format == OA_ASCII_INTERCHANGE_FORMAT)
  dst_interchange_format = Oa_profile.dst_format_for_ASCII_src;
else
  dst_interchange_format = Oa_profile.dst_format_for_binary_src;

#ifdef OA_DEBUG
OaReportFileAttributes( label_filename, data_filename, record_type,
                        record_bytes, file_offset, src_interchange_format);
#endif

/* The input tree ODL tree shouldn't ever be modified, so make a copy of all
   the nodes below the input node.  */

array_node = OaCopyTree( input_node, OA_STRIP_COMMENTS | OA_STRIP_SDT_NODES);

if (OaCheckODLTree( array_node) != 0) {
  OdlFreeTree( array_node);
  return(NULL);  /* Error message already issued and oa_errno set.  */
}

/* Call OalCreateSDT to create the SDT (Stream Decomposition Tree).  */

if ((sdt = OalCreateSDT( array_node, src_interchange_format)) == NULL) {
  sprintf( error_string, "%s: CreateSDT returned error.",
           proc_name);
  OaReportError( error_string);
  OalFreeSDT( array_node);
  return(NULL);
}

/* Note: sdt and array_node now point to the same node. */

/* Allocate memory for the array.  */

sdt_node_ptr = (SDT_node *) sdt->appl1;
object_size = sdt_node_ptr->dst.size *
              sdt_node_ptr->total_repetitions;
if ((data_ptr = (PTR) OaMalloc( (long) object_size)) == NULL) {
  sprintf( error_string, "%s: OaMalloc failed! Out of memory!", proc_name);
  oa_errno = 720;
  OaReportError( error_string);
  exit(1);
  /*NOTREACHED*/
}
if (dst_interchange_format == OA_ASCII_INTERCHANGE_FORMAT)
  c = ' ';
else
  c = 0;

#ifdef IBM_PC

/* If we're on an IBM-PC, use a for loop to do the set, since memset
   may not work for setting greater than 64K and/or over segment
   boundaries;  otherwise use memset since it's usually optimized.  */

for (i=0; i<object_size; i++)
  data_ptr[i] = c;
#else
memset( data_ptr, c, (size_t) object_size);
#endif

if ((stream_id = OalOpenStream( data_filename, record_type, record_bytes,
                                file_records, "r")) == NULL) {
  sprintf( error_string, "%s: OalOpenStream returned error.",
           proc_name);
  OaReportError( error_string);
  return(NULL);
}

/* Read the first slice of data; tell OalReadStream to read as many bytes as it
   wants, store them in it's own buffer, start reading the file at the given
   byte offset, and output how many bytes it's read in bytes_read.  */

if (OalReadStream( stream_id, (long) 0, &buf, file_offset, &bytes_read) != 0) {
  sprintf( error_string, "%s: OalReadStream returned error, aborting.",
           proc_name);
  OaReportError( error_string);
  OalFreeSDT( array_node);
  OalCloseStream( stream_id);
  return(NULL);
}

/* Compress the SDT, and initialize current_node to point to the first
   end-node (data node) in the compressed SDT; initialize the node's dst.ptr
   with the pointer returned by OaMalloc.  */

compressed_SDT = OalCompressSDT( sdt);
current_node = OalInitializeSDT( compressed_SDT, data_ptr);

/* The main loop processes a buffer of data, then reads in the next buffer;
   the SDT maps desired data to memory and throws aways the rest.  */

for (;;) {
  if (OalProcessSDT( (PTR) buf, bytes_read, &current_node) ==
      OA_REACHED_END_OF_SDT) break;

  /*  Read next slice of data, telling OalReadStream to do the same as the
      first time, except start at the current file position.  */

  buf = NULL;
  if (OalReadStream( stream_id, (long) 0, &buf, (long) -1, &bytes_read) != 0) {
    sprintf( error_string, "%s: OalReadStream returned error, aborting.",
             proc_name);
    OaReportError( error_string);
    break;
  }
}
OalCloseStream( stream_id);

/* Free the compressed SDT. */

OalFreeSDT( compressed_SDT);

/* Call OalSDTtoODLTree to modify the new ODL tree to reflect the in-memory
   data.  This changes the DATA_TYPE keywords to reflect the in-memory data
   values which may have been converted to native types.  It also strips off
   ODL tree nodes which have SDT_node->dst.size=0.  */

OalSDTtoODLTree( sdt, dst_interchange_format);

/* Create the output oa_object and initialize it.  */

oa_object = OaNewOaObject();
oa_object->odltree = sdt;
oa_object->data_ptr = data_ptr;
oa_object->size = object_size;
oa_object->is_in_memory = TRUE;
oa_object->profile = Oa_profile;
oa_object->stream_id = NULL;

return( oa_object);
}



/*****************************************************************************

  Routine:  OaReadHistogram

  Description:  OaReadHistogram opens a data file and reads a HISTOGRAM object
                into memory.

  Author:  Steve Monk, University of Colorado LASP

  Creation Date:   1 Sept  1994
  Last Modified:   1 Sept  1994

  History:

    Creation - This routine was part of the Alpha Release of the OA library.

  Input: 
         input_node - A pointer to an ODL tree node of class HISTOGRAM.
                      The tree above the input_node should have keywords
                      to specify the data file.
      
         The global variable Oa_profile is set to valid values.

  Output:  If successful, the routine returns a pointer to an OA_Object
           structure, which contains an ODL tree and a pointer to the histogram
           data.  If unsuccessful it returns NULL.

  Notes: 

*****************************************************************************/

#ifdef _NO_PROTO

OA_OBJECT OaReadHistogram( input_node)
ODLTREE input_node;

#else

OA_OBJECT OaReadHistogram( ODLTREE input_node)

#endif
{

static char *proc_name = "OaReadHistogram";
long file_offset, record_bytes, file_records;
int record_type, src_interchange_format, dst_interchange_format;
char *label_filename, *data_filename;
OA_OBJECT oa_object;
ODLTREE current_node, sdt, histogram_node, compressed_SDT;
SDTNODE sdt_node_ptr;
long object_size, bytes_read;
#ifdef IBM_PC
long i;
#endif
int object_class;
char *buf = NULL, c;
PTR data_ptr;
struct OaStreamStruct *stream_id;

object_class = OaGetObjectClass( input_node);
if (object_class != OA_HISTOGRAM) {
  sprintf( error_string, "%s: input ODLTREE node is not a HISTOGRAM object.",
           proc_name);
  oa_errno = 530;
  OaReportError( error_string);
  return(NULL);
}

if (OaGetFileKeywords( input_node, &label_filename, &data_filename,
                       &record_type, &record_bytes, &file_records,
                       &file_offset, &src_interchange_format) != 0) {
  return(NULL);  /* Error message already issued.  */
}
if (src_interchange_format == OA_ASCII_INTERCHANGE_FORMAT)
  dst_interchange_format = Oa_profile.dst_format_for_ASCII_src;
else
  dst_interchange_format = Oa_profile.dst_format_for_binary_src;

#ifdef OA_DEBUG
OaReportFileAttributes( label_filename, data_filename, record_type,
                        record_bytes, file_offset, src_interchange_format);
#endif

/* The input tree ODL tree shouldn't ever be modified, so make a copy of all
   the nodes below the input node.  */

histogram_node = OaCopyTree( input_node, OA_STRIP_COMMENTS |
                                         OA_STRIP_SDT_NODES);

if (OaCheckODLTree( histogram_node) != 0) {
  OdlFreeTree( histogram_node);
  return(NULL);  /* Error message already issued and oa_errno set.  */
}

/* Call OalCreateSDT to create the SDT (Stream Decomposition Tree).  */

if ((sdt = OalCreateSDT( histogram_node, src_interchange_format)) == NULL) {
  sprintf( error_string, "%s: CreateSDT returned error.",
           proc_name);
  OaReportError( error_string);
  OalFreeSDT( histogram_node);
  return(NULL);
}

/* Allocate memory for the histogram.  */

sdt_node_ptr = (SDT_node *) sdt->appl1;
object_size = sdt_node_ptr->dst.size * sdt_node_ptr->total_repetitions;
if ((data_ptr = (PTR) OaMalloc( (long) object_size)) == NULL){
  sprintf( error_string, "%s: OaMalloc failed! Out of memory!", proc_name);
  oa_errno = 720;
  OaReportError( error_string);
  exit(1);
  /*NOTREACHED*/
}
if (dst_interchange_format == OA_ASCII_INTERCHANGE_FORMAT)
  c = ' ';
else
  c = 0;

#ifdef IBM_PC

/* If we're on an IBM-PC, use a for loop to do the set, since memset
   may not work for setting greater than 64K and/or over segment
   boundaries;  otherwise use memset since it's usually optimized.  */

for (i=0; i<object_size; i++)
  data_ptr[i] = c;
#else
memset( data_ptr, c, (size_t) object_size);
#endif

/* Create the output oa_object and initialize it.  */

oa_object = OaNewOaObject();
oa_object->odltree = sdt;
oa_object->data_ptr = data_ptr;
oa_object->size = object_size;
oa_object->is_in_memory = TRUE;
oa_object->profile = Oa_profile;
oa_object->stream_id = NULL;

if ((stream_id = OalOpenStream( data_filename, record_type, record_bytes,
                                file_records, "r")) == NULL) {
  sprintf( error_string, "%s: OalOpenStream returned error.",
           proc_name);
  OaReportError( error_string);
  OalFreeSDT( histogram_node);
  return(NULL);
}

/* Read the first slice of data; tell OalReadStream to read as many bytes as it
   wants, store them in it's own buffer, start reading the file at the given
   byte offset, and output how many bytes it's read in bytes_read.  */

if (OalReadStream( stream_id, (long) 0, &buf, file_offset, &bytes_read) != 0) {
  sprintf( error_string, "%s: OalReadStream returned error, aborting.",
           proc_name);
  OaReportError( error_string);
  OalFreeSDT( histogram_node);
  return(NULL);
}

/* Compress the SDT, and initialize current_node to point to the first
   end-node (data node) in the compressed SDT; initialize the node's dst.ptr
   with the pointer returned by OaMalloc. In the case of a HISTOGRAM, there is
   only one node.  */

compressed_SDT = OalCompressSDT( sdt);
current_node = compressed_SDT;
sdt_node_ptr = (SDT_node *) current_node->appl1;
sdt_node_ptr->dst.ptr = data_ptr;

/* The main loop processes a buffer of data, then reads in the next buffer;
   the SDT maps desired data to memory and throws aways the rest. 
   In the case of a HISTOGRAM, all the data is mapped, none is thrown away.  */

for (;;) {
  if (OalProcessSDT( (PTR) buf, bytes_read, &current_node) ==
      OA_REACHED_END_OF_SDT) break;
 
  /*  Read next slice of data, telling OalReadStream to do the same as the
      first time, except start at the current file position.  */

  buf = NULL;
  if (OalReadStream( stream_id, (long) 0, &buf, (long) -1, &bytes_read) != 0) {
    sprintf( error_string, "%s: OalReadStream returned error, aborting.",
             proc_name);
    OaReportError( error_string);
    break;
  }
}
OalCloseStream( stream_id);

/* Free the compressed SDT. */

OalFreeSDT( compressed_SDT);

/* Call OalSDTtoODLTree to modify the new ODL tree to reflect the in-memory
   data.  This changes the DATA_TYPE keywords to reflect the in-memory data
   values which may have been converted to native types.  It also strips off
   ODL tree nodes which have SDT_node->dst.size=0.  */

OalSDTtoODLTree( sdt, dst_interchange_format);

return( oa_object);
}



/*****************************************************************************

  Routine:  OaReadHistory

  Description:  OaReadHistory opens a data file and reads a HISTORY object
                into memory.

  Author:  Steve Monk, University of Colorado LASP

  Creation Date:   9 Jan   1995
  Last Modified:   9 Jan   1995

  History:

    Creation - This routine was part of the Beta Release of the OA library.

  Input: 
         input_node - A pointer to a ODL tree node which has a ^POINTER to
                      a HISTORY object;  usually the root node, but could be
                      a FILE node under the root node.

  Output:  If successful, the routine returns a pointer to an OA_Object
           structure, which contains an ODL tree representing the parsed
           HISTORY object.  The OA_Object's data_ptr is NULL, and size is 0,
           since the HISTORY object is all ODL tree, and no object data.
           If unsuccessful it returns NULL.

  Notes: 

*****************************************************************************/

#ifdef _NO_PROTO

OA_OBJECT OaReadHistory( input_node)
ODLTREE input_node;

#else

OA_OBJECT OaReadHistory( ODLTREE input_node)

#endif
{

static char *proc_name = "OaReadHistory";
long file_offset, record_bytes, file_records;
int record_type, src_interchange_format, seek_result;
char *label_filename, *data_filename;
OA_OBJECT oa_object;
ODLTREE history_node, root_node;
struct OaStreamStruct *stream_id;

/* Check input for validity.  */

if (input_node == NULL) {
  sprintf( error_string, "%s: input_node is NULL", proc_name);
  oa_errno = 501;
  OaReportError( error_string);
  return( NULL);
}

/* Make a temporary ODL tree node under input_node with object class
   HISTORY.  This is deleted after the call to OaGetFileKeywords.  */

history_node = OdlNewObjDesc( "HISTORY", NULL, NULL, NULL, NULL, NULL,
                              (short) 0, (long) 0);
OdlPasteObjDesc( history_node, input_node);

if (OaGetFileKeywords( history_node, &label_filename, &data_filename,
                       &record_type, &record_bytes, &file_records,
                       &file_offset, &src_interchange_format) != 0) {
    OdlFreeTree( OdlCutObjDesc( history_node));
    return(NULL);  /* Error message already issued.  */
}
OdlFreeTree( OdlCutObjDesc( history_node));

src_interchange_format = OA_ASCII_INTERCHANGE_FORMAT;

#ifdef OA_DEBUG
OaReportFileAttributes( label_filename, data_filename, record_type,
                        record_bytes, file_offset, src_interchange_format);
#endif

/* Open the file the HISTORY object is in, and position to the start of the
   HISTORY object.  */

if ((stream_id = OalOpenStream( data_filename, record_type, record_bytes,
                                file_records, "r")) == NULL) {
  sprintf( error_string, "%s: OalOpenStream returned error.",
           proc_name);
  OaReportError( error_string);
  return(NULL);
}
if ((seek_result = OalSeek( stream_id, file_offset)) != 0) {
  sprintf( error_string, "%s: OalSeek returned error: %d.",
           proc_name, seek_result);
  OaReportError( error_string);
  OalCloseStream( stream_id);
  return( NULL);
}

/* Pass the file pointer to OdlParseFile, and let it parse the HISTORY object
   into an ODL tree.  If L3 eventually uses the OAL Stream Layer, this will
   be changed to pass in the stream_id instead of the file pointer.  Until
   then, won't be able to read a HISTORY object from a variable-length
   record file except on a VAX.  Also suppress error messages from the parser.
*/

root_node = OdlParseFile( NULL, stream_id->fp, NULL, NULL, NULL, 
                          1,1,1,0);

if (root_node == NULL) {
  sprintf( error_string, "%s: error parsing HISTORY object", proc_name);
  oa_errno = 1000;
  OaReportError( error_string);
  OalCloseStream( stream_id);
  return(NULL);
}
OalCloseStream( stream_id);

if (LeftmostChild( root_node) == NULL) {
  sprintf( error_string, "%s: error parsing HISTORY object", proc_name);
  oa_errno = 1000;
  OaReportError( error_string);
  return(NULL);
}

history_node = OdlCutObjDesc( LeftmostChild( root_node));
OdlFreeTree( root_node);

/* Create the output oa_object and initialize it.  */

oa_object = OaNewOaObject();
oa_object->odltree = history_node;
oa_object->data_ptr = NULL;
oa_object->size = 0;
oa_object->is_in_memory = FALSE;
oa_object->profile = Oa_profile;
oa_object->stream_id = NULL;

return( oa_object);
}



/*****************************************************************************

  Routine:  OaReadImage

  Description:  OaReadImage opens a data file and reads an entire IMAGE object
                into memory.  The image is decompressed, and any prefix and
                suffix bytes are stripped out.

  Author:  Steve Monk, University of Colorado LASP

  Creation Date:   1 Sept  1994
  Last Modified:  16 Mar   1998

  History:

    Creation - This routine was part of the Alpha Release of the OA library.
    12/11/96 - Added support for multiband images; including new 'band'
               argument.  SM
    03/16/98 - Added call to OaGIF for OA_GIF encoding type.  SM

  Input: 
         input_node - A pointer to an ODL tree node of class IMAGE.
                      The tree above the input_node should have keywords
                      to specify the data file.  If the image is HFD
                      compressed, the input_node must have a sibling
                      identifiable as the encoding histogram for decompression.
      
         The global variable Oa_profile is set to valid values.

  Output:  If successful, the routine returns a pointer to an OA_Object
           structure, which contains an ODL tree and a pointer to the image
           data.  If unsuccessful it returns NULL.

  Notes: 

*****************************************************************************/

#ifdef _NO_PROTO

OA_OBJECT OaReadImage( input_node, band)
ODLTREE input_node;
int band;

#else

OA_OBJECT OaReadImage( ODLTREE input_node, int band)

#endif
{

static char *proc_name = "OaReadImage";
long file_offset, record_bytes, file_records;
int record_type, src_interchange_format, dst_interchange_format;
char *label_filename, *data_filename;
OA_OBJECT oa_object, histogram_object, partial_image_object;
ODLTREE current_node, sdt, histogram_node, image_node;
ODLTREE compressed_SDT;
SDTNODE sdt_node_ptr;
long object_size, bands;
#ifdef IBM_PC
long i;
#endif
long lines, line_samples, sample_bits, line_prefix_bytes, line_suffix_bytes;
char *buf = NULL, c, *sample_type_str;
PTR data_ptr;
char *line_buffer = NULL;
long line_buffer_bytes, bytes_read;
struct OaStreamStruct *stream_id;
int encoding_type, band_storage_type;
void *hfd_decoding_tree = NULL;

dst_interchange_format = OA_BINARY_INTERCHANGE_FORMAT;

if (OaGetImageKeywords( input_node, &lines, &line_samples, &sample_bits,
                        &sample_type_str, &bands, &band_storage_type,
                        &line_prefix_bytes, &line_suffix_bytes,
                        &encoding_type) != 0)
  return(NULL);

if (bands > 1) {
  if ((band < 1) || (band > bands)) {
    sprintf( error_string, "%s: out of range band value.", proc_name);
    oa_errno = 520;
    OaReportError( error_string);
    return(NULL);
  }
} else {
  band = 1;
}
if (encoding_type == OA_UNKNOWN_ENCODING_TYPE) {
  oa_errno = 520;
  return(NULL);
}
if ((encoding_type == OA_PREVIOUS_PIXEL) || (bands > 1)) {
  if ((line_prefix_bytes != 0) || (line_suffix_bytes != 0)) {
    sprintf( error_string,
             "%s: LINE_PREFIX_BYTES and LINE_SUFFIX_BYTES are not supported ",
               proc_name);
  strcat( error_string, "for multiband or Previous Pixel compressed images.");
    oa_errno = 520;
    OaReportError( error_string);
    return(NULL);
  }
}

/* If the encoding type is PREVIOUS_PIXEL, use OaReadPartialImage to read it
   in, rather than repeating a lot of previous-pixel decompression code here.
   This isn't done for the other compression types (and uncompressed), because
   for these types the code here is faster than OaReadPartialImage.  */

if (encoding_type == OA_PREVIOUS_PIXEL) {

  if ((partial_image_object = OaOpenImage( input_node, 1)) == NULL)
    return( NULL);
  if ((oa_object = OaReadPartialImage( partial_image_object, 1, lines,
                                       1, line_samples)) == NULL)
    return(NULL);
  OaCloseImage( partial_image_object);

  /* Delete keywords FIRST_LINE, FIRST_SAMPLE, SOURCE_LINES, and
     SOURCE_LINE_SAMPLES which were added by OaOpenImage, since they apply
     to partial images, and we've just read the whole image.  */

  OaDeleteKwd( "FIRST_LINE", oa_object->odltree);
  OaDeleteKwd( "FIRST_SAMPLE", oa_object->odltree);
  OaDeleteKwd( "SOURCE_LINES", oa_object->odltree);
  OaDeleteKwd( "SOURCE_LINE_SAMPLES", oa_object->odltree);

  return( oa_object);

}  /* end if encoding_type is OA_PREVIOUS_PIXEL */


if (OaGetFileKeywords( input_node, &label_filename, &data_filename,
                       &record_type, &record_bytes, &file_records,
                       &file_offset, &src_interchange_format) != 0) {
  return(NULL);  /* Error message already issued.  */
}

#ifdef OA_DEBUG
OaReportFileAttributes( label_filename, data_filename, record_type,
                        record_bytes, file_offset, src_interchange_format);
#endif

/* Reject cases not currently supported (see limitations above). */

if (((encoding_type == OA_UNCOMPRESSED) ||
    (encoding_type == OA_PREVIOUS_PIXEL)) &&
    (record_type == OA_VARIABLE_LENGTH)) {
  sprintf( error_string, "%s does not support variable-length record files ",
           proc_name);
  strcat( error_string, " except for HFD compressed images.");
  oa_errno = 520;
  OaReportError( error_string);
  return(NULL);
}
if ((encoding_type == OA_HUFFMAN_FIRST_DIFFERENCE) &&
    (record_type != OA_VARIABLE_LENGTH)) {
  sprintf( error_string, "%s: HFD compressed images are only supported",
           proc_name);
  strcat( error_string, " in variable-length record files.");
  oa_errno = 520;
  OaReportError( error_string);
  return(NULL);
}
if ((encoding_type != OA_UNCOMPRESSED) && (bands > 1)) {
  sprintf( error_string, "%s Compressed multi-band images not supported!",
           proc_name);
  oa_errno = 520;
  OaReportError( error_string);
  return(NULL);
}

if (encoding_type == OA_GIF) {
  return( (OA_OBJECT) OalReadImageFromGif( data_filename, file_offset,
                                           lines, line_samples,
                                           input_node));
}

 
if (encoding_type == OA_CLEMENTINE_JPEG) {

  if (sample_bits != 8) {
    sprintf( error_string,
             "%s: Clementine decompression software does not support %ld ",
             proc_name, sample_bits);
    strcat( error_string, "SAMPLE_BITS");
    oa_errno = 520;
    OaReportError( error_string);
    return( NULL);
  }

  /* Copy the input tree from the IMAGE node down.  */

  image_node = OaCopyTree( input_node, OA_STRIP_COMMENTS | OA_STRIP_SDT_NODES);

  /* Allocate space for decompression software to fill up with the image
     as it's decompressed, and initialize it with 0's.  */

  object_size = lines * line_samples;
  if ((data_ptr = (PTR) OaMalloc( (long) object_size)) == NULL) {
    sprintf( error_string, "%s: OaMalloc failed to allocate %ld bytes for ",
             proc_name, object_size);
    strcat( error_string, "image! Out of memory!");
    oa_errno = 720;
    OaReportError( error_string);
    exit(1);
    /*NOTREACHED*/
  }
  c = 0;
#ifdef IBM_PC

  /* If we're on an IBM-PC, use a for loop to do the set, since memset
     may not work for setting greater than 64K and/or over segment
     boundaries;  otherwise use memset since it's usually optimized.  */

  for (i=0; i<object_size; i++)
    data_ptr[i] = c;
#else
  memset( data_ptr, c, (size_t) object_size);
#endif

  /* Open the file, position the file pointer to the start of the image,
     and pass the file pointer and data_ptr to OalClementineJPEGDecompress. */

  if ((stream_id = OalOpenStream( data_filename, record_type, record_bytes,
                                  file_records, "r")) == NULL) {
    sprintf( error_string, "%s: OalOpenStream returned error.",
             proc_name);
    OaReportError( error_string);
    OalFreeSDT( image_node);
    return(NULL);
  }
  OalSeek( stream_id, file_offset);
  if (OalClementineJPEGDecompress( data_ptr, lines, line_samples,
                                   stream_id->fp) != 0) {
    return( NULL);
  }
  OalCloseStream( stream_id);

  /* Create the output oa_object and initialize it.  */

  oa_object = OaNewOaObject();
  oa_object->odltree = image_node;
  oa_object->data_ptr = data_ptr;
  oa_object->size = object_size;
  oa_object->is_in_memory = TRUE;
  oa_object->profile = Oa_profile;
  oa_object->stream_id = NULL;

  OaDeleteKwd( "ENCODING_TYPE", image_node);  /* Image now decompressed. */
  OaStrtoKwdValue( "ENCODING_COMPRESSION_RATIO", image_node, "1.00");
  return( oa_object);

} /* end if encoding_type == OA_CLEMENTINE_JPEG.  */


if (encoding_type == OA_HUFFMAN_FIRST_DIFFERENCE) {

  /* Call OaFindEncodingHistogram find the node in the tree describing the
     encoding histogram needed for HFD decompression; if found it, call
     OaReadHistogram to read the histogram data from the file into memory,
     then call OalCreateHFDTree to build the Huffman decoding tree.  */

  if ((histogram_node = OaFindEncodingHistogram( input_node)) == NULL) {
    sprintf( error_string, "%s: couldn't find encoding histogram for HFD",
             proc_name);
    strcat( error_string, " compressed image.");
    oa_errno = 530;
    OaReportError( error_string);
    return( NULL);
  } else {
    if ((histogram_object = OaReadHistogram( histogram_node)) == NULL) {
      sprintf( error_string, "%s: couldn't read encoding histogram for HFD",
               proc_name);
      strcat( error_string, " compressed image.");
      OaReportError( error_string);
      return( NULL);
    }
  }
  if ((hfd_decoding_tree = OalCreateHFDTree(  histogram_object->data_ptr))
                             == NULL) {
    sprintf( error_string, "%s: OalCreateHFDTree returned NULL.",
             proc_name);
    OaReportError( error_string);
    return( NULL);
  }

  /* Once the HFD tree is created, don't need the encoding histogram
     object anymore, so free it.  */

  OaDeleteObject( histogram_object);
}  /* end if encoding_type == OA_HUFFMAN_FIRST_DIFFERENCE */


/* The input tree ODL tree shouldn't ever be modified, so make a copy of all
   the nodes below the input node.  */

image_node = OaCopyTree( input_node, OA_STRIP_COMMENTS | OA_STRIP_SDT_NODES);

if (OaCheckODLTree( image_node) != 0) {
  OdlFreeTree( image_node);
  return(NULL);  /* Error message already issued and oa_errno set.  */
}

/* For multiband images, modify some keywords to trick OalCreateSDT into
   making the kind of SDT we want.  */

if (bands > 1) {

  /* Delete keywords for BANDS, BAND_STORAGE_TYPE and BAND_SEQUENCE since
     these will no longer be applicable for the in-memory monochromatic
     image.  */

  OaDeleteKwd( "BANDS",             image_node);
  OaDeleteKwd( "BAND_STORAGE_TYPE", image_node);
  OaDeleteKwd( "BAND_SEQUENCE",     image_node);

  switch( band_storage_type) {

    case OA_BAND_SEQUENTIAL:
      file_offset += (band - 1) * sample_bits/8 * line_samples * lines;
    break;

    case OA_LINE_INTERLEAVED:
      if (band > 1)
        OaLongtoKwdValue( "LINE_PREFIX_BYTES", image_node,
                          line_samples * sample_bits/8 * (band - 1));
      if (band < bands)
        OaLongtoKwdValue( "LINE_SUFFIX_BYTES", image_node,
                          line_samples * sample_bits/8 * (bands - band));
    break;

    case OA_SAMPLE_INTERLEAVED:

      /* Trick OalCreateSDT into creating the desired SDT by making it think
         each image line is transposed into an 2-dimensional image, where
         the desired band's pixels are in a column, with undesired band data
         to the left and right of the column, represented by prefix and
         suffix bytes.  Then stack all these images on top of one another to
         get the total transposed image.  The number of lines in this total
         transposed image is equal to the number of pixels in the real image
         times the number of lines in the real image, and there's only one
         sample per line.  */

      OaLongtoKwdValue( "LINES", image_node, line_samples * lines);
      OaLongtoKwdValue( "LINE_SAMPLES", image_node, (long) 1);

      if (band > 1)
        OaLongtoKwdValue( "LINE_PREFIX_BYTES", image_node,
                          sample_bits/8 * (band - 1));
      if (band < bands)
        OaLongtoKwdValue( "LINE_SUFFIX_BYTES", image_node,
                          sample_bits/8 * (bands - band));
    break;
  }
}

/* Call OalCreateSDT to create the SDT (Stream Decomposition Tree).  */

if ((sdt = OalCreateSDT( image_node, src_interchange_format)) == NULL) {
  sprintf( error_string, "%s: CreateSDT returned error.",
           proc_name);
  OaReportError( error_string);
  OalFreeSDT( image_node);
  return(NULL);
}

/* Note: sdt and image_node now point to the same node. */

/* Undo the keyword changes made above which tricked OalCreateSDT into
   creating the right kind of SDT.  */

if (band_storage_type == OA_SAMPLE_INTERLEAVED) {
  OaLongtoKwdValue( "LINES",        image_node, lines);
  OaLongtoKwdValue( "LINE_SAMPLES", image_node, line_samples);
}

/* Allocate memory for the image.  */

sdt_node_ptr = (SDT_node *) sdt->appl1;
object_size = sdt_node_ptr->dst.size * sdt_node_ptr->total_repetitions;
if ((data_ptr = (PTR) OaMalloc( (long) object_size)) == NULL) {
  sprintf( error_string, "%s: OaMalloc failed to allocate %ld bytes for ",
           proc_name, object_size);
  strcat( error_string, "image! Out of memory!");
  oa_errno = 720;
  OaReportError( error_string);
  exit(1);
  /*NOTREACHED*/
}

c = 0;
#ifdef IBM_PC

/* If we're on an IBM-PC, use a for loop to do the set, since memset
   may not work for setting greater than 64K and/or over segment
   boundaries;  otherwise use memset since it's usually optimized.  */

for (i=0; i<object_size; i++)
  data_ptr[i] = c;
#else
memset( data_ptr, c, (size_t) object_size);
#endif

/* Create the output oa_object and initialize it.  */

oa_object = OaNewOaObject();
oa_object->odltree = sdt;
oa_object->data_ptr = data_ptr;
oa_object->size = object_size;
oa_object->is_in_memory = TRUE;
oa_object->profile = Oa_profile;
oa_object->stream_id = NULL;

if (encoding_type == OA_HUFFMAN_FIRST_DIFFERENCE) {

  /* Allocate a buffer big enough to store a decompressed image line.  If
     there are prefix and/or suffix bytes, they are assumed to be part of
     each decompressed image line.  */

  if (LeftmostChild( sdt) == NULL)  /* No prefix or suffix bytes. */
    line_buffer_bytes = sdt_node_ptr->src.size * line_samples;
  else
    line_buffer_bytes = sdt_node_ptr->src.size;
  if ((line_buffer = (char *) OaMalloc( (size_t) line_buffer_bytes)) == NULL) {
    sprintf( error_string, "%s: OaMalloc failed to allocate %ld bytes ",
             proc_name, line_buffer_bytes);
    strcat( error_string, "for decompressed line buffer! Out of memory!");
    oa_errno = 720;
    OaReportError( error_string);
    exit(1);
    /*NOTREACHED*/
  }
}

if ((stream_id = OalOpenStream( data_filename, record_type, record_bytes,
                                file_records, "r")) == NULL) {
  sprintf( error_string, "%s: OalOpenStream returned error.",
           proc_name);
  OaReportError( error_string);
  OalFreeSDT( image_node);
  return(NULL);
}

/* Read the first slice of data; tell OalReadStream to read as many bytes as it
   wants, store them in its own buffer, start reading the file at the given
   byte offset, and output how many bytes it read in bytes_read.  */

if (OalReadStream( stream_id, (long) 0, &buf, file_offset, &bytes_read) != 0) {
  sprintf( error_string, "%s: OalReadStream returned error, aborting.",
           proc_name);
  OaReportError( error_string);
  OalFreeSDT( image_node);
  OalCloseStream( stream_id);
  LemmeGo( line_buffer);
  return(NULL);
}

/* Compress the SDT, and initialize current_node to point to the first
   end-node (data node) in the compressed SDT; initialize the node's dst.ptr
   with the pointer returned by OaMalloc.  */

compressed_SDT = OalCompressSDT( sdt);
current_node = OalInitializeSDT( compressed_SDT, data_ptr);

/* The main loop processes a buffer of data, then reads in the next buffer;
   the SDT maps desired buffer bytes to memory and throws aways the rest.  */

for (;;) {

  if (encoding_type == OA_HUFFMAN_FIRST_DIFFERENCE)
    OalHFDDecompress( buf, line_buffer, &bytes_read, &line_buffer_bytes, 
                      hfd_decoding_tree);
  else {
    line_buffer = buf;
    line_buffer_bytes = bytes_read;
  }

  if (OalProcessSDT( (PTR) line_buffer, line_buffer_bytes, &current_node) ==
      OA_REACHED_END_OF_SDT) break;

  /*  Read next slice of data, telling OalReadStream to do the same as the
      first time, except start at the current file position.  */

  buf = NULL;
  if (OalReadStream( stream_id, (long) 0, &buf, (long) -1, &bytes_read) != 0) {
    sprintf( error_string, "%s: OalReadStream returned error, aborting.",
             proc_name);
    OaReportError( error_string);
    break;
  }
}
OalCloseStream( stream_id);
LemmeGo( line_buffer);

/* Free the compressed SDT. */

OalFreeSDT( compressed_SDT);

/* Call OalSDTtoODLTree to modify the new ODL tree to reflect the in-memory
   data.  This changes the DATA_TYPE keywords to reflect the in-memory data
   values which may have been converted to native types.  It also strips off
   ODL tree nodes which have SDT_node->dst.size=0.  */

OalSDTtoODLTree( sdt, dst_interchange_format);

/* Remove keywords for ENCODING_TYPE, LINE_PREFIX_BYTES and LINE_SUFFIX_BYTES,
   since these are no longer applicable for the in-memory IMAGE.  */

OaDeleteKwd( "ENCODING_TYPE", image_node);
OaDeleteKwd( "LINE*PREFIX*BYTES", image_node);
OaDeleteKwd( "LINE*SUFFIX*BYTES", image_node);
OaDeleteKwd( "^LINE*PREFIX*STRUCTURE", image_node);
OaDeleteKwd( "^LINE*SUFFIX*STRUCTURE", image_node);

if (encoding_type == OA_HUFFMAN_FIRST_DIFFERENCE)
  OalFreeHFDTree( hfd_decoding_tree);
return( oa_object);
}



/*****************************************************************************

  Routine:  OaReadImagePixels

  Description:  OaReadImagePixels is a low-level routine which returns pixels
                (samples) from an image starting at the specified location
                (line, sample number); band has already been set in
                OaOpenImage.  The number of pixels it returns varies according
                to the start location, the compression type, and the line
                length.  The high level routine OaReadPartialImage makes calls
                to OaReadImagePixels, and is the preferred routine for most
                users.  OaReadImagePixels is part of the sequence of calls: 
                OaOpenImage, OaReadImagePixels (multiple calls), OaCloseImage.
                The samples are decompressed and converted to native format
                according to Oa_profile, and the image handle object's
                data_ptr set pointing to them.  The data file is left open.
                OaOpenImage must be called before making calls to
                OaReadImagePixels.  After making the last call to
                OaReadImagePixels, call OaCloseImage to free the image handle
                object and its internal buffers.

  Author:  Steve Monk, University of Colorado LASP

  Creation Date:  29 Sept  1994
  Last Modified:  02 Feb   1998

  History:

    Creation - This routine was part of the Beta Release of the OA library.
    12/11/96 - Added support for multiband images.  SM
    02/02/98 - Fixed OalSeek bug in HDF part.  SM

  Input: 
         image_handle_object - A pointer to an Oa_Object structure which was
                               initialized by a call to OaOpenImage. 
                               Previous calls may have been made to
                               OaReadImagePixels.  The image_handle_object's
                               appl1 pointer points to an image_handle
                               structure, which keeps track of the position and
                               number of the last read samples (pixels), and
                               buffers the last read samples.   
                
         start_line          - The LINE at which to start returning samples;
                               1 <= start_line <= LINES

         start_sample        - The sample number at which to start returning
                               samples;  1 <= start_sample <= LINE_SAMPLES

         The global variable Oa_profile is set to valid values.

  Output:  If successful, the routine returns the number of pixels read,
           or 0 on error.  The input OA_Object structure contains an ODL tree
           describing the samples as a IMAGE with 1 line and LINE_SAMPLES
           pixels.  The pixels are pointed to by image_handle_object->data_ptr,
           and image_handle_object->size is set to the total number of bytes. 
           If image_handle_object->data_ptr was non-NULL on input, then it
           has been freed and now points to the newly allocated memory.
        
  Notes: 
  1) The caller should not modify the image_handle structure in
     image_handle_object.
  2) There is a minimal buffering system underlying this routine - the data
     read, compressed and converted from the previous call is saved in a
     buffer.
  3) The image handle object's data_ptr is set to point into the image
     handle's internal buffer, and should NOT be freed, since it's not
     necessarily a base pointer returned by OaMalloc, so don't use
     OaDeleteObject to free the image handle object - use OaCloseImage instead!
  4) Current limitations:
     a) The supported compression (encoding) types are Previous Pixel and
        Huffman First Difference.
     b) If the image is HFD encoded, it must be in a variable-length record
        file. 
     c) Uncompressed and Previous Pixel compressed images must be in
        fixed-length or undefined record files;  variable-length record files
        are only supported for HFD images.
     d) Previous Pixel encoded images may not have prefix or suffix bytes.
     e) Multi-band images may not be compressed, nor have prefix or suffix
        bytes.

******************************************************************************/

#ifdef _NO_PROTO

int OaReadImagePixels( image_handle_object, start_line, start_sample)
OA_OBJECT image_handle_object;
long start_line, start_sample;
#else

int OaReadImagePixels( OA_OBJECT image_handle_object, long start_line,
                       long start_sample)

#endif
{

static char *proc_name = "OaReadImagePixels";
ODLTREE image_node, current_node;
SDTNODE image_node_sdt_ptr;
char *buf = NULL, *ptr;
long bytes_to_read, bytes_read, bytes, record_number;
long byte_offset, sub_byte_offset, current_samples, buf_sample_offset;
long line_bytes_including_all_bands, requested_sample_offset;
short *decompressed_PP;
struct oa_image_handle *image_handle = NULL;
short endian_indicator=1;       /* Initialized to 1 to determine the value */
                                /* of *platform_is_little_endian below.    */
char *platform_is_little_endian = (char *) &endian_indicator;
/* If platform is little-endian (lsb first), then the 1 initialized in
   endian_indicator will be in the first byte, and *platform_is_little_endian
   will have the value TRUE (1), otherwise it will be FALSE (0).  A test is
   done on *platform_is_little_endian in the Previous Pixel section below to
   decide whether or not to swap a 2-byte pixel from MSB to LSB order.  */

/* Check input arguments. */

if (image_handle_object == NULL) {
  sprintf( error_string, "%s: input image_handle_object is NULL.", proc_name);
  oa_errno = 501;
  OaReportError( error_string);
  return(0);
}

if (image_handle_object->appl1 == NULL) {
  sprintf( error_string,
           "%s: input image_handle_object must have an image_handle",
           proc_name);
  strcat( error_string, " structure attached via appl1.");
  oa_errno = 501;
  OaReportError( error_string);
  return(0);
}

image_handle = (struct oa_image_handle *) image_handle_object->appl1;
if ((start_line < 1) || (start_line > image_handle->source_lines) ||
    (start_sample < 1) || (start_sample > image_handle->source_line_samples)) {
  sprintf( error_string,
           "%s: invalid start_line or start_sample.", proc_name);
  oa_errno = 502;
  OaReportError( error_string);
  return(0);
}

if ((image_handle->compression_type != OA_UNCOMPRESSED) &&
    (image_handle->compression_type != OA_HUFFMAN_FIRST_DIFFERENCE) &&
    (image_handle->compression_type != OA_PREVIOUS_PIXEL)) {
  sprintf( error_string, "%s: unsupported compression type: %d.", proc_name,
           (int) image_handle->compression_type);
  oa_errno = 520;
  OaReportError( error_string);
  return(0);
}

image_node = image_handle_object->odltree;
image_node_sdt_ptr = (SDT_node *) image_node->appl1;

/* Three independent code sections follow, for HUFFMAN_FIRST_DIFFERENCE,
   UNCOMPRESSED, and PREVIOUS_PIXEL.  */



if (image_handle->compression_type == OA_HUFFMAN_FIRST_DIFFERENCE) {

  /* (Re)initialize the SDT so it's ready to process a new image line.  */

  current_node = OalInitializeSDT( image_node, image_handle->buf);

  /* If the requested start line isn't in the image_handle's buffer, have to
     go to the file.  The line number of the data in the buffer is one less
     than image_handle->next_line, since one line of data was saved from the
     last call.  */

  if ((start_line != (image_handle->next_line - 1)) ||
      (image_handle->buf_samples == 0)) {

    /* Start_line isn't buffered, so have to go to the file.  If we aren't
       already positioned to read the start_line, call OaSeek to position
       there.  HFD compressed images are always in variable-length record
       files, with one record for each line;  however, records and lines don't
       necessarily match one-to-one because the image may not start at the
       first record.  Note: stream_id->current_position starts at 0, and
       start_line and image_handle->next_line start at 1.  */

    if (start_line != image_handle->next_line) {

      record_number = image_handle_object->stream_id->current_position +
                      (start_line - image_handle->next_line);
      if (OalSeek( image_handle_object->stream_id, record_number) != 0) {
        sprintf( error_string, "%s: OalSeek returned error, aborting.",
                 proc_name);
        OaReportError( error_string);
        return(0);
      }
      image_handle->next_line = start_line;
    }

    /* Read one variable-length record, which contains one compressed line;
       decompress it and filter it through the SDT.  */

    buf = NULL;
    if (OalReadStream( image_handle_object->stream_id, 0, &buf, -1,
                       &bytes_read) != 0) {
      sprintf( error_string, "%s: OalReadStream returned error, aborting.",
               proc_name);
      OaReportError( error_string);
      return(0);
    }
    image_handle->next_line += 1;
    bytes = image_handle->decomp.HFD.line_buffer_bytes;
    OalHFDDecompress( buf, image_handle->decomp.HFD.line_buffer, &bytes_read,
                      &bytes, image_handle->decomp.HFD.decoding_tree);

    if (bytes != image_handle->decomp.HFD.line_buffer_bytes) {
      sprintf( error_string, "%s: SDT source size is not equal to size of HFD",
               proc_name);
      strcat( error_string, " decompressed line buffer.");
      oa_errno = 710;
      OaReportError( error_string);
      return(0);
    }
    OalProcessSDT( (PTR) image_handle->decomp.HFD.line_buffer, bytes, 
                   &current_node);
  }  /* end if requested line isn't in image_handle's buffer */

  /* Requested line is now in the image_handle's buffer, so set the
     image handle object's data_ptr to point to the start sample in the
     buffer.  */

  bytes = (image_handle->source_line_samples - start_sample + 1) *
           image_handle->sample_bytes;
  image_handle_object->data_ptr = (PTR) image_handle->buf +
                                  (start_sample-1)*image_handle->sample_bytes;
  image_handle_object->size = bytes;
  image_handle_object->is_in_memory = TRUE;

  /* Update keywords describing the samples now in pointed to by
     image_handle_object->data_ptr.  */

  OaLongtoKwdValue( "LINE_SAMPLES", image_node,
                     image_handle->source_line_samples - start_sample + 1);
  OaLongtoKwdValue( "FIRST_LINE", image_node, (long) start_line);
  OaLongtoKwdValue( "FIRST_SAMPLE", image_node, (long) start_sample);

  return( (int) image_handle->source_line_samples - start_sample + 1);

}  /* end if compression_type == OA_HUFFMAN_FIRST_DIFFERENCE  */



if (image_handle->compression_type == OA_UNCOMPRESSED) {

  /* (Re)initialize the SDT so it's ready to process a new image line.  */

  current_node = OalInitializeSDT( image_node, image_handle->buf);

  /* If start_line isn't in the image_handle's buffer, have to go to the file.
     The line number of the data in the buffer saved from the last call is one
     less than image_handle->next_line.  */

  if ((start_line != (image_handle->next_line - 1)) ||
      (image_handle->buf_samples == 0)) {

    /* Start_line isn't buffered, so go to the file.  If we aren't already
       positioned to read start_line, call OaSeek to position there. 
       Notes on OalSeek:
       1) Uncompressed images are only supported in fixed-length and undefined
          record files.  The record length may be less than, equal to, or
          greater than an image line (plus prefix and suffix bytes, if any). 
       2) Monochromatic images are read one line at a time; the current line is
          buffered, and the file pointer is left pointing to the beginning of
          the next line.
       3) Multi-band images are read one line of one band at a time, except
          for pixel interleaved images, where one line containing all the
          bands for that line are read.  This means the file pointer is left
          pointing to the next line, except for line interleaved images,
          where it is left pointing to the next band in the line just read.
       4) stream_id->current_position starts at 0, and start_line and
          image_handle->next_line start at 1.  */

    if (start_line != image_handle->next_line) {

      if (image_handle->band_storage_type == OA_LINE_INTERLEAVED) {
        line_bytes_including_all_bands = image_handle->source_line_samples *
                                         image_handle->sample_bytes *
                                         image_handle->bands;

        /* sub_byte_offset is the number of bytes from the current band
           back to the first band of this line.  First we get back to the
           first band in this line, then calculate the offset to the
           desired line from there.  */

        sub_byte_offset = (image_handle_object->stream_id->current_position -
                           image_handle->image_start_offset) %
                           line_bytes_including_all_bands;
        byte_offset = image_handle_object->stream_id->current_position -
                      sub_byte_offset +
                      (start_line - image_handle->next_line) *
                      line_bytes_including_all_bands;
      } else {
        byte_offset = image_handle_object->stream_id->current_position +
                      (start_line - image_handle->next_line) *
                      image_node_sdt_ptr->src.size *
                      image_node_sdt_ptr->total_repetitions;
      }
      if (OalSeek( image_handle_object->stream_id, byte_offset) != 0) {
        sprintf( error_string, "%s: OalSeek returned error, aborting.",
                 proc_name);
        OaReportError( error_string);
        return(0);
      }
      image_handle->next_line = start_line;
    }   /* end if line not in image_handle buffer */

    /* Loop calling OalReadStream and OalProcessSDT until exactly one line has
       been processed.  Since the Stream Layer is reading a fixed-length or
       undefined record file, it can read an exact specified number of bytes
       (not so for variable-length record files, which is why they're not
       supported here).  */

    bytes_to_read = image_node_sdt_ptr->src.size *
                    image_node_sdt_ptr->total_repetitions;
    while (bytes_to_read > 0) {
      buf = NULL;
      if (OalReadStream( image_handle_object->stream_id, bytes_to_read,
                         &buf, -1, &bytes_read) != 0) {
        sprintf( error_string, "%s: OalReadStream returned error, aborting.",
                 proc_name);
        OaReportError( error_string);
        return(0);
      }
      bytes_to_read -= bytes_read;
      if (OalProcessSDT( (PTR) buf, bytes_read, &current_node) ==
         OA_REACHED_END_OF_SDT) break;
    }
    if (bytes_to_read != 0) {
      sprintf( error_string,
               "%s: total bytes read from file: %d is not equal to size",
               proc_name, (int) (image_node_sdt_ptr->src.size -
                                 bytes_to_read));
      strcat( error_string, " of source line in SDT");
      strcat( error_string,
              "\nImage position and file position are out-of-sync.");
      oa_errno = 710;
      OaReportError( error_string);
      return(0);
    }
    image_handle->next_line += 1;
  }  /* end if requested line isn't in image_handle's buffer */

  /* Requested line is now in the image_handle's buffer, so set the
     image handle object's data_ptr to point to the start sample in the
     buffer.  */

  bytes = (image_handle->source_line_samples - start_sample + 1) *
           image_handle->sample_bytes;
  image_handle_object->data_ptr = (PTR) image_handle->buf + 
                                  (start_sample-1) * 
                                  image_handle->sample_bytes;
  image_handle_object->size = bytes;
  image_handle_object->is_in_memory = TRUE;

  /* Update keywords describing the samples now in pointed to by
     image_handle_object->data_ptr.  */

  OaLongtoKwdValue( "LINE_SAMPLES", image_node,
                     image_handle->source_line_samples - start_sample + 1);
  OaLongtoKwdValue( "FIRST_LINE", image_node, (long) start_line);
  OaLongtoKwdValue( "FIRST_SAMPLE", image_node, (long) start_sample);
  return( (int) (image_handle->source_line_samples - start_sample + 1));

}  /* end if compression_type == OA_UNCOMPRESSED */



if (image_handle->compression_type == OA_PREVIOUS_PIXEL) {

  /* If the requested start_line, start_sample isn't in the image_handle's
     buffer, have to go to the file.  requested_sample_offset and
     buf_sample_offset are the offsets in sample units of the requested
     sample and the first sample in the buffer, respectively; both start at 0.
     Note: image_handle->buf contains image_handle->buf_samples samples saved
     by the last call.  */

  requested_sample_offset  = (start_line-1) *
                              image_handle->source_line_samples +
                             (start_sample-1);
  buf_sample_offset = (image_handle->next_line-1) *
                      image_handle->source_line_samples +
                      (image_handle->next_sample-1) -
                      image_handle->buf_samples;

  if (requested_sample_offset < buf_sample_offset) {

    /* Requested start_sample isn't in the image_handle's buffer, and it
       occurs in the file before the next sample the file pointer is positioned
       at, so reset the file pointer to the start of the image and start all
       over again.  */

    if (OalSeek( image_handle_object->stream_id,
                 image_handle->image_start_offset) != 0) {
      sprintf( error_string, "%s: OalSeek returned error, aborting.",
               proc_name);
      OaReportError( error_string);
      return(0);
    }
    image_handle->buf_samples = 0;
    image_handle->next_line = 1;
    image_handle->next_sample = 1;
    image_handle->decomp.PP.found_255 = FALSE;
    image_handle->decomp.PP.previous_byte_exists = FALSE;
    buf_sample_offset = 0;
  }

  /* Loop reading from the file until the offset of the last sample in the
     buffer is greater than or equal to the offset of the requested start
     pixel, i.e. until the start pixel is in the buffer.  Since this may be
     the first read, the buffer might be empty, so check buf_samples too.  */

  decompressed_PP = (short *) image_handle->buf;
  while ((image_handle->buf_samples == 0) ||
         (buf_sample_offset + image_handle->buf_samples <=
          requested_sample_offset)) {

    /* Read a single stream_id->buf full of data.  */

    buf = NULL;
    if (OalReadStream( image_handle_object->stream_id, 0, &buf, -1,
                       &bytes_read) != 0) {
      sprintf( error_string, "%s: OalReadStream returned error, aborting.",
               proc_name);
      OaReportError( error_string);
      return(0);
    }

    /* Decompress the data in stream_id->buf.  If this isn't the first buffer
       of compressed data, then the decompression state was saved as follows,
       depending on the last byte in the previous buffer:
       1) If the last byte was a 255 flagging the start of a 2-byte pixel,
          found_255 = TRUE.
       2) If the last byte occurred after such a 255, found_255 = TRUE,
          previous_byte = <last byte>, and previous_byte_exists = TRUE.
       Note that a 255 can occur in either of the 2 bytes following the 255
       which flags the start of a pixel.  */

    current_samples = 0;

    /* If this is the beginning of the compressed data stream, check that the
       first byte is a 255.  */

    if ((image_handle->buf_samples == 0) &&
        (image_handle->decomp.PP.found_255 == FALSE) &&
        ((unsigned char) buf[0] != 255)) {
      sprintf( error_string, "%s: first byte in Previous Pixel compressed ",
               proc_name);
      strcat( error_string, "data was not a 255.");
      oa_errno = 710;
      OaReportError( error_string);
      return(0);
    }

    /* Loop through all the bytes in the buffer, decompressing them.  */

    for (byte_offset = 0; byte_offset < bytes_read; byte_offset++) {
      if (image_handle->decomp.PP.found_255 == TRUE) {
        if (image_handle->decomp.PP.previous_byte_exists == TRUE) {

          /* The last byte was the first (MSB) byte of a 2-byte pixel;
             combine it with the current (LSB) byte, store it, and
             re-initialize the parsing variables.  */

          ptr = (char *) &(decompressed_PP[ current_samples]);
          if (*platform_is_little_endian == TRUE) {
            ptr[0] = buf[ byte_offset];
            ptr[1] = image_handle->decomp.PP.previous_byte;  /* MSB */
          } else {
            ptr[1] = buf[ byte_offset];
            ptr[0] = image_handle->decomp.PP.previous_byte;  /* MSB */
          }
          image_handle->decomp.PP.previous_pixel =
            decompressed_PP[ current_samples];
          current_samples++;
          image_handle->decomp.PP.found_255 = FALSE;
          image_handle->decomp.PP.previous_byte_exists = FALSE;

        } else {

          /* The last byte was a 255 marking the start of a 2-byte pixel;
             the current byte is the first (MSB) byte of the pixel; save it
             in previous_byte.  */

          image_handle->decomp.PP.previous_byte = buf[ byte_offset];
          image_handle->decomp.PP.previous_byte_exists = TRUE;
        }

      } else {

        /* There is no stored-up data, so examine the next byte.  */

        if ((unsigned char) buf[ byte_offset] == 255) {
          image_handle->decomp.PP.found_255 = TRUE;
        } else {
          decompressed_PP[ current_samples] =
            image_handle->decomp.PP.previous_pixel +
            (unsigned char) buf[byte_offset] - 127;
          image_handle->decomp.PP.previous_pixel =
            decompressed_PP[ current_samples];
          current_samples++;
        }
      }
    }  /* end for loop processing all bytes in buf.  */

    /* Update the variables which keep track of the position of the next pixel
       to be read, and the offset (in pixels from the beginning of the
       image) of the first sample in the buffer.  */

    buf_sample_offset += image_handle->buf_samples;
    image_handle->buf_samples = current_samples;
    image_handle->next_line = (buf_sample_offset + current_samples) /
                              image_handle->source_line_samples + 1;
    image_handle->next_sample = (buf_sample_offset + current_samples) % 
                                image_handle->source_line_samples + 1;

  }  /* end while requested start pixel isn't in image_handle's buffer */

  /* Requested start pixel is now in the image_handle's buffer, so set the
     image handle object's data_ptr to point to the start sample in the
     buffer.  */

  bytes = (image_handle->buf_samples -
          (requested_sample_offset - buf_sample_offset)) * 2;
  image_handle_object->data_ptr = (PTR) image_handle->buf +
                                  image_handle->buf_samples*2 - bytes;
  image_handle_object->size = bytes;
  image_handle_object->is_in_memory = TRUE;

  /* Update keywords describing the samples now in pointed to by
     image_handle_object->data_ptr.  */

  OaLongtoKwdValue( "LINE_SAMPLES", image_node, (long) (bytes/2));
  OaLongtoKwdValue( "FIRST_LINE", image_node, (long) start_line);
  OaLongtoKwdValue( "FIRST_SAMPLE", image_node, (long) start_sample);
  return( (int) bytes/2);

}  /* end if compression_type == OA_PREVIOUS_PIXEL  */
/*NOTREACHED*/
return(0);
}



/*****************************************************************************

  Routine:  OaReadImageFromQube

  Description:  OaReadImageFromQube opens a data file and reads in the IMAGE
                for the specified band from the core of the QUBE into memory.
                It throws out all suffixes, if any are present.

  Author:  Steve Monk, University of Colorado LASP

  Creation Date:  27 Nov   1995
  Last Modified:  09 Mar   1998

  History:

    Creation - This routine was part of the Version 1.0 Release of the OA
               library.
    03/09/98 - Added BIL and suffix plane support.  SM

  Input: 
         input_node - A pointer to an ODL tree node of class QUBE.
                      The tree above the input_node should have keywords
                      to specify the data file.

         band       - The band to read.
      
         The global variable Oa_profile is set to valid values.

  Output:  If successful, the routine returns a pointer to an OA_Object
           structure, which contains an ODL tree of type IMAGE and a pointer
           to the image data.  If unsuccessful it returns NULL.

  Notes: 

  1) Suffixes: in standard ISIS Qubes, each item in a suffix is allocated 4
     bytes, even though the data type stored there may be less than 4 bytes;
     in fact, some Qubes have suffixes allocated, but no actual suffix data
     in them.  This is so the user can add suffix data without rewriting the
     file.

*****************************************************************************/

#ifdef _NO_PROTO

OA_OBJECT OaReadImageFromQube( input_node, band)
ODLTREE input_node;
int     band;

#else

OA_OBJECT OaReadImageFromQube( ODLTREE input_node, int band)

#endif
{

static char *proc_name = "OaReadImageFromQube";
long file_offset, record_bytes, file_records;
int record_type, src_interchange_format, dst_interchange_format;
char *label_filename, *data_filename;
OA_OBJECT oa_object;
ODLTREE current_node, sdt, image_node;
ODLTREE compressed_SDT;
SDTNODE sdt_node_ptr;
long object_size, line_samples, lines, bands, core_item_bytes, skip_bytes;
#ifdef IBM_PC
long i;
#endif
long *suffix_items, *core_items, line_suffix_bytes, bytes_read;
char *buf = NULL, c, **axis_names, *core_item_type;
PTR data_ptr;
struct OaStreamStruct *stream_id;
int band_storage_type=OA_UNKNOWN_BAND_STORAGE_TYPE;
unsigned long total_bytes_read=0, total_bytes_to_read;

dst_interchange_format = OA_BINARY_INTERCHANGE_FORMAT;

if (OaGetQubeKeywords( input_node, &core_items, &axis_names, &suffix_items,
                       &core_item_bytes, &core_item_type) != 0)
  return( NULL);

if (OaGetFileKeywords( input_node, &label_filename, &data_filename,
                       &record_type, &record_bytes, &file_records,
                       &file_offset, &src_interchange_format) != 0) {
  return(NULL);  /* Error message already issued.  */
}

#ifdef OA_DEBUG
OaReportFileAttributes( label_filename, data_filename, record_type,
                        record_bytes, file_offset, src_interchange_format);
#endif

/* The storage orders this routine can currently handle are band-sequential,
   indicated by AXIS_NAME = (SAMPLE, LINE, BAND), and band-interleaved-by-
   line, indicated by AXIS_NAME = (SAMPLE, BAND, LINE).  */

if ((strcmp( axis_names[0], "SAMPLE") == 0) &&
    (strcmp( axis_names[1], "LINE") == 0) &&
    (strcmp( axis_names[2], "BAND") == 0)) {
  band_storage_type = OA_BAND_SEQUENTIAL;
  bands = core_items[2];
}
if ((strcmp( axis_names[0], "SAMPLE") == 0) &&
    (strcmp( axis_names[1], "BAND") == 0) &&
    (strcmp( axis_names[2], "LINE") == 0)) {
  band_storage_type = OA_LINE_INTERLEAVED;
  bands = core_items[1];
}
if ((band_storage_type != OA_BAND_SEQUENTIAL) &&
    (band_storage_type != OA_LINE_INTERLEAVED)) {
  sprintf( error_string, "%s: Cannot handle this storage order: %s %s %s",
           proc_name, axis_names[0], axis_names[1], axis_names[2]);
  oa_errno = 520;
  OaReportError( error_string);
  return( NULL);
}

/* Check that input 'band' is within the range of existing bands.  */

if ((band < 1) || (band > bands)) {
  sprintf( error_string, "%s: input band is out-of-range", proc_name);
  oa_errno = 502;
  OaReportError( error_string);
  return( NULL);
}


/* The technique used to read in an image from the qube is trick OalCreateSDT
   into thinking it's creating an SDT for an image.  We use LINE_SUFFIX_BYTES
   to represent all the data between the end of one image line and the start
   of the next image line, so that all this data will be thrown out;  this
   includes suffixes in various dimensions, corners, and all the core data
   belonging to other bands.  Then we set file_offset to be the start of
   the qube plus all the interveaning core, suffixes, corners etc. to get
   to the start of the first line in the desired band.  In the case of the
   last line, if our LINE_SUFFIX_BYTES goes past the end of the file, then
   OalReadStream will give an error which we'll catch by keeping track of
   whether we've read all the image lines (total_bytes_read >=
   total_bytes_to_read).  This can't happen for band-sequential, but can
   for band-interleaved-by-line.  */

if (band_storage_type == OA_BAND_SEQUENTIAL) {
  lines = core_items[1];
  line_samples = core_items[0];
  line_suffix_bytes = suffix_items[0] * 4;
  skip_bytes = (suffix_items[1] * 4) * line_samples;     /* bottom plane */
  skip_bytes += (suffix_items[0] * 4) * suffix_items[1]; /* corner */

  /* Calculate the offset into the QUBE, in bytes, of the first pixel of the
     first line of the desired image, and add it to the offset of start of the
     QUBE.  */

  file_offset += (((line_samples * core_item_bytes + line_suffix_bytes) *
                 lines) + skip_bytes) * (band-1);
  total_bytes_to_read = (line_samples * core_item_bytes + line_suffix_bytes) *
                         lines;
}

if (band_storage_type == OA_LINE_INTERLEAVED) {
  line_samples = core_items[0];
  lines = core_items[2];
  line_suffix_bytes = suffix_items[0] * 4;

  /* Core and line suffix bytes between end of line (beginning of line's
     suffix bytes) and start of next line in same band. */
  skip_bytes = (line_samples * core_item_bytes + line_suffix_bytes) *
               (bands - 1) + line_suffix_bytes;

  /* Add one line's worth of backplane bytes.  */
  skip_bytes += suffix_items[1] * 4 * line_samples;

  /* Add corner bytes. */
  skip_bytes += suffix_items[1] * line_suffix_bytes;

  /* Calculate the offset into the QUBE, in bytes, of the first pixel of the
     first line of the desired image, and add it to the offset of start of the
     QUBE.  */

  file_offset += ((line_samples * core_item_bytes + line_suffix_bytes) *
                  (band-1));
  total_bytes_to_read = (line_samples * core_item_bytes + skip_bytes) *
                         lines - skip_bytes + line_suffix_bytes;
  line_suffix_bytes = skip_bytes;
}


/* Set up an ODL tree describing an image, then let OalCreateSDT do the work
   of creating the SDT.  */

image_node = OdlNewObjDesc( "IMAGE", NULL, NULL, NULL, NULL, NULL,
                            (short) 0, (long) 0);
OaLongtoKwdValue( "LINE_SAMPLES", image_node, line_samples);
OaLongtoKwdValue( "SAMPLE_BITS", image_node, core_item_bytes * 8);
OaLongtoKwdValue( "LINES", image_node, lines);
OaStrtoKwdValue( "SAMPLE_TYPE", image_node, core_item_type);
if (line_suffix_bytes > 0)
  OaLongtoKwdValue( "LINE_SUFFIX_BYTES", image_node, line_suffix_bytes);


/* Call OalCreateSDT to create the SDT (Stream Decomposition Tree).  */

if ((sdt = OalCreateSDT( image_node, src_interchange_format)) == NULL) {
  sprintf( error_string, "%s: CreateSDT returned error.",
           proc_name);
  OaReportError( error_string);
  OalFreeSDT( image_node);
  return(NULL);
}

/* Note: sdt and image_node now point to the same node. */

/* Allocate memory for the image.  */

sdt_node_ptr = (SDT_node *) sdt->appl1;
object_size = sdt_node_ptr->dst.size * sdt_node_ptr->total_repetitions;
if ((data_ptr = (PTR) OaMalloc( (long) object_size)) == NULL) {
  sprintf( error_string, "%s: OaMalloc failed to allocate %ld bytes for ",
           proc_name, object_size);
  strcat( error_string, "image! Out of memory!");
  oa_errno = 720;
  OaReportError( error_string);
  exit(1);
  /*NOTREACHED*/
}
if (dst_interchange_format == OA_ASCII_INTERCHANGE_FORMAT)
  c = ' ';
else
  c = 0;

#ifdef IBM_PC

/* If we're on an IBM-PC, use a for loop to do the set, since memset
   may not work for setting greater than 64K and/or over segment
   boundaries;  otherwise use memset since it's usually optimized.  */

for (i=0; i<object_size; i++)
  data_ptr[i] = c;
#else
memset( data_ptr, c, (size_t) object_size);
#endif

/* Create the output oa_object and initialize it.  */

oa_object = OaNewOaObject();
oa_object->odltree = sdt;
oa_object->data_ptr = data_ptr;
oa_object->size = object_size;
oa_object->is_in_memory = TRUE;
oa_object->profile = Oa_profile;
oa_object->stream_id = NULL;

if ((stream_id = OalOpenStream( data_filename, record_type, record_bytes,
                                file_records, "r")) == NULL) {
  sprintf( error_string, "%s: OalOpenStream returned error.",
           proc_name);
  OaReportError( error_string);
  OalFreeSDT( image_node);
  return(NULL);
}

/* Read the first slice of data; tell OalReadStream to read as many bytes as it
   wants, store them in its own buffer, start reading the file at the given
   byte offset, and output how many bytes it read in bytes_read.  */

if (OalReadStream( stream_id, (long) 0, &buf, file_offset, &bytes_read) != 0) {
  sprintf( error_string, "%s: OalReadStream returned error, aborting.",
           proc_name);
  OaReportError( error_string);
  OalFreeSDT( image_node);
  OalCloseStream( stream_id);
  return(NULL);
}
total_bytes_read += bytes_read;

/* Compress the SDT, and initialize current_node to point to the first
   end-node (data node) in the compressed SDT; initialize the node's dst.ptr
   with the pointer returned by OaMalloc.  */

compressed_SDT = OalCompressSDT( sdt);
current_node = OalInitializeSDT( compressed_SDT, data_ptr);

/* The main loop processes a buffer of data, then reads in the next buffer;
   the SDT maps desired buffer bytes to memory and throws aways the rest.  */

for (;;) {

  if (OalProcessSDT( (PTR) buf, bytes_read, &current_node) ==
      OA_REACHED_END_OF_SDT) break;

  /*  Read next slice of data, telling OalReadStream to do the same as the
      first time, except start at the current file position.  */

  buf = NULL;
  if (OalReadStream( stream_id, (long) 0, &buf, (long) -1, &bytes_read) != 0) {
    if (total_bytes_read < total_bytes_to_read) {
      sprintf( error_string, "%s: OalReadStream returned error, aborting.",
               proc_name);
      OaReportError( error_string);
    }
    break;
  }
  total_bytes_read += bytes_read;
}
OalCloseStream( stream_id);

/* Free the compressed SDT. */

OalFreeSDT( compressed_SDT);

/* Call OalSDTtoODLTree to modify the new ODL tree to reflect the in-memory
   data.  This changes the DATA_TYPE keywords to reflect the in-memory data
   values which may have been converted to native types.  It also strips off
   ODL tree nodes which have SDT_node->dst.size=0.  */

OalSDTtoODLTree( sdt, dst_interchange_format);

OaDeleteKwd( "LINE*SUFFIX*BYTES", image_node);  /* No longer applicable.  */

return( oa_object);
}



/*****************************************************************************

  Routine:  OaReadSpectrumFromImage

  Description:  This routine reads a spectrum from a multi-band image, at
                the given line/sample location.

  Author:  Steve Monk, University of Colorado LASP

  Creation Date:  11 Dec   1996
  Last Modified:  11 Dec   1996

  History:

    Creation - This routine was part of the Version 1.2 release of the OA
               library.

  Input: 
         input_node - A pointer to an ODL tree node of class IMAGE.
                      The tree above the input_node should have keywords
                      to specify the data file.

         line       - The line to read the spectrum from.

         sample     - The sample to read the spectrum from.

  Output:  If successful, the routine returns a pointer to an OA_Object
           structure, which contains an ODL tree and a pointer to the spectrum
           data.  The spectrum contains all the bands of the image, at the
           given line/sample location.  If unsuccessful it returns NULL.

  Notes: 

*****************************************************************************/

#ifdef _NO_PROTO

OA_OBJECT OaReadSpectrumFromImage( input_node, line, sample)
ODLTREE input_node;
int line;
int sample;

#else

OA_OBJECT OaReadSpectrumFromImage( ODLTREE input_node, int line, int sample)

#endif
{

static char *proc_name = "OaReadSpectrumFromImage";
long file_offset, record_bytes, file_records;
int record_type, src_interchange_format, dst_interchange_format;
char *label_filename, *data_filename;
OA_OBJECT oa_object;
ODLTREE current_node, sdt, spectrum_node, column_node, compressed_SDT;
SDTNODE sdt_node_ptr;
long object_size, bands, bytes_read;
#ifdef IBM_PC
long i;
#endif
long lines, line_samples, sample_bits, line_prefix_bytes, line_suffix_bytes;
char *buf = NULL, c, *sample_type_str, *str;
PTR data_ptr;
struct OaStreamStruct *stream_id;
int encoding_type, band_storage_type;

dst_interchange_format = OA_BINARY_INTERCHANGE_FORMAT;

if (OaGetImageKeywords( input_node, &lines, &line_samples, &sample_bits,
                        &sample_type_str, &bands, &band_storage_type,
                        &line_prefix_bytes, &line_suffix_bytes,
                        &encoding_type) != 0)
  return(NULL);

if ((line < 1) || (line > lines) || (sample < 1) || (sample > line_samples)) {
  sprintf( error_string, "%s: out of range line or sample argument.",
           proc_name);
  oa_errno = 520;
  OaReportError( error_string);
  return(NULL);
}
if (encoding_type == OA_UNKNOWN_ENCODING_TYPE) {
  oa_errno = 520;
  return(NULL);
}
if (bands < 2) {
  sprintf( error_string, "%s: image has only one band!", proc_name);
  oa_errno = 520;
  OaReportError( error_string);
  return(NULL);
}
if (encoding_type != OA_UNCOMPRESSED) {
  sprintf( error_string, "%s: compressed multi-band images not supported!",
           proc_name);
  oa_errno = 520;
  OaReportError( error_string);
  return(NULL);
}
if ((line_prefix_bytes != 0) || (line_suffix_bytes != 0)) {
  sprintf( error_string,
           "%s: LINE_PREFIX_BYTES and LINE_SUFFIX_BYTES are not supported ",
               proc_name);
  strcat( error_string, "for multiband images.");
  oa_errno = 520;
  OaReportError( error_string);
  return(NULL);
}
if (OaCheckODLTree( input_node) != 0) {
  OdlFreeTree( input_node);
  return(NULL);  /* Error message already issued and oa_errno set.  */
}

if (OaGetFileKeywords( input_node, &label_filename, &data_filename,
                       &record_type, &record_bytes, &file_records,
                       &file_offset, &src_interchange_format) != 0) {
  return(NULL);  /* Error message already issued.  */
}

#ifdef OA_DEBUG
OaReportFileAttributes( label_filename, data_filename, record_type,
                        record_bytes, file_offset, src_interchange_format);
#endif

if (record_type == OA_VARIABLE_LENGTH) {
  sprintf( error_string, "%s does not support variable-length record files!",
           proc_name);
  oa_errno = 520;
  OaReportError( error_string);
  return(NULL);
}

/* Create the ODL tree to represent the spectrum.  Initialize keywords to
   reflect the source data, which will be from the image.  This ODL tree
   will be used as input to OalCreateSDT.  */

spectrum_node = OdlNewObjDesc( "SPECTRUM", NULL, NULL, NULL, NULL, NULL,
                               (short) 0, (long) 0);
OaStrtoKwdValue( "INTERCHANGE_FORMAT", spectrum_node, "BINARY");
OaLongtoKwdValue( "ROWS", spectrum_node, bands);
OaLongtoKwdValue( "ROW_BYTES", spectrum_node, sample_bits/8);
OaLongtoKwdValue( "COLUMNS", spectrum_node, (long) 1);

column_node = OdlNewObjDesc( "COLUMN", NULL, NULL, NULL, NULL, NULL,
                             (short) 0, (long) 0);
OdlPasteObjDesc( column_node, spectrum_node);
OaStrtoKwdValue( "NAME", column_node, "SAMPLE");
OaKwdValuetoStr( "SAMPLE_TYPE", input_node, &str);
OaStrtoKwdValue( "DATA_TYPE", column_node, str);
OaLongtoKwdValue( "START_BYTE", column_node, 1);
OaLongtoKwdValue( "BYTES", column_node, sample_bits/8);

/* Modify the tree so that unwanted data will be tossed out, and add the offset
   of the first data described by the tree to file_offset.  */

switch (band_storage_type) {

  case OA_BAND_SEQUENTIAL:

    /* Each band of the multi-spectral image is represented by a row in the
       SDT.  All the samples before the desired sample are thrown out by
       representing them as ROW_PREFIX_BYTES.  Similarily, all the samples
       after the desired sample are thrown out by representing them as
       ROW_SUFFIX_BYTES.  */

    line_prefix_bytes = ((line - 1) * line_samples + (sample - 1)) *
                        sample_bits/8;
    OaLongtoKwdValue( "ROW_PREFIX_BYTES", spectrum_node, line_prefix_bytes);
    OaLongtoKwdValue( "ROW_SUFFIX_BYTES", spectrum_node,
                      ((lines - line) * line_samples +
                       (line_samples - sample)) * sample_bits/8);
  break;

  case OA_LINE_INTERLEAVED:

    /* The SDT describes all the bands of a single line in the multiband
       image.  Each row in the SDT represents one band's line, with prefix
       and suffix bytes representing the samples before and after the desired
       sample, which are not wanted.  The offset of the line in the multiband
       image is added to file_offset, so the first OalReadStream will start
       reading there.  */

    file_offset += (line - 1) * line_samples * bands * sample_bits/8;
    line_prefix_bytes = (sample - 1) * sample_bits/8;
    OaLongtoKwdValue( "ROW_PREFIX_BYTES", spectrum_node, line_prefix_bytes);
    OaLongtoKwdValue( "ROW_SUFFIX_BYTES", spectrum_node,
                      (line_samples - sample) * sample_bits/8);
  break;

  case OA_SAMPLE_INTERLEAVED:

    /* The desired spectrum data is in one contiguous chunk within the
       multiband image, so calculate its offset and add it to file_offset,
       so the first OalReadStream will start reading there.  */

    file_offset += ((line - 1) * line_samples + (sample - 1)) * bands *
                   sample_bits/8;
  break;
}

/* Call OalCreateSDT to create the SDT (Stream Decomposition Tree).  */

if ((sdt = OalCreateSDT( spectrum_node, src_interchange_format)) == NULL) {
  sprintf( error_string, "%s: CreateSDT returned error.",
           proc_name);
  OaReportError( error_string);
  OalFreeSDT( spectrum_node);
  return(NULL);
}

/* Note: sdt and spectrum_node now point to the same node. */

/* Undo the keyword changes made above which tricked OalCreateSDT into
   creating the right kind of SDT.  */

OaDeleteKwd( "ROW_PREFIX_BYTES", spectrum_node);
OaDeleteKwd( "ROW_SUFFIX_BYTES", spectrum_node);

/* Allocate memory for the spectrum.  */

sdt_node_ptr = (SDT_node *) sdt->appl1;
object_size = sdt_node_ptr->dst.size * sdt_node_ptr->total_repetitions;
if ((data_ptr = (PTR) OaMalloc( (long) object_size)) == NULL) {
  sprintf( error_string, "%s: OaMalloc failed to allocate %ld bytes for ",
           proc_name, object_size);
  strcat( error_string, "image! Out of memory!");
  oa_errno = 720;
  OaReportError( error_string);
  exit(1);
  /*NOTREACHED*/
}

c = 0;
#ifdef IBM_PC

/* If we're on an IBM-PC, use a for loop to do the set, since memset
   may not work for setting greater than 64K and/or over segment
   boundaries;  otherwise use memset since it's usually optimized.  */

for (i=0; i<object_size; i++)
  data_ptr[i] = c;
#else
memset( data_ptr, c, (size_t) object_size);
#endif

/* Create the output oa_object and initialize it.  */

oa_object = OaNewOaObject();
oa_object->odltree = sdt;
oa_object->data_ptr = data_ptr;
oa_object->size = object_size;
oa_object->is_in_memory = TRUE;
oa_object->profile = Oa_profile;
oa_object->stream_id = NULL;

if ((stream_id = OalOpenStream( data_filename, record_type, record_bytes,
                                file_records, "r")) == NULL) {
  sprintf( error_string, "%s: OalOpenStream returned error.",
           proc_name);
  OaReportError( error_string);
  OalFreeSDT( spectrum_node);
  return(NULL);
}

/* Read the first slice of data; tell OalReadStream to read as many bytes as it
   wants, store them in its own buffer, start reading the file at the given
   byte offset, and output how many bytes it read in bytes_read.  */

if (OalReadStream( stream_id, (long) 0, &buf, file_offset, &bytes_read) != 0) {
  sprintf( error_string, "%s: OalReadStream returned error, aborting.",
           proc_name);
  OaReportError( error_string);
  OalFreeSDT( spectrum_node);
  OalCloseStream( stream_id);
  return(NULL);
}

/* Compress the SDT, and initialize current_node to point to the first
   end-node (data node) in the compressed SDT; initialize the node's dst.ptr
   with the pointer returned by OaMalloc.  */

compressed_SDT = OalCompressSDT( sdt);
current_node = OalInitializeSDT( compressed_SDT, data_ptr);

/* The main loop processes a buffer of data, then reads in the next buffer;
   the SDT maps desired buffer bytes to memory and throws aways the rest.  */

for (;;) {

  if (OalProcessSDT( buf, bytes_read, &current_node) ==
      OA_REACHED_END_OF_SDT) break;

  /*  Read next slice of data, telling OalReadStream to do the same as the
      first time, except start at the current file position.  */

  buf = NULL;
  if (OalReadStream( stream_id, (long) 0, &buf, (long) -1, &bytes_read) != 0) {
    sprintf( error_string, "%s: OalReadStream returned error, aborting.",
             proc_name);
    OaReportError( error_string);
    break;
  }
}
OalCloseStream( stream_id);

/* Free the compressed SDT. */

OalFreeSDT( compressed_SDT);

/* Call OalSDTtoODLTree to modify the new ODL tree to reflect the in-memory
   data.  This changes the DATA_TYPE keywords to reflect the in-memory data
   values which may have been converted to native types.  It also strips off
   ODL tree nodes which have SDT_node->dst.size=0.  */

OalSDTtoODLTree( sdt, dst_interchange_format);

return( oa_object);
}



/*****************************************************************************

  Routine:  OaReadSpectrumFromQube

  Description:  OaReadSpectrumFromQube opens a data file and reads in the
                SPECTRUM for the specified line/sample from the core of the
                QUBE into memory.

  Author:  Steve Monk, University of Colorado LASP

  Creation Date:  27 Nov   1995
  Last Modified:  09 Mar   1998

  History:

    Creation - This routine was part of the Version 1.0 Release of the OA
               library.
    03/09/98 - Added BIL and suffix plane support.  SM

  Input: 
         input_node - A pointer to an ODL tree node of class QUBE.
                      The tree above the input_node should have keywords
                      to specify the data file.

         line       - The line location of the spectrum.      

         sample     - The sample location of the spectrum.

         The global variable Oa_profile is set to valid values.

  Output:  If successful, the routine returns a pointer to an OA_Object
           structure, which contains an ODL tree of type SPECTRUM, and a
           pointer to the spectrum data.  If unsuccessful it returns NULL.
                It throws out all suffixes, if any are present.

  Notes: 

  1) Suffixes: in standard ISIS Qubes, each item in a suffix is allocated 4
     bytes, even though the data type stored there may be less than 4 bytes;
     in fact, some Qubes have suffixes allocated, but no actual suffix data
     in them.  This is so the user can add suffix data without rewriting the
     file.

*****************************************************************************/

#ifdef _NO_PROTO

OA_OBJECT OaReadSpectrumFromQube( input_node, line, sample)
ODLTREE input_node;
int     line;
int     sample;

#else

OA_OBJECT OaReadSpectrumFromQube( ODLTREE input_node, int line, int sample)

#endif
{

static char *proc_name = "OaReadSpectrumFromQube";
long file_offset, record_bytes, file_records;
int record_type, src_interchange_format, dst_interchange_format;
int band_storage_type;
char *label_filename, *data_filename;
OA_OBJECT oa_object;
ODLTREE current_node, sdt, spectrum_node, column_node;
ODLTREE compressed_SDT;
SDTNODE sdt_node_ptr;
long object_size, line_samples, lines, bands, core_item_bytes;
#ifdef IBM_PC
long i;
#endif
long *suffix_items, *core_items, line_suffix_bytes, bytes_read, skip_bytes;
char *buf = NULL, c, **axis_names, *core_item_type;
PTR data_ptr;
struct OaStreamStruct *stream_id;
unsigned long total_bytes_read=0, total_bytes_to_read;

dst_interchange_format = OA_BINARY_INTERCHANGE_FORMAT;

if (OaGetQubeKeywords( input_node, &core_items, &axis_names, &suffix_items,
                       &core_item_bytes, &core_item_type) != 0)
  return( NULL);

if (OaGetFileKeywords( input_node, &label_filename, &data_filename,
                       &record_type, &record_bytes, &file_records,
                       &file_offset, &src_interchange_format) != 0) {
  return(NULL);  /* Error message already issued.  */
}

#ifdef OA_DEBUG
OaReportFileAttributes( label_filename, data_filename, record_type,
                        record_bytes, file_offset, src_interchange_format);
#endif

/* The storage orders this routine can currently handle are band-sequential,
   indicated by AXIS_NAME = (SAMPLE, LINE, BAND), and band-interleaved-by-
   line, indicated by AXIS_NAME = (SAMPLE, BAND, LINE).  */

if ((strcmp( axis_names[0], "SAMPLE") == 0) &&
    (strcmp( axis_names[1], "LINE") == 0) &&
    (strcmp( axis_names[2], "BAND") == 0)) {
  band_storage_type = OA_BAND_SEQUENTIAL;
  bands = core_items[2];
  lines = core_items[1];
  line_samples = core_items[0];
}
if ((strcmp( axis_names[0], "SAMPLE") == 0) &&
    (strcmp( axis_names[1], "BAND") == 0) &&
    (strcmp( axis_names[2], "LINE") == 0)) {
  band_storage_type = OA_LINE_INTERLEAVED;
  bands = core_items[1];
  lines = core_items[2];
  line_samples = core_items[0];
}

line_suffix_bytes = suffix_items[0] * 4;

if ((band_storage_type != OA_BAND_SEQUENTIAL) &&
    (band_storage_type != OA_LINE_INTERLEAVED)) {
  sprintf( error_string, "%s: Cannot handle this storage order: %s %s %s",
           proc_name, axis_names[0], axis_names[1], axis_names[2]);
  oa_errno = 520;
  OaReportError( error_string);
  return( NULL);
}

/* Check that inputs 'line' and 'sample' are within the range of the QUBE.  */

if ((line < 1) || (line > lines) || (sample < 1) || (sample > line_samples)) {
  sprintf( error_string, "%s: input line or sample is out-of-range",
           proc_name);
  oa_errno = 502;
  OaReportError( error_string);
  return( NULL);
}


/* The technique used to read in a spectrum from the qube is trick OalCreateSDT
   into thinking it's creating an SDT for a table.  To get to the desired
   pixel in the first band, we set file_offset to be the start of the qube
   plus all the interveaning core, suffixes, corners etc.  Then we use
   ROW_SUFFIX_BYTES to represent all the data between a pixel in band x and
   same pixel in band x+1, so that all this data will be thrown out;  this
   includes suffixes in various dimensions, corners, and all the core data
   belonging to other pixel locations.  After we read in the pixel from the
   last band, the SDT will still specify reading ROW_SUFFIX_BYTES before
   it's been fully traversed, and this may cause an attemp to read past the
   end of the file; OalReadStream will give an error which we'll catch by
   keeping track of whether we've read the entire spectrum yet
   (total_bytes_read >= total_bytes_to_read).  */

if (band_storage_type == OA_BAND_SEQUENTIAL) {

  /* Position initial file offset past all the lines preceeding the line of
     the desired pixel, and past all pixels before the desired pixel in
     the pixel's line.  */

  file_offset += (line_samples * core_item_bytes + line_suffix_bytes) *
                 (line - 1) + (sample-1) * core_item_bytes;

  /* Skip pixels after the desired pixel in this line, and skip line suffix
     bytes.  */

  skip_bytes = (line_samples - sample) * core_item_bytes + line_suffix_bytes;

  /* Skip all lines and their line suffix bytes which occur after the desired
     pixel's line, up to the end of this band.  */

  skip_bytes += (line_samples * core_item_bytes + line_suffix_bytes) *
                (lines - line);

  /* Skip any bottom plane bytes and their suffix bytes which occur after
     the end of the band and before the start of the next band.  */

  skip_bytes += (suffix_items[1] * 4) * line_samples;    /* bottom plane */
  skip_bytes += (suffix_items[0] * 4) * suffix_items[1]; /* corner */

  /* Skip all lines and their suffix bytes in the next band which occur
     before the desired pixel's line.  */

  skip_bytes += (line_samples * core_item_bytes + line_suffix_bytes) *
                (line-1);

  /* Skip all pixels before the desired pixel in this line.  */

  skip_bytes += (sample-1) * core_item_bytes;

  total_bytes_to_read = (core_item_bytes + skip_bytes) * lines * bands -
                        skip_bytes;
}


if (band_storage_type == OA_LINE_INTERLEAVED) {

  /* Position past all image lines before the line of the desired pixel.  */

  file_offset += (line_samples * core_item_bytes + line_suffix_bytes) *
                 bands * (line-1);

  /* Position past any back planes and their suffix bytes (corner) which
     occur before the first band of the desired pixel's line.  */

  file_offset += (suffix_items[1] * 4) * line_samples * (line-1); /*backplane*/
  file_offset += (suffix_items[0] * 4) * suffix_items[1] * (line-1); /*corner*/

  /* Position past pixels preceeding the desired pixel in the first band's
     line.  */

  file_offset += core_item_bytes * (sample-1);

  /* Skip pixels after the desired pixel in this line, and skip line suffix
     bytes.  */

  skip_bytes = (line_samples - sample) * core_item_bytes + line_suffix_bytes;

  /* Skip all pixels before the desired pixel in the next band occurrence
     of this line.  */

  skip_bytes += (sample-1) * core_item_bytes;

  total_bytes_to_read = (skip_bytes + core_item_bytes) * bands -
                         skip_bytes;
}


spectrum_node = OdlNewObjDesc( "SPECTRUM", NULL, NULL, NULL, NULL, NULL,
                               (short) 0, (long) 0);
OaStrtoKwdValue( "INTERCHANGE_FORMAT", spectrum_node, "BINARY");
OaLongtoKwdValue( "ROWS", spectrum_node, bands);
OaLongtoKwdValue( "ROW_BYTES", spectrum_node, core_item_bytes);
OaLongtoKwdValue( "COLUMNS", spectrum_node, (long) 1);
OaLongtoKwdValue( "LINE_SUFFIX_BYTES", spectrum_node, skip_bytes);

column_node = OdlNewObjDesc( "COLUMN", NULL, NULL, NULL, NULL, NULL,
                             (short) 0, (long) 0);
OdlPasteObjDesc( column_node, spectrum_node);
OaStrtoKwdValue( "NAME", column_node, "SAMPLE");
OaStrtoKwdValue( "DATA_TYPE", column_node, core_item_type);
OaLongtoKwdValue( "START_BYTE", column_node, (long) 1);
OaLongtoKwdValue( "BYTES", column_node, core_item_bytes);

/* Call OalCreateSDT to create the SDT (Stream Decomposition Tree).  */

if ((sdt = OalCreateSDT( spectrum_node, src_interchange_format)) == NULL) {
  sprintf( error_string, "%s: CreateSDT returned error.",
           proc_name);
  OaReportError( error_string);
  OalFreeSDT( spectrum_node);
  return(NULL);
}

/* Note: sdt and spectrum_node now point to the same node. */

/* Allocate memory for the spectrum.  */

sdt_node_ptr = (SDT_node *) sdt->appl1;
object_size = sdt_node_ptr->dst.size * sdt_node_ptr->total_repetitions;
if ((data_ptr = (PTR) OaMalloc( (long) object_size)) == NULL) {
  sprintf( error_string, "%s: OaMalloc failed to allocate %ld bytes for ",
           proc_name, object_size);
  strcat( error_string, "image! Out of memory!");
  oa_errno = 720;
  OaReportError( error_string);
  exit(1);
  /*NOTREACHED*/
}
c = 0;

#ifdef IBM_PC

/* If we're on an IBM-PC, use a for loop to do the set, since memset
   may not work for setting greater than 64K and/or over segment
   boundaries;  otherwise use memset since it's usually optimized.  */

for (i=0; i<object_size; i++)
  data_ptr[i] = c;
#else
memset( data_ptr, c, (size_t) object_size);
#endif

/* Create the output oa_object and initialize it.  */

oa_object = OaNewOaObject();
oa_object->odltree = sdt;
oa_object->data_ptr = data_ptr;
oa_object->size = object_size;
oa_object->is_in_memory = TRUE;
oa_object->profile = Oa_profile;
oa_object->stream_id = NULL;

if ((stream_id = OalOpenStream( data_filename, record_type, record_bytes,
                                file_records, "r")) == NULL) {
  sprintf( error_string, "%s: OalOpenStream returned error.",
           proc_name);
  OaReportError( error_string);
  OalFreeSDT( spectrum_node);
  return(NULL);
}

/* Read the first slice of data; tell OalReadStream to read as many bytes as it
   wants, store them in its own buffer, start reading the file at the given
   byte offset, and output how many bytes it read in bytes_read.  */

if (OalReadStream( stream_id, (long) 0, &buf, file_offset, &bytes_read) != 0) {
  sprintf( error_string, "%s: OalReadStream returned error, aborting.",
           proc_name);
  OaReportError( error_string);
  OalFreeSDT( spectrum_node);
  OalCloseStream( stream_id);
  return(NULL);
}
total_bytes_read += bytes_read;

/* Compress the SDT, and initialize current_node to point to the first
   end-node (data node) in the compressed SDT; initialize the node's dst.ptr
   with the pointer returned by OaMalloc.  */

compressed_SDT = OalCompressSDT( sdt);
current_node = OalInitializeSDT( compressed_SDT, data_ptr);

/* The main loop processes a buffer of data, then reads in the next buffer;
   the SDT maps desired buffer bytes to memory and throws aways the rest.  */

for (;;) {

  if (OalProcessSDT( (PTR) buf, bytes_read, &current_node) ==
      OA_REACHED_END_OF_SDT) break;

  /*  Read next slice of data, telling OalReadStream to do the same as the
      first time, except start at the current file position.  */

  buf = NULL;
  if (OalReadStream( stream_id, (long) 0, &buf, (long) -1, &bytes_read) != 0) {
    if (total_bytes_read < total_bytes_to_read) {
      sprintf( error_string, "%s: OalReadStream returned error, aborting.",
               proc_name);
      OaReportError( error_string);
    }
    break;
  }
  total_bytes_read += bytes_read;
}
OalCloseStream( stream_id);

/* Free the compressed SDT. */

OalFreeSDT( compressed_SDT);

/* Call OalSDTtoODLTree to modify the new ODL tree to reflect the in-memory
   data.  This changes the DATA_TYPE keywords to reflect the in-memory data
   values which may have been converted to native types.  It also strips off
   ODL tree nodes which have SDT_node->dst.size=0.  */

OalSDTtoODLTree( sdt, dst_interchange_format);

return( oa_object);
}



/*****************************************************************************

  Routine:  OaParseLabelFile

  Description:  This routine returns a pointer to an ODL tree that represents
                a parsed ODL label, given the name of a file that contains
                an ODL label in ASCII format.  This routine will optionally
                expand ^STRUCTURE pointers.  It is functionally identical to
                L3's OdlParseLabelFile, except that it also works with an
                attached label in a variable-length record file, e.g. the
                Voyager images on CD-ROM.  It uses the OA Stream Layer to
                read records from variable-length record files.
                Starting with OAL Version 1.3, when given a file containing
                one of the external file formats supported by OAL, this
                routine will call external file format specific code to
                create a label for the file on-the-fly.   This is currently
                done only for GIF files.

  Author:  Steve Monk, University of Colorado LASP

  Creation Date:  26 Oct  1994
  Last Modified:  16 Mar  1998

  History:

    Creation - This routine was part of the Beta Release of the OA library.
    12/11/95 - For variable-length record files, perform ^STRUCTURE expansion
               AFTER the file_name fields have been corrected.  This is
               necessary for the code which looks for ^STRUCTURE files.  SM
    03/16/98 - Added call OalCreateODLTreeFromGif when OalOpenStream detects
               a GIF file.  SM

  Input: 
          filespec     - A character string that represents a file
                         specification, assumed to be valid for the host
                         system.  If not, an error message is issued.

          errfilespec  - Identifies the file into which parser errors will
                         be written.  If the file previously exists, then the
                         error messages are appended to the existing file.
                         If NULL is specified, then the error messages are
                         written to stdout.

          expand       - expand is a mask (typedef unsigned short MASK) which
                         specifies whether or not the specified ODL files are
                         to be expanded.  See lablib3.h.

                         ODL_EXPAND_STRUCTURES - expand ^STRUCTURE and
                         ^*_STRUCTURE keywords only.

                         ODL_EXPAND_CATALOG - expand ^CATALOG keywords only.

                         ODL_EXPAND_STRUCTURES | ODL_EXPAND_CATALOG - both
                         of the above.
   
          nomsgs       - A flag indicating whether or not parser error
                         messages are to be written.  A value of TRUE or (1)
                         indicates that parser error messages will not be
                         written even if an error message file has been
                         specified.
        
  Output:  If successful, an ODL tree is returned.

  Notes: 

*****************************************************************************/

#ifdef _NO_PROTO

ODLTREE OaParseLabelFile( filespec, errfilespec, expand, nomsgs)
char *filespec;
char *errfilespec;
MASK expand;
unsigned short nomsgs;

#else

ODLTREE OaParseLabelFile( char *filespec, char *errfilespec, MASK expand,
                          unsigned short nomsgs)

#endif
{

static char *proc_name = "OaParseLabelFile";
struct OaStreamStruct *stream_id;
ODLTREE odltree, current_node;
KEYWORD *kwdptr;
char *buf, *label_string;
int reached_end_of_label = FALSE;
long bytes_read, label_string_bytes;

/* Open the label file, specifying OA_UNKNOWN_RECORD_TYPE for the record_type.
   OalOpenStream will decide if it's a variable-length record file, a stream
   text file, or one of the external file formats supported by OAL, such as
   GIF.   */

if ((stream_id = OalOpenStream( filespec, OA_UNKNOWN_RECORD_TYPE,
                                (long) 0, (long) 0, "r")) == NULL) {
  sprintf( error_string, "%s: OalOpenStream returned error.",
           proc_name);
  OaReportError( error_string);
  return( NULL);
}

if (stream_id->record_type == OA_GIF) {
  odltree = (ODLTREE) OalCreateODLTreeFromGif( stream_id->fp,
                                               stream_id->filename);
  OalCloseStream( stream_id);
  return( odltree);
}

if (stream_id->record_type != OA_VARIABLE_LENGTH) {

  /* Since the file doesn't have variable-length records (i.e. with explicit,
     2-byte LSB integer record counts), OdlParseLabelFile can read it.  */

  OalCloseStream( stream_id);
  return( OdlParseLabelFile( filespec, errfilespec, expand, nomsgs));

} else {

  /* File has non-transparent variable-length records, so use the OAL Stream
     Layer to read records from the file, and build up a long string
     containing the entire label for L3 to parse.  */

  if ((label_string = (char *) OaMalloc( (size_t) 1)) == NULL) {
    sprintf( error_string, "%s: OaMalloc failed! Out of memory!", proc_name);
    oa_errno = 720;
    OaReportError( error_string);
    exit(1);
    /*NOTREACHED*/
  }
  *label_string = ' ';
  label_string_bytes = 1;
  while (reached_end_of_label == FALSE) {
    buf = NULL;
    if (OalReadStream( stream_id, (long) 0, &buf, (long) -1, &bytes_read)
                       != 0) {
      sprintf( error_string, "%s: OalReadStream returned error, aborting.",
               proc_name);
      OaReportError( error_string);
      OalCloseStream( stream_id);
      return(NULL);
    }
    label_string = (char *) OaRealloc( label_string, (long) label_string_bytes,
                                       (long) label_string_bytes + bytes_read
                                        + 1);
    if (label_string == NULL) {
      sprintf( error_string, "%s: OaRealloc failed! Out of memory!",
               proc_name);
      sprintf( error_string + strlen( error_string),
               "\nlabel_string_bytes = %ld, bytes_read = %ld.",
               label_string_bytes, bytes_read);
      oa_errno = 720;
      OaReportError( error_string);
      exit(1);
      /*NOTREACHED*/
    }
    memcpy( label_string + label_string_bytes, buf, bytes_read);
    label_string_bytes += bytes_read;
    label_string[ label_string_bytes] = '\n';
    label_string_bytes++;

    buf[ bytes_read] = '\0';

    /* Detect the end of the label by finding a 3-byte record containing
       "END".  */

    if ((bytes_read == 3) && (strcmp( buf, "END") == 0)) {
      reached_end_of_label = TRUE;
      label_string[ label_string_bytes-1] = '\0';
    }
  }   /* end while reached_end_of_label == FALSE  */

  /* Close the file.  */

  OalCloseStream( stream_id);

  /* Call L3 to parse the label string, specifying NO ^STRUCTURE or
     ^CATALOG expansion.  */

  odltree = OdlParseLabelString( label_string, errfilespec, 0, nomsgs);
  if (odltree == NULL) {
     sprintf( error_string, "%s: OdlParseLabelString returned NULL!", proc_name);
     OaReportError( error_string);
  }
  LemmeGo( label_string);

  /* OdlParseLabelString no longer writes the string to a temporary file
     and OdlParseLabelFile to read the file like it used to.  Rather it calls
     OdlParseFile which now parses the string directly, and leaves the
     file_name field in all the ODL tree nodes and keywords set to NULL;
     these need to be changed to the name of the original label file.  */
 
  if (odltree != NULL) {
    current_node = odltree;

    /* Do a pre-order traverse of the entire tree, and at each node, replace
       the file_name field by the original filespec.  Do the same for the
       file_name field in all the node's keywords.  */

    while (current_node != NULL) {
      if (current_node->file_name == NULL) {
        CopyString( current_node->file_name, filespec);
      }

      kwdptr = OdlGetFirstKwd( current_node);
      while (kwdptr != NULL) {
        if (kwdptr->file_name == NULL) {
          CopyString( kwdptr->file_name, filespec);
	}
        kwdptr = OdlGetNextKwd( kwdptr);
      }

      /* Position current_node to the next node;  if current_node has children,
         then the leftmost child is the new current_node;  otherwise, if
         current_node has a right sibling, the the right sibling is the new
         current_node;  otherwise search upwards in the tree until get to a
         node which does have a right sibling.  */

      if (LeftmostChild( current_node) != NULL)
        current_node = LeftmostChild( current_node);
      else {
        while (current_node != NULL) {
          if (RightSibling( current_node) != NULL) {
            current_node = RightSibling( current_node);
            break;
          }
          current_node = Parent( current_node);
        }
      }
    }  /* end while current_node != NULL  */
  } else {    /* end if odltree != NULL          */
    oa_errno = 1000;
    return( NULL);
  }

  /* Now expand the label.  */

  odltree = OdlExpandLabelFile( odltree, errfilespec, expand, nomsgs);
  return( odltree);

}     /* end else a variable-length record file   */
}



/*****************************************************************************

  Routine:  OaReadObject

  Description:  OaReadObject opens a data file and reads an object into
                memory.  It determines the class of the object and calls
                the appropriate class-specific OA read function.

  Author:  Steve Monk, University of Colorado LASP

  Creation Date:   1 Sept  1994
  Last Modified:   1 Sept  1994

  History:

    Creation - This routine was part of the Alpha Release of the OA library.

  Input: 
         object_node - A pointer to an ODL tree node which is a top-level
                       object, i.e. TABLE, IMAGE e