2.1 Overview of an NDF
 2.2 Overview of the NDF_ Routines
 2.3 Error Handling
 2.4 Overview of a Typical Application

This section presents an overview of what NDF data structures are, and the facilities which the NDF_ system provides for manipulating them.

2.1 Overview of an NDF

The simplest way of regarding an NDF is to view it as a collection of those items which might typically be required in an astronomical image or spectrum. The main part is an N-dimensional array of data (where N is 1 for a spectrum, 2 for an image, etc.), but this may also be accompanied by a number of other items which are conveniently categorised as follows:

Character components: TITLENDF title
LABELData label
UNITS Data units
Array components: DATAData pixel values
VARIANCEPixel variance estimates
QUALITYPixel quality values
Miscellaneous components: AXISCoordinate axes
WCSWorld coordinate systems
HISTORYProcessing history
Extensions: EXTENSIONProvides extensibility

The names of these components are significant, since they are used by the NDF access routines to identify the component(s) to which certain operations should be applied.1 The following describes the purpose and interpretation of each component in slightly more detail.

Character components:

– This is a character string, whose value is intended for general use as a heading for such things as graphical output; e.g. ‘M51 in good seeing’.
– This is a character string, whose value is intended to be used on the axis of graphs to describe the quantity in the NDF’s data component; e.g. ‘Surface brightness’.
– This is a character string, whose value describes the physical units of the quantity stored in the NDF’s data component; e.g. ‘J/(m 2Angs)’.

Array components:

– This is an N-dimensional array of pixel values representing the spectrum, image, etc. stored in the NDF. This is the only NDF component which must always be present. All the others are optional.
– This is an array of the same shape and size as the data array, and represents the measurement errors or uncertainties associated with the individual data values. If present, these are always stored as variance estimates for each pixel.
– This is an array of the same shape and size as the data array, and holds a set of unsigned byte values. These are used to assign additional “quality” attributes to each pixel (for instance, whether it is part of the active area of a detector). Quality values may be used to influence the way in which the NDF’s data and variance components are processed, both by general-purpose software and by specialised applications.

Miscellaneous components:

– This component name represents a group of axis components which may be used to describe the shape and position of the NDF’s pixels in a rectangular coordinate system. The physical units and a label for each axis of this coordinate system may also be stored. (Note that the ability to associate extensions with an NDF’s axis coordinate system, although described in SGP/38, is not yet available via the NDF access routines described here.)
– This component may be used to hold information about any “world coordinate systems” associated with the NDF. These may include celestial coordinate systems, such as right ascension and declination (in various flavours), but may also represent other coordinates, including wavelength.2 Multiple coordinate systems may be present.

The WCS component is a rather more complex entity than most other NDF components and a full description is currently beyond the scope of this document. It stores world coordinate information in a format defined by the AST library (see SUN/210) and known as a “FrameSet”. You should consult SUN/210 for a full description of the facilities which a FrameSet provides. The NDF_ library simply provides routines for reading and writing this information (see NDF_GTWCS and NDF_PTWCS).

– This component may be used to keep a record of the processing history which the NDF undergoes. If present, this component should be updated by any applications which modify the data structure.


are user-defined HDS structures associated with the NDF, and are used to give the data format flexibility by allowing it to be extended. Their formulation is not covered by the NDF definition, but a few simple routines are provided for accessing and manipulating named extensions, and for reading and writing the values of components stored within them.

2.2 Overview of the NDF_ Routines

The NDF access routines described in this document all have names of the form:

NDF_ <name >

where <name > identifies the operation which the routine performs. These routines provide facilities for performing the following types of operation on NDF data structures:

A full description of each routine can be found in Appendix D of this document.

2.3 Error Handling

The NDF_ routines adhere throughout to the standard error-handling strategy described in SUN/104. Most of the routines therefore carry an integer inherited status argument called STATUS and will return without action unless this is set to the value SAI__OK3 when they are invoked. When necessary, error reports are made through the ERR_ routines in the manner described in SUN/104. Where exceptions to this general behaviour exist, they are noted in the appropriate subroutine descriptions in Appendix D.

2.4 Overview of a Typical Application

The following contains an example of a simple application which uses the NDF_ routines to add the data arrays of two NDF data structures to produce a new NDF. This is not quite the simplest “add” application which could be written, but is close to it. Nevertheless, it will do a good job, and will respond correctly to unforseen circumstances or conditions which it is not designed to handle by issuing sensible error messages.

The intention here is simply to give a flavour of how the NDF_ routines are used, so don’t worry if you don’t understand all the details. The example is followed by some brief programming notes which include references to other relevant sections of this document which can be consulted if necessary. If you are interested, a more sophisticated “add” application with extra commentary can also be found in §A.7.

        SUBROUTINE ADD( STATUS )                                 [1]
        INCLUDE ’SAE_PAR’                                        [2]
        INTEGER STATUS, EL, NDF1, NDF2, NDF3, PNTR1( 1 ), PNTR2( 1 ), PNTR3( 1 )
  *  Check inherited global status and begin an NDF context.
        IF ( STATUS .NE. SAI__OK ) RETURN                        [3]
        CALL NDF_BEGIN                                           [4]
  *  Obtain identifiers for the two input NDFs and trim their pixel-index
  *  bounds to match.
        CALL NDF_ASSOC( ’IN1’, ’READ’, NDF1, STATUS )            [5]
        CALL NDF_MBND( ’TRIM’, NDF1, NDF2, STATUS )              [6]
  *  Create a new output NDF based on the first input NDF.
        CALL NDF_PROP( NDF1, ’Axis,Quality’, ’OUT’, NDF3, STATUS )  [7]
  *  Map the input and output data arrays.
        CALL NDF_MAP( NDF1, ’Data’, ’_REAL’, ’READ’, PNTR1, EL, STATUS )  [8]
        CALL NDF_MAP( NDF2, ’Data’, ’_REAL’, ’READ’, PNTR2, EL, STATUS )
        CALL NDF_MAP( NDF3, ’Data’, ’_REAL’, ’WRITE’, PNTR3, EL, STATUS )
  *  Check that the input arrays do not contain bad pixels.
        CALL NDF_MBAD( .FALSE., NDF1, NDF2, ’Data’, .TRUE., BAD, STATUS )  [9]
  *  Add the data arrays.
        CALL ADDIT( EL, %VAL( PNTR1( 1 ) ), %VAL( PNTR2( 1 ) ),  [10]
       :            %VAL( PNTR3( 1 ) ), STATUS )
  *  End the NDF context.
        CALL NDF_END( STATUS )                                   [12]
  *  Subroutine to perform the addition.
        SUBROUTINE ADDIT( EL, A, B, C, STATUS )                  [11]
        REAL A( EL ), B( EL ), C( EL )
        DO 1 I = 1, EL
           C( I ) = A( I ) + B( I )
   1    CONTINUE

Programming notes:

Note that the application is actually a subroutine, called ADD, with a single integer argument called STATUS. This is the ADAM method of writing applications (see SUN/101).
The INCLUDE statement is used to define standard “symbolic constants”, such as the value SAI__OK which is used in this routine. Such constants should always be defined in this way rather than by using actual numerical values. The file SAE_PAR is almost always needed, and should be included as standard in every application.
The value of the STATUS argument is checked. This is because the application uses the error handling strategy described in SUN/104, which requires that a subroutine should do nothing unless its STATUS argument is set to the value SAI__OK on entry. Here, we simply return without action if STATUS has the wrong value.
An NDF context is now opened, by calling NDF_BEGIN. This call matches the corresponding NDF_END call at the end of the ADD routine. When the NDF_END call is reached, the NDF_ system will “clean up” by closing down everything which has been used since the matching call to NDF_BEGIN (see §3.4). Since we want to clean up everything in the application at this point, the initial call to NDF_BEGIN is put right at the start.
The two input NDFs which we want to add are now obtained using the parameters ‘IN1’ and ‘IN2’. Lots of things happen behind the scenes at this point, possibly involving prompting the user to supply the names of the data structures to be added, and a pair of integer values NDF1 and NDF2 are returned. These values are NDF identifiers and are used to refer to the NDFs throughout the rest of the application (see §3.2).
The first thing we do with these identifiers is to pass them to NDF_MBND. This routine ensures that the two NDFs are the same shape and size, which is what we require. The details of how this is done are explained much later (in §17.3). For now, just accept that it works.
An output NDF is created next by calling NDF_PROP which uses the parameter ‘OUT’ to get the new data structure (probably prompting the user for its name) and returns another identifier for it in NDF3. NDF_PROP bases the new NDF on the first input NDF (see §14.4). This ensures that it’s the right size, etc., and also that it contains any ancillary information which can legitimately be copied from the input.
The data arrays in the input and output NDFs are then accessed by calling NDF_MAP. Rather than returning actual data values, this routine returns pointers for the data values in PNTR1, PNTR2 and PNTR3 (see §8.2). Note that we want to ‘READ’ the input arrays and ‘WRITE’ to the output array.
Since this is a very simple application, it cannot handle the special bad-pixel values which may be present in some NDF data structures. The call to NDF_MBAD at this point checks that there are none present (see §17.2). If there are, then an appropriate error message will result and the application will abort.
The subroutine ADDIT which performs the work is now called. The pointer values returned by NDF_MAP are turned into actual Fortran arrays at this point, which ADDIT can access. This is done by using the %VAL function in the call to ADDIT (see §8.2).
ADDIT itself is a very simple subroutine. Since all the arrays it will be passed are the same size (we have ensured this), there is no need to worry about their dimensions. They are all handled as if they were 1-dimensional, and simply added. The application could easily be altered to perform a different function by changing this routine.
Finally, NDF_END is called. As already explained, this shuts everything down, ensuring that all NDF data files are closed, etc. before the application finishes.

1Note that the name “DATA” used by the NDF_ routines to refer to an NDF’s data component differs from the actual name of the HDS object in which it is stored, which is “DATA_ARRAY”.

2In this respect, the WCS component provides a superset of the facilities provided by the AXIS component. However, the AXIS component is retained because it has been used historically by a significant number of astronomical applications. The NDF_ library maintains consistency between these two components (to the extent that their nature allows). Thus, for example, the rectangular coordinate system defined by the AXIS component is also accessible through the WCS component.

3The symbolic constant SAI__OK is defined in the include file SAE_PAR.