20 Creating Your Own Private Mappings (IntraMaps)

 20.1 The Need for Extensibility
 20.2 The IntraMap Model
 20.3 Limitations of IntraMaps
 20.4 Writing a Transformation Routine
 20.5 Registering a Transformation Routine
 20.6 Creating an IntraMap
 20.7 Restricted Implementations of Transformation Routines
 20.8 Variable Numbers of Coordinates
 20.9 Adapting a Transformation Routine to Individual IntraMaps
 20.10 Simplifying IntraMaps
 20.11 Writing and Reading IntraMaps
 20.12 Managing Transformation Routines in Libraries

20.1 The Need for Extensibility

However many Mapping classes are provided by AST, sooner or later you will want to transform coordinates in some way that has not been foreseen. You might want to plot a graph in some novel curvilinear coordinate system (perhaps you already have a WCS system in your software and just want to use AST for its graphical capabilities). Alternatively, you might need to calibrate a complex dataset (like an objective prism plate) where each position must be converted to world coordinates with reference to calibration data under the control of an elaborate algorithm.

In such cases, it is clear that the basic pre-formed components provided by AST for building Mappings are just not enough. What you need is access to a programming language. However, if you write your own software to transform coordinate values, then it must be made available in the form of an AST class (from which you can create Objects) before it can be used in conjunction with other AST facilities.

At this point you might consider writing your own AST class, but this is not recommended. Not only would the internal conventions used by AST take some time to master, but you might also find yourself having to change your software whenever a new version of AST was released. Fortunately, there is a much easier route provided by the IntraMap class.

20.2 The IntraMap Model

To allow you to write your own Mappings, AST provides a special kind of Mapping called an IntraMap. An IntraMap is a sort of “wrapper” for a coordinate transformation routine written in Fortran. You write this routine yourself and then register it with AST. This, in effect, creates a new class from which you can create Mappings (i.e. IntraMaps) which will transform coordinates in whatever way your transformation routine specifies.

Because IntraMaps are Mappings, they may be used in the same way as any other Mapping. For instance, they may be combined in series or parallel with other Mappings using a CmpMap6), they may be inverted (§5.6), you may enquire about their attributes (§4.5), they may be inserted into FrameSets (§13), etc. They do, however, have some important limitations of which you should be aware before we go on to consider how to create them.

20.3 Limitations of IntraMaps

By now, you might be wondering why any other kind of Mapping is required at all. After all, why not simply write your own coordinate transformation routines in Fortran, wrap them up in IntraMaps and do away with all the other Mapping classes in AST?

The reason is not too hard to find. Any transformation routine you write is created solely by you, so it is a private extension which does not form a permanent part of AST. If you use it to calibrate some data and then pass that data to someone else, who has only the standard version of AST, then they will not be able to interpret it.

Thus, while an IntraMap is fine for use by you and your collaborators (who we assume have access to the same transformation routines), it does not address the need for universal data exchange like other AST Mappings do. This is where the “Intra” in the class name “IntraMap” comes from, implying private or internal usage.

For this reason, it is unwise to store IntraMaps in datasets, unless they will be used solely for communication between collaborating items of software which share conventions about their use. A private database describing coordinate systems on a graphics device might be an example where IntraMaps would be suitable, because the data would probably never be accessed by anyone else’s software. Restricting IntraMap usage to within a single program (i.e. never writing it out) is, of course, completely safe.

If, by accident, an IntraMap should happen to escape as part of a dataset, then the unsuspecting recipient is likely to receive an error message when they attempt to read the data. However, AST will associate details of the IntraMap’s transformation routine and its author (if provided) with the data, so that the recipient can make an intelligent enquiry to obtain the necessary software if this proves essential.

20.4 Writing a Transformation Routine

The first stage in creating an IntraMap is to write the coordinate transformation routine. This should have a calling interface like the AST_TRANN function provided by AST (q.v.). Here is a simple example of a suitable transformation routine which transforms coordinates by squaring them:

        SUBROUTINE SQRTRAN( THIS, NPOINT, NCOORD_IN, INDIM, IN, FORWARD,
      :                     NCOORD_OUT, OUTDIM, OUT, STATUS )
        INTEGER THIS, NPOINT, NCOORD_IN, INDIM, NCOORD_OUT, OUTDIM, STATUS
        DOUBLE PRECISION IN( INDIM, NCOORD_IN ), OUT( OUTDIM, NCOORD_OUT )
        LOGICAL FORWARD
  
        INCLUDE ’AST_PAR’
        DOUBLE PRECISION X
        INTEGER COORD, POINT
  
  *  Forward transformation.
        IF ( FORWARD ) THEN
           DO 2 POINT = 1, NPOINT
              DO 1 COORD = 1, NCOORD_IN
                 X = IN( POINT, COORD )
                 IF ( X .EQ. AST__BAD ) THEN
                    OUT( POINT, COORD ) = AST__BAD
                 ELSE
                    OUT( POINT, COORD ) = X * X
                 ENDIF
   1          CONTINUE
   2       CONTINUE
  
  *  Inverse transformation.
        ELSE
           DO 4 POINT = 1, NPOINT
              DO 3 COORD = 1, NCOORD_IN
                 X = IN( POINT, COORD )
                 IF ( X .LT. 0.0D0 .OR. X .EQ. AST__BAD ) THEN
                    OUT( POINT, COORD ) = AST__BAD
                 ELSE
                    OUT( POINT, COORD ) = SQRT( X )
                 ENDIF
   3          CONTINUE
   4       CONTINUE
        ENDIF
        END

As you can see, the routine comes in two halves which implement the forward and inverse coordinate transformations. The number of points to be transformed (NPOINT) and the numbers of input and output coordinates per point (NCOORD_IN and NCOORD_OUT—in this case both are assumed equal) are passed to the routine. A pair of loops then accesses all the coordinate values. Note that it is legitimate to omit one or other of the forward/inverse transformations and simply not to implement it, if it will not be required. It is also permissible to require that the numbers of input and output coordinates be fixed (e.g. at 2), or to write the routine so that it can handle arbitrary dimensionality, as here.

Before using an incoming coordinate, the routine must first check that it is not set to the value AST__BAD, which indicates missing data (§5.9). If it is, the same value is also assigned to any affected output coordinates. The value AST__BAD is also generated if any coordinates cannot be transformed. In this example, this can happen with the inverse transformation if negative values are encountered, so that the square root cannot be taken.

There are very few restrictions on what a coordinate transformation routine may do. For example, it may freely perform I/O to access any external data needed, it may invoke other AST facilities (but beware of unwanted recursion), etc. Typically, you may also want to pass information to it via global variables held in common blocks. Remember, however, that whatever facilities the transformation routine requires must be available in every program which uses it.

Generally, it is not a good idea to retain context information within a transformation routine. That is, it should transform each set of coordinates as a single point and retain no memory of the points it has transformed before. This is in order to conform with the AST model of a Mapping.

If an error occurs within a transformation routine, it should set its STATUS argument to an error value before returning. This will alert AST to the error, causing it to abort the current operation. The error value AST__ITFER is available for this purpose, but other values may also be used (e.g. if you wish to distinguish different types of error). The AST__ITFER error value is defined in the AST_ERR include file.

20.5 Registering a Transformation Routine

Having written your coordinate transformation routine, the next step is to register it with AST. Registration is performed using AST_INTRAREG, as follows:

        EXTERNAL SQRTRAN
  
        CHARACTER * ( 80 ) AUTHOR, CONTACT, PURPOSE
  
        ...
  
        PURPOSE = ’Square each coordinate value’
        AUTHOR  = ’R.F. Warren-Smith & D.S. Berry’
        CONTACT = ’http://www.starlink.ac.uk/cgi-bin/htxserver/’ //
                  ’sun210.htx/?xref_SqrTran’
  
        CALL AST_INTRAREG( ’SqrTran’, 2, 2, SQRTRAN, 0,
       :                   PURPOSE, AUTHOR, CONTACT, STATUS )

Note that the transformation routine must also appear in a Fortran EXTERNAL statement.

The first argument to AST_INTRAREG is a name by which the transformation routine will be known. This will be used when we come to create an IntraMap and is case sensitive. We recommend that you base this on the actual routine name and make this sufficiently unusual that it is unlikely to clash with any other routines in most people’s software.

The next two arguments specify the number of input and output coordinates which the transformation routine will handle. These correspond with the Nin and Nout attributes of the IntraMap we will create. Here, we have set them both to 2, which means that we will only be able to create IntraMaps with 2 input and 2 output coordinates (despite the fact that the transformation routine can actually handle other dimensionalities). We will see later (§20.8) how to remove this restriction.

The fourth argument should contain a set of flags which describe the transformation routine in a little more detail. We will return to this shortly (§20.7 & §20.10). For now, we supply a value of zero.

The remaining arguments are character strings which document the transformation routine, mainly for the benefit of anyone who is unfortunate enough to encounter a reference to it in their data which they cannot interpret. As explained above (§20.3), you should try and avoid this, but accidents will happen, so you should always provide strings containing the following:

(1)
A short description of what the transformation routine is for.
(2)
The name of the author.
(3)
Contact details, such as an e-mail or WWW address.

The idea is that anyone finding an IntraMap in their data, but lacking the necessary transformation routine, should be able to contact the author and make a sensible enquiry in order to obtain it. If you expect many enquiries, you may like to set up a World Wide Web page and use that instead (in the example above, we use the WWW address of the relevant part of this document).

20.6 Creating an IntraMap

Once a transformation routine been registered, creating an IntraMap from it is simple:

        INTEGER INTRAMAP
  
        ...
  
        INTRAMAP = AST_INTRAMAP( ’SqrTran’, 2, 2, ’ ’, STATUS );

We simply use the AST_INTRAMAP constructor function and pass it the name of the transformation routine to use. This name is the same (case sensitive) one that we associated with the routine when we registered it using AST_INTRAREG20.5).

You can, of course, register any number of transformation routines and select which one to use whenever you create an IntraMap. You can also create any number of independent IntraMaps using each transformation routine. In this sense, each transformation routine you register effectively creates a new “sub-class” of IntraMap, from which you can create Objects just like any other class. However, an error will occur if you attempt to use a transformation routine that has not yet been registered.

The second and third arguments to AST_INTRAMAP are the numbers of input and output coordinates. These define the Nin and Nout attributes for the IntraMap that is created and they must match the corresponding numbers given when the transformation routine was registered.

The penultimate argument is the usual attribute initialisation string. You may set attribute values for an IntraMap in exactly the same way as for any other Mapping4.6, and also see §20.9).

20.7 Restricted Implementations of Transformation Routines

You may not always want to use both the forward and inverse transformations when you create an IntraMap, so it is possible to omit either from the underlying coordinate transformation routine. Consider the following, for example:

        SUBROUTINE POLY3TRAN( THIS, NPOINT, NCOORD_IN, INDIM, IN, FORWARD,
       :                      NCOORD_OUT, OUTDIM, OUT, STATUS )
        INTEGER THIS, NPOINT, NCOORD_IN, INDIM, NCOORD_OUT, OUTDIM, STATUS
        DOUBLE PRECISION IN( INDIM, NCOORD_IN ), OUT( OUTDIM, NCOORD_OUT )
        LOGICAL FORWARD
  
        INCLUDE ’AST_PAR’
        DOUBLE PRECISION X
        INTEGER POINT
  
  *  Forward transformation.
        DO 1 POINT = 1, NPOINT
           X = IN( POINT, 1 )
           IF ( X .EQ. AST__BAD ) THEN
              OUT( POINT, 1 ) = AST__BAD
           ELSE
              OUT( POINT, 1 ) =
       :      6.18D0 + X * ( 0.12D0 + X * ( -0.003D0 + X * 0.0000101D0 ) )
           END IF
   1    CONTINUE
        END

This implements a 1-dimensional cubic polynomial transformation. Since this is somewhat awkward to invert, however, we have only implemented the forward transformation. When registering the routine, this is indicated via the FLAGS argument to AST_INTRAREG, as follows:

        EXTERNAL POLY3TRAN
  
        ...
  
        CALL AST_INTRAREG( ’Poly3Tran’, 1, 1, POLY3TRAN, AST__NOINV,
       :                   PURPOSE, AUTHOR, CONTACT, STATUS )

Here, the fifth argument has been set to the flag value AST__NOINV to indicate the lack of an inverse. If the forward transformation were absent, we would use AST__NOFOR instead. Flag values for this argument may be combined by summing them if necessary.

20.8 Variable Numbers of Coordinates

In our earlier examples, we have used a fixed number of input and output coordinates when registering a coordinate transformation routine. It is not necessary to impose this restriction, however, if the transformation routine can cope with a variable number of coordinates (as with the example in §20.4). We indicate the acceptability of a variable number when registering the transformation routine by supplying the value AST__ANY for the number of input and/or output coordinates, as follows:

        CALL AST_INTRAREG( ’SqrTran’, AST__ANY, AST__ANY, SQRTRAN, 0,
       :                   PURPOSE, AUTHOR, CONTACT, STATUS )

The result is that an IntraMap may now be created with any number of input and output coordinates. For example:

        INTEGER INTRAMAP1, INTRAMAP2
  
        ...
  
        INTRAMAP1 = AST_INTRAMAP( ’SqrTran’, 1, 1, ’ ’, STATUS )
        INTRAMAP2 = AST_INTRAMAP( ’SqrTran’, 3, 3, ’Invert=1’, STATUS )

It is possible to fix either the number of input or output coordinates (by supplying an explicit number to AST_INTRAREG), but more subtle restrictions on the number of coordinates, such as requiring that Nin and Nout be equal, are not supported. This means that:

        INTRAMAP = AST_INTRAMAP( ’SqrTran’, 1, 2, ’ ’, STATUS )

will be accepted without error, although the transformation routine cannot actually handle such a combination sensibly. If this is important, it would be worth adding a check within the transformation routine itself, so that the error would be detected when it came to be used.

20.9 Adapting a Transformation Routine to Individual IntraMaps

In the examples given so far, our coordinate transformation routines have not made use of the THIS pointer passed to them (which identifies the IntraMap whose transformation we are implementing). In practice, this will often be the case. However, the presence of the THIS pointer allows the transformation routine to invoke any other AST routine on the IntraMap, and this permits enquiries about its attributes. The transformation routine’s behaviour can therefore be modified according to any attribute values which are set. This turns out to be a useful thing to do, so each IntraMap has a special IntraFlag attribute reserved for exactly this purpose.

Consider, for instance, the case where the transformation routine has access to several alternative sets of internally-stored data which it may apply to perform its transformation. Rather than implement many different versions of the transformation routine, you may switch between them by setting a value for the IntraFlag attribute when you create an instance of an IntraMap, for example:

        INTRAMAP1 = AST_INTRAMAP( ’MyTran’, 2, 2, ’IntraFlag=A’, STATUS )
        INTRAMAP2 = AST_INTRAMAP( ’MyTran’, 2, 2, ’IntraFlag=B’, STATUS )

The transformation routine may then enquire the value of the IntraFlag attribute (e.g. using AST_GETC and passing it the THIS pointer) and use whichever dataset is required for that particular IntraMap.

This approach is particularly useful when the number of possible transformations is unbounded or not known in advance, in which case the IntraFlag attribute may be used to hold numerical values encoded as part of a character string (effectively using them as data for the IntraMap). It is also superior to the use of a global switch for communication (e.g. setting an index to select the “current” data before using the IntraMap), because it continues to work when several IntraMaps are embedded within a more complex compound Mapping, when you may have no control over the order in which they are used.

20.10 Simplifying IntraMaps

A notable disadvantage of IntraMaps is that they are “black boxes” as far as AST is concerned. This means that they have limited ability to participate in the simplification of compound Mappings performed, e.g., by AST_SIMPLIFY6.7), because AST cannot know how they interact with other Mappings. In reality, of course, they will often implement such specialised coordinate transformations that the simplification possibilities will be rather limited anyway.

One important simplification, however, is the ability of a Mapping to cancel with its own inverse to yield a unit Mapping (a UnitMap). This is important because Mappings are frequently used to relate a dataset to some external standard (a celestial coordinate system, for example). When inter-relating two similar datasets calibrated using the same standard, part of the Mapping often cancels, because it is applied first in one direction and then the other, effectively eliminating the reference to the standard. This is often a useful simplification and can lead to greater efficiency.

Many transformations have this property of cancelling with their own inverse, but not necessarily all. Consider the following transformation routine, for example:

        SUBROUTINE MAXTRAN( THIS, NPOINT, NCOORD_IN, INDIM, IN, FORWARD,
       :                    NCOORD_OUT, OUTDIM, OUT, STATUS )
        INTEGER THIS, NPOINT, NCOORD_IN, INDIM, NCOORD_OUT, OUTDIM, STATUS
        DOUBLE PRECISION IN( INDIM, NCOORD_IN ), OUT( OUTDIM, NCOORD_OUT )
        LOGICAL FORWARD
  
        INCLUDE ’AST_PAR’
        DOUBLE PRECISION HI, X
        INTEGER COORD, POINT
  
  *  Forward transformation.
        IF ( FORWARD ) THEN
           DO 2 POINT = 1, NPOINT
              HI = AST__BAD
              DO 1 COORD = 1, NCOORD_IN
                 X = IN( POINT, COORD )
                 IF ( X .NE. AST__BAD ) THEN
                    IF ( X .GT. HI .OR. HI .EQ. AST__BAD ) HI = X
                 END IF
   1          CONTINUE
   2       CONTINUE
  
  *  Inverse transformation.
        ELSE
           DO 4 COORD = 1, NCOORD_OUT
              DO 3 POINT = 1, NPOINT
                 OUT( POINT, COORD ) = IN( POINT, 1 )
   3          CONTINUE
   4       CONTINUE
        END IF
        END

This routine takes any number of input coordinates and returns a single output coordinate which is the maximum value of the input coordinates. Its inverse (actually a “pseudo-inverse”) sets all the input coordinates to the value of the output coordinate.36

If this routine is applied in the forward direction and then in the inverse direction, it does not in general restore the original coordinate values. However, if applied in the inverse direction and then the forward direction, it does. Hence, replacing the sequence of operations with an equivalent UnitMap is possible in the latter case, but not in the former.

To distinguish these possibilities, two flag values are provided for use with AST_INTRAREG to indicate what simplification (if any) is possible. For example, to register the above transformation routine, we might use:

        EXTERNAL MAXTRAN
  
        ...
  
        CALL AST_INTRAREG( ’MaxTran’, AST__ANY, 1, MAXTRAN, AST__SIMPIF,
       :                   PURPOSE, AUTHOR, CONTACT, STATUS )

Here, the flag value AST__SIMPIF supplied for the fifth argument indicates that simplification is possible if the transformation is applied in the inverse direction followed by the forward direction. To indicate the complementary case, the flag AST__SIMPFI would be used instead. If both simplifications are possible (as with the SQRTRAN function in §20.4), then we would use the sum of both values.

In practice, some judgement is usually necessary when deciding whether to allow simplification. For example, seen in one light our SQRTRAN routine (§20.4) does not cancel with its own inverse, because squaring a coordinate value and then taking its square root can change the original value, if this was negative. Therefore, replacing this combination with a UnitMap will change the behaviour of a compound Mapping and should not be allowed. Seen in another light, however, where the coordinates being processed are intrinsically all positive, it is a permissible and probably useful simplification.

If such distinctions are ever important in practice, it is simple to register the same transformation routine twice with different flag values (use a separate name for each) and then use whichever is appropriate when creating an IntraMap.

20.11 Writing and Reading IntraMaps

It is most important to realise that when you write an IntraMap to a Channel15.3), the transformation routine which it uses is not stored with it. To do so is impossible, because the routine has been compiled and loaded into memory ready for execution before AST gets to see it. However, AST does store the name associated with the transformation routine and various details about the IntraMap itself.

This means that any program attempting to read the IntraMap (§15.4) cannot make use of it unless it also has independent access to the original transformation routine. If it does not have access to this routine, an error will occur at the point where the IntraMap is read and the associated error message will direct the user to the author of the transformation routine for more information.

However, if the necessary transformation routine is available, and has been registered before the read operation takes place, then AST is able to re-create the original IntraMap and will do so. Registration of the transformation routine must, of course, use the same name (and, in fact, be identical in most particulars) as was used in the original program which wrote the data.

This means that a set of co-operating programs which all have access to the same set of transformation routines and register them in identical fashion (see §20.12 for how this can best be achieved) can freely exchange data that contain IntraMaps. The need to avoid exporting such data to unsuspecting third parties (§20.3) must, however, be re-iterated.

20.12 Managing Transformation Routines in Libraries

If you are developing a large suite of data reduction software, you may have a need to use IntraMaps at various points within it. Very probably this will occur in unrelated modules which are compiled separately and then stored in a library. Since the transformation routines required must be registered before they can be used, this makes it difficult to decide where to perform this registration, especially since any particular data reduction program may use an arbitrary subset of the modules in your library.

To assist with this problem, AST allows you to perform the same registration of a transformation routine any number of times, so long as it is performed using an identical invocation of AST_INTRAREG on each occasion (i.e. all of its arguments must be identical). This means you do not have to keep track of whether a particular routine has already been registered but could, in fact, register it on each occasion immediately before it is required (wherever that may be). In order that all registrations are identical, however, it is recommended that you group them all together into a single routine, perhaps as follows:

        SUBROUTINE MYTRANS( STATUS )
        INTEGER STATUS
  
        INCLUDE ’AST_PAR’
        EXTERNAL MAXTRAN, POLY3TRAN, SQRTRAN
  
        ...
  
        CALL AST_INTRAREG( ’MaxTran’, AST__ANY, 1, MAXTRAN, AST__SIMPIF,
       :                   PURPOSE, AUTHOR, CONTACT, STATUS )
  
        ...
  
        CALL AST_INTRAREG( ’Poly3Tran’, 1, 1, POLY3TRAN, AST__NOINV,
       :                   PURPOSE, AUTHOR, CONTACT, STATUS )
  
        ...
  
        CALL AST_INTRAREG( ’SqrTran, 2, 2, SQRTRAN, 0,
       :                   PURPOSE, AUTHOR, CONTACT, STATUS )
        END

You can then simply invoke this routine wherever necessary. It is, in fact, particularly important to register all relevant transformation routines in this way before you attempt to read an Object that might be (or contain) an IntraMap20.11). This is because you may not know in advance which of these transformation routines the IntraMap will use, so they must all be available in order to avoid an error.

36Remember that IN holds the original “output” coordinates when applying the inverse transformation and OUT holds the original “input” coordinates.