### 14 Higher Level Operations on FrameSets

#### 14.1 Creating FrameSets with astConvert

Before considering the important subject of using FrameSets to convert between coordinate systems (§14.2), let us return briefly to reconsider the output generated by astConvert. We used this function earlier (§12), when converting between the coordinate systems represented by various kinds of Frame, and indicated that it returns a FrameSet to represent the coordinate conversion it identifies. We are now in a position to examine the structure of this FrameSet.

Take our earlier example (§12.1) of converting between the celestial coordinate systems represented by two SkyFrames:

#include "star/ast.h"
AstFrameSet *cvt;
AstSkyFrame *skyframe1, *skyframe2;

...

skyframe1 = astSkyFrame( "System=FK4-NO-E, Epoch=B1958, Equinox=B1960" );
skyframe2 = astSkyFrame( "System=Ecliptic, Equinox=J2010.5" );

cvt = astConvert( skyframe1, skyframe2, "" );

This will produce a pointer, “cvt”, to the FrameSet shown in Figure 15.

As can be seen, this FrameSet contains just two Frames. The source Frame supplied to astConvert becomes its base Frame, while the destination Frame becomes its current Frame. (The FrameSet, of course, simply holds pointers to these Frames, rather than making copies.) The Mapping which relates the base Frame to the current Frame is the one which implements the required conversion.

As we noted earlier (§12.1), the FrameSet returned by astConvert may be used both as a Mapping and as a Frame to perform most of the functions you are likely to need. However, the Mapping may be extracted for use on its own if necessary, using astGetMapping13.7), for example:

AstMapping *mapping;

...

mapping = astGetMapping( cvt, AST__BASE, AST__CURRENT );

#### 14.2 Converting between FrameSet Coordinate Systems

We now consider the process of converting between the coordinate systems represented by two FrameSets. This is a most important operation, as a subsequent example (§14.3) will show, and is illustrated in Figure 16.

Recalling (§13.8) that a FrameSet will behave like its current Frame when necessary, conversion between two FrameSets is performed using astConvert12.1), but supplying pointers to FrameSets instead of Frames. The effect of this is to convert between the coordinate systems represented by the current Frames of each FrameSet:

AstFrameSet *frameseta, *framesetb;

...

cvt = astConvert( frameseta, framesetb, "SKY" );

When using FrameSets, we are presented with considerably more conversion options than when using Frames alone. This is because each current Frame is related to all the other Frames in its respective FrameSet. Therefore, if we can establish a link between any pair of Frames, one from each FrameSet, we can form a complete conversion path between the two current Frames (Figure 16).

This expanded range of options is, of course, precisely the intention. By connecting Frames together within a FrameSet, we have extended the range of coordinate systems that can be reached from any one of them. We are therefore no longer restricted to converting between Frames with the same Domain value (§7.12), but can go via a range of intermediate coordinate systems in order to make the connection we require. Transformation between different domains has therefore become possible because, in assembling the FrameSets, we provided the additional information needed to inter-relate them.

It is important to appreciate, however, that the choice of “missing link” is crucial in determining the conversion that results. Although each FrameSet may be perfectly self-consistent internally, this does not mean that all conversion paths through the combined network of Mappings are equivalent. Quite the contrary in fact: everything depends on where the inter-connecting link between the two FrameSets is made. In practice, there may be a large number of possible pairings of Frames and hence of possible links. Other factors must therefore be used to restrict the choice. These are:

(1)
Not every possible pairing of Frames is legitimate. For example, you cannot convert directly between a basic Frame and a SkyFrame which belong to different classes, so such pairings will be ignored.
(2)
In a similar way, you cannot convert directly between Frames with different Domain values (§7.12). If the Domain attribute is used consistently (typically only one Frame in each FrameSet will have a particular Domain value), then this further restricts the choice.
(3)
The third argument of astConvert may then be used to specify explicitly which Domain value the paired Frames should have. You may also supply a comma-separated list of preferences here (see below).
(4)
If the above steps fail to uniquely identify the link, then the first suitable pairing of Frames is used, so that any ambiguity is resolved by the order in which Frames are considered for pairing (see the description of the astConvert function in Appendix B for details of the search order).25

In the example above we supplied the string “SKY” as the third argument of astConvert. This constitutes a request that a pair of Frames with the Domain value SKY (i.e. representing celestial coordinate systems) should be used to inter-relate the two FrameSets. Note that this does not specify which celestial coordinate system to use, but is a general request that the two FrameSets be inter-related using coordinates on the celestial sphere.

Of course, it may be that this request cannot be met because there may not be a celestial coordinate system in both FrameSets. If this is likely to happen, we can supply a list of preferences, or a domain search path, as the third argument to astConvert, such as the following:

cvt = astConvert( frameseta, framesetb, "SKY,PIXEL,GRID," );

Now, if the two FrameSets cannot be inter-related using the SKY domain, astConvert will attempt to use the PIXEL domain instead. If this also fails, it will try the GRID domain. A blank field in the domain search path (here indicated by the final comma) allows any Domain value to be used. This can be employed as a last resort when all else has failed.

If astConvert succeeds in identifying a conversion, it will return a pointer to a FrameSet (§14.1) in which the source and destination Frames are inter-connected by the required Mapping. In this case, of course, these Frames will be the current Frames of the two FrameSets, but in all other respects the returned FrameSet is the same as when converting between Frames.

Very importantly, however, astConvert may modify the FrameSets you are converting between. It does this, in order to indicate which pairing of Frames was used to inter-relate them, by changing the Base attribute for each FrameSet so that the Frame used in the pairing becomes its base Frame (§13.4).

Finally, note that astConvert may also be used to convert between a FrameSet and a Frame, or vice versa. If a pointer to a Frame is supplied for either the first or second argument, it will behave like a FrameSet containing only a single Frame.

#### 14.3 Example—Registering Two Images

Consider two images which have been calibrated by attaching FrameSets to them, such that the base Frame of each FrameSet corresponds to the raw data grid coordinates of each image (the GRID domain of §7.13). Suppose, also, that these FrameSets contain an unknown number of other Frames, representing alternative world coordinate systems. What we wish to do is register these two images, such that we can transform from a position in the data grid of one into the corresponding position in the data grid of the other. This is a very practical example because images will typically be calibrated using FrameSets in precisely this way.

The first step will probably involve making a copy of both FrameSets (using astCopy—§4.13), since we will be modifying them. Let “frameseta” and “framesetb” be pointers to these copies. Since we want to convert between the base Frames of these FrameSets (i.e. their data grid coordinates), the next step is to make these Frames current. This is simply done by inverting both FrameSets, which interchanges their base and current Frames. astInvert will perform this task:

astInvert( frameseta );
astInvert( framesetb );

To identify the required conversion, we now use astConvert, supplying a suitable domain search path with which we would like our two images to be registered:

cvt = astConvert( frameseta, framesetb, "SKY,PIXEL,GRID" );
if ( cvt == AST__NULL ) {
<no conversion was possible>
} else {
<conversion was possible>
}

The effects of this are:

(1)
astConvert first attempts to register the two images on the celestial sphere (i.e. using the SKY domain). To do this, it searches for a celestial coordinate system, although not necessarily the same one, attached to each image. If it finds a suitable pair of coordinate systems, it then registers the images by matching corresponding positions on the sky.
(2)
If this fails, astConvert next tries to match positions in the PIXEL domain (§7.12). If it succeeds, the two images will then be registered so that their corresponding pixel positions correspond. If the PIXEL domain is offset from the data grid (as typically happens in data reduction systems which implement a “pixel origin”), then this will be correctly accounted for.
(3)
If this also fails, the GRID domain is finally used. This will result in image registration by matching corresponding points in the data grids used by both images. This means they will be aligned so that the first element their data arrays correspond.
(4)
If all of the above fail, astConvert will return the value AST__NULL. Otherwise a pointer to a FrameSet will be returned.

The resulting “cvt” FrameSet may then be used directly (§12.1) to convert between positions in the data grid of the first image and corresponding positions in the data grid of the second image.

To determine which domain was used to achieve registration, we can use the fact that the Base attribute of each FrameSet is set by astConvert to indicate which intermediate Frames were used. We can therefore simply invert either FrameSet (to make its base Frame become the current one) and then enquire the Domain value:

const char *domain;

...

astInvert( frameseta );
domain = astGetC( frameseta, "Domain" );

If conversion was successful, the result will be one of the strings “SKY”, “PIXEL” or “GRID”.

#### 14.4 Re-Defining a FrameSet Coordinate System

As discussed earlier (§13.4), an important application of a FrameSet is to allow coordinate system information to be attached to entities such as images in order to calibrate them. In addition, one of the main objectives of AST is to simplify the propagation of such information through successive stages of data processing, so that it remains consistent with the associated image data.

In such a situation, the FrameSet’s base Frame would correspond with the image’s data grid coordinates and its other Frames (if any) with the various alternative world coordinate systems associated with the image. If the data processing being performed does not change the relationship between the image’s data grid coordinates and any of the associated world coordinate systems, then propagation of the WCS information is straightforward and simply involves copying the FrameSet associated with the image.

If any of these relationships change, however, then corresponding changes must be made to the way Frames within the FrameSet are inter-related. By far the most common case occurs when the image undergoes some geometrical transformation resulting in “re-gridding” on to another data grid, but the same principles can be applied to any re-definition of a coordinate system.

To pursue the re-gridding example, we would need to modify our FrameSet to account for the fact that the image’s data grid coordinate system (corresponding to the FrameSet’s base Frame) has changed. Looking at the steps needed in detail, we might proceed as follows:

(1)
Create a Mapping which represents the relationship between the original data grid coordinate system and the new one.
(2)
Obtain a Frame to represent the new data grid coordinate system (we could re-use the original base Frame here, using astGetFrame to obtain a pointer to it).
(3)
Add the new Frame to the FrameSet, related to the original base Frame by the new Mapping. This Frame now represents the new data grid coordinate system and is correctly related to all the other Frames present.26
(4)
Remove the original base Frame (representing the old data grid coordinate system).
(5)
Make the new Frame the base Frame and restore the original current Frame.

The effect of these steps is to change the relationship between the base Frame and all the other Frames present. It is as if a new Mapping has been interposed between the Frame we want to alter and all the other Frames within the FrameSet (Figure 17).

Performing the steps above is rather lengthy, however, so the astRemapFrame function is provided to perform all of these operations in one go. A practical example of its use is given below (§14.5).

#### 14.5 Example—Binning an Image

As an example of using astRemapFrame, consider a case where the pixels of a 2-dimensional image have been binned 2$×$2, so as to reduce the image size by a factor of two in each dimension. We must now modify the associated FrameSet to reflect this change to the image. Much the same process would be needed for any other geometrical change the image might undergo.

We first set up a Mapping (a WinMap in this case) which relates the data grid coordinates in the original image to those in the new one:

AstWinMap *winmap;
double ina[ 2 ] = { 0.5, 0.5 };
double inb[ 2 ] = { 2.5, 2.5 };
double outa[ 2 ] = { 0.5, 0.5 };
double outb[ 2 ] = { 1.5, 1.5 };

...

winmap = astWinMap( 2, ina, inb, outa, outb, "" );

Here, we have simply set up arrays containing the data grid coordinates of the bottom left and top right corners of the first element in the output image (“outa” and “outb”) and the corresponding coordinates in the input image (“ina” and “inb”). astWinMap then creates a WinMap which performs the required transformation. We do not need to know the size of the image.

We can then pass this WinMap to astRemapFrame. This modifies the relationship between our FrameSet’s base Frame and the other Frames in the FrameSet, so that the base Frame represents the data grid coordinate system of the new image rather than the old one:

AstFrameSet *frameset;

...

astRemapFrame( frameset, AST__BASE, winmap );

Any other coordinate systems described by the FrameSet, no matter how many of these there might be, are now correctly associated with the new image.

#### 14.6 Maintaining the Integrity of FrameSets

When constructing a FrameSet, you are provided with a framework into which you can place any combination of Frames and Mappings that you wish. There are relatively few constraints on this process and no checks are performed to see whether the FrameSet you construct makes physical sense. It is quite possible, for example, to construct a FrameSet containing two identical SkyFrames which are inter-related by a non-unit Mapping. AST will not object if you do this, but it makes no sense, because applying a non-unit Mapping to any set of celestial coordinates cannot yield positions that are still in the original coordinate system. If you use such a FrameSet to perform coordinate conversions, you are likely to get unpredictable results because the information in the FrameSet is corrupt.

It is, of course, your responsibility as a programmer to ensure the validity of any information which you insert into a FrameSet. Normally, this is straightforward and simply consists of formulating your problem correctly (a diagram can often help to clarify how coordinate systems are inter-related) and writing the appropriate bug-free code to construct the FrameSet. However, once you start to modify an existing FrameSet, there are new opportunities for corrupting it!

Consider, for example, a FrameSet whose current Frame is a SkyFrame. We can set a new value for this SkyFrame’s Equinox attribute simply by using astSet on the FrameSet, as follows:

astSet( frameset, "Equinox=J2010" );

The effect of this will be to change the celestial coordinate system which the current Frame represents. You can see, however, that this has the potential to make the FrameSet corrupt unless corresponding changes are also made to the Mapping which relates this SkyFrame to the other Frames within the FrameSet. In fact, it is a general rule that any change to a FrameSet which affects its current Frame can potentially require corresponding changes to the FrameSet’s Mappings in order to maintain its overall integrity.

Fortunately, once you have stored valid information in a FrameSet, AST will look after these details for you automatically, so that the FrameSet’s integrity is maintained. In the example above, it would do this by appropriately re-mapping the current Frame (as if astRemapFrame had been used—§14.4) in response to the use of astSet. One way of illustrating this process is as follows:

AstSkyFrame *skyframe;

...

skyframe = astSkyFrame( "" );
frameSet = astFrameSet( skyframe );
astAddFrame( frameset, 1, astUnitMap( 2, "" ), skyframe );

This constructs a trivial FrameSet whose base and current Frames are both the same SkyFrame connected by a UnitMap. You can think of this as a “pipe” connecting two coordinate systems. At present, these two systems represent identical ICRS coordinates, so the FrameSet implements a unit Mapping. We can change the coordinate system on the current end of this pipe as follows:

astSet( frameset, "System=Ecliptic, Equinox=J2010" );

and the Mapping which the FrameSet implements would change accordingly. To change the coordinate system on the base end of the pipe, we might use:

astInvert( frameset );
astSet( frameset, "System=Galactic" );
astInvert( frameset );

The FrameSet would then convert between galactic and ecliptic coordinates.

Note that astSet is not the only function which has this effect: astClear behaves similarly, as also does astPermAxes7.9). If you need to circumvent this mechanism for any reason, this can be done by going behind the scenes and obtaining a pointer directly to the Frame you wish to modify. Consider the following, for example:

skyframe = astGetFrame( frameset, AST__CURRENT );
astSet( skyframe, "Equinox=J2010" );
skyframe = astAnnul( skyframe );

Here, astSet is applied to the SkyFrame pointer rather than the FrameSet pointer, so the usual checks on FrameSet integrity do not occur. The SkyFrame’s Equinox attribute will therefore be modified without any corresponding change to the FrameSet’s Mappings. In this case you must take responsibility yourself for maintaining the FrameSet’s integrity, perhaps through appropriate use of astRemapFrame.

#### 14.7 Merging FrameSets

As well as adding individual Frames to a FrameSet13.3), it is also possible to add complete sets of inter-related Frames which are contained within another FrameSet. This, of course, corresponds to the process of merging two FrameSets (Figure 18).

This process is performed by adding one FrameSet to another using astAddFrame, in much the same manner as when adding a new Frame to an existing FrameSet (§13.3). It is simply a matter of providing a FrameSet pointer, instead of a Frame pointer, for the 4th argument. In performing the merger you must, as usual, supply a Mapping, but in this case the Mapping should relate the current Frame of the FrameSet being added to one of the Frames already present. For example, you might perform the merger shown in Figure 18 as follows:

AstMapping *mapping;

...

astAddFrame( frameseta, 1, mapping, framesetb );

The Frames acquired by “frameseta” from the FrameSet being added (“framesetb”) are re-numbered so that they retain their original order and follow on consecutively after the Frames that were already present, whose indices remain unchanged. The base Frame of “frameseta” remains unchanged, but the current Frame of “framesetb” becomes its new current Frame. All the inter-relationships between Frames in both FrameSets remain in place and are preserved in the merged FrameSet.

Note that while this process modifies the first FrameSet (“frameseta”), it leaves the original contents of the one being added (“framesetb”) unchanged.

25If you find that how this ambiguity is resolved actually makes a difference to the conversion that results, then you have probably constructed a FrameSet which lacks internal self-consistency. For example, you might have two Frames representing indistinguishable coordinate systems but inter-related by a non-null Mapping.

26This is because any transformation to or from this new Frame must go via the base Frame representing the original data grid coordinate system, which we assume was correctly related to all the other Frames present.