4 Additional Features

 4.1 Transforming General Coordinate Data
 4.2 Handling of Bad Coordinate Values
 4.3 Concatenating Transformations
 4.4 Prefixing and Appending Transformations
 4.5 Inverting Transformations
 4.6 Formatting Transformation Functions

4.1 Transforming General Coordinate Data

As well as providing routines for applying compiled mappings to 1- and 2-dimensional data (Sections 3.6 & 3.7), the TRANSFORM facility also has a set of routines for applying more general mappings. These are appropriate, for instance, when the number of input or output data coordinates is greater than 2 (or when these numbers are unequal) or when the number of coordinates is not known in advance. In such cases, all the input and output coordinates must each reside in single data arrays.

These general routines have names of the form TRN_TRNx, where x is either I, R or D according to whether the data are specified by integer, real or double precision values respectively. Thus, to transform a general set of data points specified by an array of real coordinates, the routine TRN_TRNR would be used, thus:

  CALL TRN_TRNR( BAD, ND1, NCIN, NDAT, DATA, ID, NR1, NCOUT, RESULT, STATUS )

where:

Example 6. Mapping 3-dimensional coordinates into 2 dimensions.

In the following, a set of data points representing a 3-dimensional object is converted into a related set of 2-dimensional points using TRN_TRNR. The resulting points are then plotted using GKS. With a suitable choice of mapping, such an arrangement might be used to generate perspective pictures of 3-dimensional objects:

  *  Define dimensions of the DATA and RESULT arrays.
        PARAMETER ( MAXPTS = 5000, MAXDIM = 3 )
  
  *  Declare variables and arrays.
        INTEGER IPOINT, STATUS, ID
        REAL X, Y, Z, DATA( MAXPTS, MAXDIM ), RESULT( MAXPTS, MAXDIM )
  
  *  Generate 3-d coordinates of helix.
        DO 1 IPOINT = 1, 3601
           THETA = REAL( IPOINT - 1 ) * 2.0 * 3.14159 / 360.0
           X = COS( THETA )                              [1]
           Y = SIN( THETA )
           Z = REAL( IPOINT - 1 ) / 360.0
  
  *  Enter coordinates into the DATA array.
           DATA( IPOINT, 1 ) = X                         [2]
           DATA( IPOINT, 2 ) = Y
           DATA( IPOINT, 3 ) = Z
      1 CONTINUE
  
  *  Transform the data points.
        CALL TRN_TRNR( .FALSE., MAXPTS, 3, 3601, DATA, ID, MAXPTS, 2,
       :               RESULT, STATUS )                  [3]
  
  *  Plot the resulting 2-dimensional data points.
        CALL GPL( 3601, RESULT( 1, 1 ), RESULT( 1, 2 ) ) [4]

Programming notes:

(1)
The coordinates of a set of 3-dimensional data points are generated.
(2)
The coordinates are stored so that DATA( I, J ) contains the J’th coordinate of data point I. These coordinates need not fill the entire array.
(3)
TRN_TRNR is called to apply the compiled mapping. The size of the input and output coordinate arrays are specified, together with the numbers of coordinates associated with each input and output data point. An error will result if the compiled mapping does not have the appropriate numbers of input and output variables.
(4)
The 2-dimensional output data points are plotted. Their coordinates are stored in the RESULT array in a similar manner to those in the DATA array. As before, these coordinates need not entirely fill the RESULT array.

4.2 Handling of Bad Coordinate Values

During the process of transforming coordinate data, numerical errors (such as division by zero or overflow) will inevitably occur from time to time. There is no need to include specific protection against this when a transformation is formulated, however, because such errors will automatically be trapped and converted into one of the standard Starlink bad values.4

This form of error trapping is performed by all the routines which apply compiled mappings to coordinate data. The resulting “bad coordinates” will, where necessary, be propagated through each stage in the evaluation of a compiled mapping, so that all the results which have been affected by numerical errors can later be identified. No error report is currently made (or STATUS value set) if a numerical error of this nature occurs.

All the routines which apply compiled mappings to coordinate data can also recognise bad coordinate values supplied as input, and will propagate these if required. Frequently, however, there will not be any bad coordinates in the input data stream and it may be possible to save appreciable amounts of processing time by disabling recognition of these values (thereby eliminating unnecessary checking), especially when large arrays are being processed. Each relevant routine therefore carries a logical argument called BAD, which specifies whether recognition of bad input data is required (execution will generally be faster if BAD is .FALSE.). Note that this argument only affects recognition of bad data in the input stream, while numerical errors which occur during the computation are always converted into bad values and correctly propagated to the output.

4.3 Concatenating Transformations

It is common to find that the relationship between two coordinate systems is most easily expressed in terms of several transformations applied in succession. For instance, the transformation between positions on the sky and the pixel coordinates of a CCD image might be divided into two stages; the first representing the effect of imaging the sky into the focal plane of the telescope, and the second taking account of the position, size and orientation of the detector in the focal plane. The TRANSFORM facility makes explicit provision for cases such as this by allowing transformations to be concatenated.


x1 x2
Forward
PICT
Inverse
y1 X1 y2 X2 y3 X3
Forward
PICT
Inverse
Y1
PICT
x1 x2
Forward
PICT
Inverse
Y1

Figure 1: Concatenating two transformations.


A rather general case of concatenation is illustrated in Figure 1. In this example, Transformation 1 relates two input variables (x1, x2) to three “intermediate” variables (y1, y2, y3), which are also the input variables (X1, X2, X3) of Transformation 2. This transformation, in turn, has a single final output variable (Y1). The concatenation process involves eliminating the three intermediate variables and storing the two transformation definitions together in a single new transformation. This new transformation then has two input variables (x1, x2) and a single output variable (Y1). Using the ‘.’ symbol to represent concatenation, this entire process may be summarised as:

[2 3].[3 1] = [2 1]

or, in general:

[i j].[j k] = [i k]

Note that the number of output variables from the first transformation must be equal to the number of input variables to the second transformation. For obvious reasons, also, it is not permitted to concatenate a transformation in which only the forward mapping is defined with another in which only the inverse mapping is defined (e.g. [i j] could not be concatenated with [j k]).

The result of concatenating two transformations is itself a transformation, so the process may be repeated indefinitely, making it possible for a whole sequence of transformations to be joined together and processed as a single unit. Once transformations have been combined in this way, however, they cannot later be separated.

4.3.1 Performing concatenation

Concatenation of a pair of transformations is performed by the routine TRN_JOIN (concatenate transformations), as follows:

  CALL TRN_JOIN( LOCTR1, LOCTR2, ELOC, NAME, LOCNEW, STATUS )

where:

As with the TRN_NEW routine (Section 3.3), a temporary transformation may also be produced by leaving the ELOC argument (and optionally the NAME argument) blank when TRN_JOIN is invoked.

4.4 Prefixing and Appending Transformations

In addition to producing new transformations by concatenating existing ones, it is also possible to modify an existing transformation by prefixing or appending another one to it. For example, to prefix one transformation to another, TRN_PRFX (prefix transformation) would be used, thus:

  CALL TRN_PRFX( LOCTR1, LOCTR2, STATUS )

In this case, the two transformations with locators LOCTR1 and LOCTR2 are concatenated (as above), but the resultant transformation replaces the second one, so that the first transformation is, in effect, prefixed to it. The first transformation itself is not altered.

The routine TRN_APND (append transformation) behaves in a similar way, except that it appends the second transformation to the first. In this case the second transformation remains unchanged.

Example 7. Adjusting an astrometric calibration.

Suppose LOCTRS is a locator to a transformation which relates the pixel coordinates (x, y) of a CCD image to positions on the sky (α, δ). If the size of this image is altered during data reduction, then its pixel coordinates will change and the astrometric calibration will need adjustment to take account of this.

If the x and y pixel coordinates are reduced by (say) 19 and 25 units, then a transformation inter-relating the old and new pixel coordinates could be formulated as follows:

Forward  xold =xnew + 19 yold =ynew + 25 Inverse  xnew = xold 19 ynew = yold 25 (4)

and then created. This transformation (with locator LOCTRA, say) represents the necessary adjustment which should be applied as a prefix to the original transformation, thus:

  CALL TRN_PRFX( LOCTRA, LOCTRS, STATUS )

The modified transformation (LOCTRS) will then correctly relate the image’s new pixel coordinates to positions on the sky, the necessary adjustment having been achieved through the following concatenation process:

[(xnew, ynew) (xold, yold)].[(xold, yold) (α, δ)] = [(xnew, ynew) (α, δ)]

Note that it is not necessary to know anything about the nature of the original transformation [(xold, yold) (α, δ)] in order to apply such a correction.


4.5 Inverting Transformations

Inversion of a transformation is a straightforward process involving the inter-change of the forward and inverse mappings (the numbers of input and output variables will also be inter-changed). It is performed by TRN_INV (invert transformation), thus:

  CALL TRN_INV( LOCTR, STATUS )

where LOCTR is a locator to the transformation to be inverted. After such a call, compiling the transformation in the forward direction will yield the same compiled mapping as would have been obtained by compiling in the inverse direction prior to calling TRN_INV.

The main use for TRN_INV is in adapting transformations which have been created or supplied the “wrong way round” for some purpose (e.g. concatenation with another transformation).

4.6 Formatting Transformation Functions

To simplify the process of formatting transformation functions, a set of routines is provided which allows numerical or textual values to be inserted into “template” character strings. These routines largely eliminate the need to use Fortran WRITE statements to construct transformation functions containing numerical values.

The routines have names of the form TRN_STOK[x], where x is either I, R or D according to whether the type of value to be inserted is integer, real or double precision respectively. The routine TRN_STOK (i.e. with x omitted altogether) is used for making textual (rather than numerical) insertions but otherwise functions in the same way. These routines rely on the concept of a token,5 which is placed in a character string at the point where an insertion is to be made and which is subsequently replaced by the value to be inserted. For example, if the character variable TEXT had the value:

  ’magnitude = - 2.5 * log10( count ) + zero_point’

then TRN_STOKR (substitute a real token value) could be used to substitute a numerical value for the ‘zero_point’ token, as follows:

  CALL TRN_STOKR( ’zero_point’, 17.7, TEXT, NSUBS, STATUS )

This causes all occurrences of the ‘zero_point’ token to be replaced with the formatted real number ‘17.7’, while NSUBS returns the number of substitutions made (in this case there will only be one). The value of TEXT then becomes:

  ’magnitude = - 2.5 * log10( count ) + 17.7’

Note that the TRN_STOKx routines will respect the syntax of transformation functions, so that negative numerical values will be enclosed in parentheses before a substitution is made. For instance, if the call to TRN_STOKR had been:

  CALL TRN_STOKR( ’zero_point’, -17.7, TEXT, NSUBS, STATUS )

then the ‘zero_point’ token would be replaced by ‘(-17.7)’ to prevent the illegal expression ‘…+ -17.7’ from being produced. The need for two extra characters to accommodate these parentheses must be remembered when declaring the size of character strings.

Example 8. A “packaged” transformation.

This illustrates the use of TRN_STOKR in a subroutine to “package up” the creation of a simple [1 1] transformation which contains several adjustable numerical parameters. The same principles can be followed to write routines for creating a variety of specialised transformations.

        SUBROUTINE LINEAR( SCALE, ZERO, ELOC, NAME, LOCTR, STATUS )
  
  *  Declare variables.
        INCLUDE ’SAE_PAR’
        INTEGER STATUS, NSUBS
        REAL SCALE, ZERO, INVSCL
        CHARACTER * ( * ) ELOC, NAME, LOCTR
        CHARACTER FOR( 1 ) * 80, INV( 1 ) * 80
  
  *  Check STATUS.
        IF ( STATUS .NE. SAI__OK ) RETURN
  
  *  Formulate the forward transformation function.
        FOR( 1 ) = ’out = ( in - zero ) * scale’                 [1]
        CALL TRN_STOKR( ’zero’, ZERO, FOR( 1 ), NSUBS, STATUS )
        CALL TRN_STOKR( ’scale’, SCALE, FOR( 1 ), NSUBS, STATUS )
  
  *  If possible, formulate the inverse transformation function.
        IF( SCALE .NE. 0.0 ) THEN
           INVSCL = 1.0 / SCALE
           INV( 1 ) = ’in = ( out * inv_scale ) + zero’          [2]
           CALL TRN_STOKR( ’zero’, ZERO, INV( 1 ), NSUBS, STATUS )
           CALL TRN_STOKR( ’inv_scale’, INVSCL, INV( 1 ), NSUBS, STATUS )
  
  *  Inverse is undefined...
        ELSE
           INV( 1 ) = ’in’                                       [3]
        ENDIF
  
  *  Create the transformation.
        CALL TRN_NEW( 1, 1, FOR, INV, ’_REAL:’,                  [4]
       :              ’Shift and linear scaling: in --> out’,
       :              ELOC, NAME, LOCTR, STATUS )
  
        END

Programming notes:

(1)
The forward transformation function is assigned and the numerical parameters SCALE and ZERO are substituted into it.
(2)
If possible, the inverse transformation function is defined similarly. Note that the reciprocal of SCALE is taken so that multiplication may be used in preference to the less efficient division operation.
(3)
If SCALE is zero, then the inverse mapping cannot be defined so the inverse transformation function is assigned a value to indicate this.
(4)
The transformation is created.

Token delimiters.  Tokens should normally be delimited from surrounding text (such as variable names) by non-alphanumeric characters, otherwise they will not be recognised. The syntax of transformation functions ensures that this will normally be so, but, when this is not possible, enclosing angle brackets ‘ < >’ may also be used as token delimiters. In this case the brackets will be replaced as if they were part of the token itself. By this means, values may be substituted to form part of a variable name if required.
Example 9. Substituting values into variable names.

This simple example formulates a set of transformation functions to multiply each ordinate of a 3-dimensional coordinate system (X1, X2, X3) by 2.0 to yield the coordinates (Y1, Y2, Y3):

         INTEGER I, NSUBS, STATUS
         CHARACTER FOR( 3 ) * 30
  
         DO 1 I = 1, 3
            FOR( I ) = ’Y<I> = X<I> * 2.0’
            CALL TRN_STOKI( ’I’, I, FOR( I ), NSUBS, STATUS )
       1 CONTINUE

Recursive substitution.  The routine TRN_STOK, which allows text to be substituted in place of a token, admits some more sophisticated possibilities, including the insertion of text which itself contains tokens (perhaps including further instances of the original token). Although the substitution performed by a single call to TRN_STOK will not be affected by the presence of tokens within the substituted text (i.e. the substitution is not recursive), the routine may nevertheless be invoked repeatedly to perform recursive substitution if required. This capability can be used to construct more complex expressions which have a suitable recursively defined form.
Example 10. Constructing a polynomial expression.

The following constructs an expression representing a polynomial in X with an arbitrary number of numerical coefficients:

  *  Declare variables.
        INTEGER NCOEFF, STATUS, I, NSUBS
        REAL COEFF( NCOEFF )
        CHARACTER * ( * ) EXPRS
  
  *  Initialise the expression.
        EXPRS = ’<next_term><coeff>’                     [1]
  
  *  Substitute each coefficient value.
        DO 1 I = 1, NCOEFF
           CALL TRN_STOKR( ’coeff’, COEFF( I ), EXPRS,   [2]
       :                   NSUBS, STATUS )
  
  *  Expand the token representing the next term.
           IF( I .NE. NCOEFF ) THEN
              CALL TRN_STOK( ’next_term’,                [3]
       :                     ’(<next_term><coeff>)*X+’,
       :                     EXPRS, NSUBS, STATUS )
  
  *  Eliminate the final ’<next_term>’ token.
           ELSE
              CALL TRN_STOK( ’next_term’, ’ ’,           [4]
       :                     EXPRS, NSUBS, STATUS )
           ENDIF
     1  CONTINUE
  

Programming notes:

(1)
The character variable EXPRS, which is to contain the expression, is initialised to the value ‘<next_term><coeff>’.
(2)
The ‘<coeff>’ token is substituted with a coefficient value (say 0.1) to give ‘<next_term>0.1’.
(3)
The ‘<next_term>’ token is expanded to give ‘(<next_term><coeff>)*X+0.1’. The process is then repeated to replace the ‘<next_term><coeff>’ part of this expression using the value of the next coefficient.
(4)
After the last coefficient value has been substituted the remaining ‘<next_term>’ token is eliminated by replacing it with a blank string.

The effect of this algorithm with (say) the 5 polynomial coefficients 0.1, 0.12, 1.06, 4.4 & 1E7, would be to produce the following expression:

  ’((((1E-7)*X+(-4.4))*X+1.06)*X+0.12)*X+0.1’

which casts the polynomial into a form for efficient evaluation using Horner’s method.


4The Starlink bad values are symbolic constants with names of the form VAL__BADx, where x is either D, R, I, W, UW, B or UB according to the data type in question. These constants are defined in the include file PRM_PAR (see SGP/38 & SUN/39 for further information).

5Tokens take the same form as the variable names used in transformation functions – i.e. they may contain only alpha-numeric characters (including underscore) and must begin with an alphabetic character. Tokens may be of any length and may use mixed case (token substitution is not case-sensitive) but embedded blanks are not allowed.