1 /*
2  # This file is part of the Astrometry.net suite.
3  # Licensed under a 3-clause BSD style license - see LICENSE
4  */
5 
6 #include <math.h>
7 #include <string.h>
8 
9 #include "qfits_rw.h"
10 #include "qfits_tools.h"
11 
12 #include "os-features.h"
13 #include "sip_qfits.h"
14 #include "an-bool.h"
15 #include "fitsioutils.h"
16 #include "errors.h"
17 #include "log.h"
18 #include "ioutils.h"
19 
20 #include "anqfits.h"
21 
sip_from_string(const char * str,int slen,sip_t * dest)22 sip_t* sip_from_string(const char* str, int slen, sip_t* dest) {
23     qfits_header* hdr;
24     sip_t* rtn;
25     if (slen == 0) {
26         slen = strlen(str);
27     }
28     hdr = qfits_header_read_hdr_string((const unsigned char*)str, slen);
29     if (!hdr) {
30         ERROR("Failed to parse a FITS header from the given string");
31         return NULL;
32     }
33     rtn = sip_read_header(hdr, dest);
34     qfits_header_destroy(hdr);
35     return rtn;
36 }
37 
sip_read_tan_or_sip_header_file_ext(const char * wcsfn,int ext,sip_t * dest,anbool forcetan)38 sip_t* sip_read_tan_or_sip_header_file_ext(const char* wcsfn, int ext, sip_t* dest, anbool forcetan) {
39     sip_t* rtn;
40     if (forcetan) {
41         sip_t sip;
42         memset(&sip, 0, sizeof(sip_t));
43         if (!tan_read_header_file_ext(wcsfn, ext, &(sip.wcstan))) {
44             ERROR("Failed to parse TAN header from file %s, extension %i", wcsfn, ext);
45             return NULL;
46         }
47         if (!dest)
48             dest = malloc(sizeof(sip_t));
49         memcpy(dest, &sip, sizeof(sip_t));
50         return dest;
51     } else {
52         rtn = sip_read_header_file_ext(wcsfn, ext, dest);
53         if (!rtn)
54             ERROR("Failed to parse SIP header from file %s, extension %i", wcsfn, ext);
55         return rtn;
56     }
57 }
58 
sip_write_to(const sip_t * sip,FILE * fid)59 int sip_write_to(const sip_t* sip, FILE* fid) {
60     qfits_header* hdr;
61     int res;
62     if ((sip->a_order == 0) && (sip->b_order == 0) &&
63         (sip->ap_order == 0) && (sip->bp_order == 0))
64         return tan_write_to(&(sip->wcstan), fid);
65     hdr = sip_create_header(sip);
66     if (!hdr) {
67         ERROR("Failed to create FITS header from WCS");
68         return -1;
69     }
70     res = qfits_header_dump(hdr, fid);
71     qfits_header_destroy(hdr);
72     return res;
73 }
74 
sip_write_to_file(const sip_t * sip,const char * fn)75 int sip_write_to_file(const sip_t* sip, const char* fn) {
76     FILE* fid;
77     int res;
78     if ((sip->a_order == 0) && (sip->b_order == 0) &&
79         (sip->ap_order == 0) && (sip->bp_order == 0))
80         return tan_write_to_file(&(sip->wcstan), fn);
81 
82     fid = fopen(fn, "wb");
83     if (!fid) {
84         SYSERROR("Failed to open file \"%s\" to write WCS header", fn);
85         return -1;
86     }
87     res = sip_write_to(sip, fid);
88     if (res) {
89         ERROR("Failed to write FITS header to file \"%s\"", fn);
90         fclose(fid); //# Modified by Robert Lancaster for the StellarSolver Internal Library, to prevent leak
91         return -1;
92     }
93     if (fclose(fid)) {
94         SYSERROR("Failed to close file \"%s\" after writing WCS header", fn);
95         return -1;
96     }
97     return 0;
98 }
99 
tan_write_to(const tan_t * tan,FILE * fid)100 int tan_write_to(const tan_t* tan, FILE* fid) {
101     qfits_header* hdr;
102     int res;
103     hdr = tan_create_header(tan);
104     if (!hdr) {
105         ERROR("Failed to create FITS header from WCS");
106         return -1;
107     }
108     res = qfits_header_dump(hdr, fid);
109     qfits_header_destroy(hdr);
110     return res;
111 }
112 
tan_write_to_file(const tan_t * tan,const char * fn)113 int tan_write_to_file(const tan_t* tan, const char* fn) {
114     FILE* fid;
115     int res;
116     fid = fopen(fn, "wb");
117     if (!fid) {
118         SYSERROR("Failed to open file \"%s\" to write WCS header", fn);
119         return -1;
120     }
121     res = tan_write_to(tan, fid);
122     if (res) {
123         ERROR("Failed to write FITS header to file \"%s\"", fn);
124         fclose(fid); //# Modified by Robert Lancaster for the StellarSolver Internal Library, to prevent leak
125         return -1;
126     }
127     if (fclose(fid)) {
128         SYSERROR("Failed to close file \"%s\" after writing WCS header", fn);
129         return -1;
130     }
131     return 0;
132 }
133 
wcs_hdr_common(qfits_header * hdr,const tan_t * tan)134 static void wcs_hdr_common(qfits_header* hdr, const tan_t* tan) {
135     qfits_header_add(hdr, "WCSAXES", "2", NULL, NULL);
136     qfits_header_add(hdr, "EQUINOX", "2000.0", "Equatorial coordinates definition (yr)", NULL);
137     qfits_header_add(hdr, "LONPOLE", "180.0", NULL, NULL);
138     qfits_header_add(hdr, "LATPOLE", "0.0", NULL, NULL);
139 
140     fits_header_add_double(hdr, "CRVAL1", tan->crval[0], "RA  of reference point");
141     fits_header_add_double(hdr, "CRVAL2", tan->crval[1], "DEC of reference point");
142     fits_header_add_double(hdr, "CRPIX1", tan->crpix[0], "X reference pixel");
143     fits_header_add_double(hdr, "CRPIX2", tan->crpix[1], "Y reference pixel");
144     qfits_header_add(hdr, "CUNIT1", "deg", "X pixel scale units", NULL);
145     qfits_header_add(hdr, "CUNIT2", "deg", "Y pixel scale units", NULL);
146 
147     fits_header_add_double(hdr, "CD1_1", tan->cd[0][0], "Transformation matrix");
148     fits_header_add_double(hdr, "CD1_2", tan->cd[0][1], "");
149     fits_header_add_double(hdr, "CD2_1", tan->cd[1][0], "");
150     fits_header_add_double(hdr, "CD2_2", tan->cd[1][1], "");
151 
152     if (tan->imagew > 0.0)
153         fits_header_add_double(hdr, "IMAGEW", tan->imagew, "Image width,  in pixels.");
154     if (tan->imageh > 0.0)
155         fits_header_add_double(hdr, "IMAGEH", tan->imageh, "Image height, in pixels.");
156 }
157 
sip_get_image_size(const qfits_header * hdr,int * pW,int * pH)158 int sip_get_image_size(const qfits_header* hdr, int* pW, int* pH) {
159     int W, H;
160     W = qfits_header_getint(hdr, "IMAGEW", 0);
161     debug("sip_get_image_size: IMAGEW = %i\n", W);
162     H = qfits_header_getint(hdr, "IMAGEH", 0);
163     debug("sip_get_image_size: IMAGEH = %i\n", H);
164     if (W == 0 || H == 0) {
165         // no IMAGE[WH].  Check for fpack-compressed image.
166         int eq;
167         char* str = fits_get_dupstring(hdr, "XTENSION");
168         //printf("XTENSION: '%s'\n", str);
169         // qfits_header_getstr turns the string double-quotes to single-quotes
170         eq = streq(str, "BINTABLE");
171         free(str);
172         if (eq) {
173             // ZNAXIS1 =                 2046 / length of data axis 1
174             // ZNAXIS2 =                 4094 / length of data axis 2
175             if (!W) {
176                 W = qfits_header_getint(hdr, "ZNAXIS1", 0);
177                 debug("sip_get_image_size: ZNAXIS1 = %i\n", W);
178             }
179             if (!H) {
180                 H = qfits_header_getint(hdr, "ZNAXIS2", 0);
181                 debug("sip_get_image_size: ZNAXIS2 = %i\n", H);
182             }
183         }
184         if (!W) {
185             W = qfits_header_getint(hdr, "NAXIS1", 0);
186             debug("sip_get_image_size: NAXIS1 = %i\n", W);
187         }
188         if (!H) {
189             H = qfits_header_getint(hdr, "NAXIS2", 0);
190             debug("sip_get_image_size: NAXIS2 = %i\n", H);
191         }
192     }
193     if (pW) *pW = W;
194     if (pH) *pH = H;
195     return 0;
196 }
197 
add_polynomial(qfits_header * hdr,const char * format,int order,const double * data,int datastride)198 static void add_polynomial(qfits_header* hdr, const char* format,
199                            int order, const double* data, int datastride) {
200     int i, j;
201     char key[64];
202     for (i=0; i<=order; i++)
203         for (j=0; (i+j)<=order; j++) {
204             //if (i+j < 1)
205             //	continue;
206             //if (drop_linear && (i+j < 2))
207             //	continue;
208             sprintf(key, format, i, j);
209             fits_header_add_double(hdr, key, data[i*datastride + j], "");
210         }
211 }
212 
sip_add_to_header(qfits_header * hdr,const sip_t * sip)213 void sip_add_to_header(qfits_header* hdr, const sip_t* sip) {
214     wcs_hdr_common(hdr, &(sip->wcstan));
215     if (sip->wcstan.sin) {
216         qfits_header_add_after(hdr, "WCSAXES", "CTYPE2", "DEC--SIN-SIP", "SIN projection + SIP distortions", NULL);
217         qfits_header_add_after(hdr, "WCSAXES", "CTYPE1", "RA---SIN-SIP", "SIN projection + SIP distortions", NULL);
218     } else {
219         qfits_header_add_after(hdr, "WCSAXES", "CTYPE2", "DEC--TAN-SIP", "TAN (gnomic) projection + SIP distortions", NULL);
220         qfits_header_add_after(hdr, "WCSAXES", "CTYPE1", "RA---TAN-SIP", "TAN (gnomic) projection + SIP distortions", NULL);
221     }
222 
223     fits_header_add_int(hdr, "A_ORDER", sip->a_order, "Polynomial order, axis 1");
224     add_polynomial(hdr, "A_%i_%i", sip->a_order, (double*)sip->a, SIP_MAXORDER);
225 
226     fits_header_add_int(hdr, "B_ORDER", sip->b_order, "Polynomial order, axis 2");
227     add_polynomial(hdr, "B_%i_%i", sip->b_order, (double*)sip->b, SIP_MAXORDER);
228 
229     fits_header_add_int(hdr, "AP_ORDER", sip->ap_order, "Inv polynomial order, axis 1");
230     add_polynomial(hdr, "AP_%i_%i", sip->ap_order, (double*)sip->ap, SIP_MAXORDER);
231 
232     fits_header_add_int(hdr, "BP_ORDER", sip->bp_order, "Inv polynomial order, axis 2");
233     add_polynomial(hdr, "BP_%i_%i", sip->bp_order, (double*)sip->bp, SIP_MAXORDER);
234 }
235 
sip_create_header(const sip_t * sip)236 qfits_header* sip_create_header(const sip_t* sip) {
237     qfits_header* hdr = qfits_table_prim_header_default();
238     sip_add_to_header(hdr, sip);
239     return hdr;
240 }
241 
tan_add_to_header(qfits_header * hdr,const tan_t * tan)242 void tan_add_to_header(qfits_header* hdr, const tan_t* tan) {
243     wcs_hdr_common(hdr, tan);
244     if (tan->sin) {
245         qfits_header_add_after(hdr, "WCSAXES", "CTYPE2", "DEC--SIN", "SIN projection", NULL);
246         qfits_header_add_after(hdr, "WCSAXES", "CTYPE1", "RA---SIN", "SIN projection", NULL);
247     } else {
248         qfits_header_add_after(hdr, "WCSAXES", "CTYPE2", "DEC--TAN", "TAN (gnomic) projection", NULL);
249         qfits_header_add_after(hdr, "WCSAXES", "CTYPE1", "RA---TAN", "TAN (gnomic) projection", NULL);
250     }
251 }
252 
tan_create_header(const tan_t * tan)253 qfits_header* tan_create_header(const tan_t* tan) {
254     qfits_header* hdr = qfits_table_prim_header_default();
255     tan_add_to_header(hdr, tan);
256     return hdr;
257 }
258 
read_header_file(const char * fn,int ext,anbool only,void * dest,void * (* readfunc)(const qfits_header *,void *))259 static void* read_header_file(const char* fn, int ext, anbool only, void* dest,
260                               void* (*readfunc)(const qfits_header*, void*)) {
261     qfits_header* hdr;
262     void* result;
263     if (only) {
264         hdr = anqfits_get_header_only(fn, ext);
265     } else {
266         hdr = anqfits_get_header2(fn, ext);
267     }
268     if (!hdr) {
269         ERROR("Failed to read FITS header from file \"%s\" extension %i", fn, ext);
270         return NULL;
271     }
272     result = readfunc(hdr, dest);
273     if (!result) {
274         ERROR("Failed to parse WCS header from file \"%s\" extension %i", fn, ext);
275     }
276     qfits_header_destroy(hdr);
277     return result;
278 }
279 
280 // silly little dispatch function to avoid casting - I like a modicum of type safety
call_sip_read_header(const qfits_header * hdr,void * dest)281 static void* call_sip_read_header(const qfits_header* hdr, void* dest) {
282     return sip_read_header(hdr, dest);
283 }
sip_read_header_file(const char * fn,sip_t * dest)284 sip_t* sip_read_header_file(const char* fn, sip_t* dest) {
285     return read_header_file(fn, 0, FALSE, dest, call_sip_read_header);
286 }
sip_read_header_file_ext(const char * fn,int ext,sip_t * dest)287 sip_t* sip_read_header_file_ext(const char* fn, int ext, sip_t* dest) {
288     return read_header_file(fn, ext, TRUE, dest, call_sip_read_header);
289 }
sip_read_header_file_ext_only(const char * fn,int ext,sip_t * dest)290 sip_t* sip_read_header_file_ext_only(const char* fn, int ext, sip_t* dest) {
291     return read_header_file(fn, ext, TRUE, dest, call_sip_read_header);
292 }
293 
call_tan_read_header(const qfits_header * hdr,void * dest)294 static void* call_tan_read_header(const qfits_header* hdr, void* dest) {
295     return tan_read_header(hdr, dest);
296 }
tan_read_header_file(const char * fn,tan_t * dest)297 tan_t* tan_read_header_file(const char* fn, tan_t* dest) {
298     return read_header_file(fn, 0, FALSE, dest, call_tan_read_header);
299 }
tan_read_header_file_ext(const char * fn,int ext,tan_t * dest)300 tan_t* tan_read_header_file_ext(const char* fn, int ext, tan_t* dest) {
301     return read_header_file(fn, ext, FALSE, dest, call_tan_read_header);
302 }
tan_read_header_file_ext_only(const char * fn,int ext,tan_t * dest)303 tan_t* tan_read_header_file_ext_only(const char* fn, int ext, tan_t* dest) {
304     return read_header_file(fn, ext, TRUE, dest, call_tan_read_header);
305 }
306 
307 
read_polynomial(const qfits_header * hdr,const char * format,int order,double * data,int datastride,anbool skip_linear,anbool skip_zero)308 static anbool read_polynomial(const qfits_header* hdr, const char* format,
309                               int order, double* data, int datastride,
310                               anbool skip_linear, anbool skip_zero) {
311     int i, j;
312     char key[64];
313     double nil = -HUGE_VAL;
314     double val;
315     for (i=0; i<=order; i++)
316         for (j=0; (i+j)<=order; j++) {
317             if (skip_zero && (i+j < 1))
318                 continue;
319             if (skip_linear && (i+j < 2))
320                 continue;
321             sprintf(key, format, i, j);
322             val = qfits_header_getdouble(hdr, key, nil);
323             if (val == nil) {
324                 // don't warn if linear terms are "missing"
325                 if (i+j >= 2) {
326                     ERROR("SIP: warning: key \"%s\" not found; setting to zero.", key);
327                 }
328                 val=0.0;
329             }
330             data[i*datastride + j] = val;
331         }
332     return TRUE;
333 }
334 
sip_read_header(const qfits_header * hdr,sip_t * dest)335 sip_t* sip_read_header(const qfits_header* hdr, sip_t* dest) {
336     sip_t sip;
337     char* str;
338     const char* key;
339     const char* expect;
340     const char* expect2;
341     anbool is_sin;
342     anbool is_tan;
343     anbool skip_linear;
344     anbool skip_zero;
345     char pretty[FITS_LINESZ];
346 
347     memset(&sip, 0, sizeof(sip_t));
348 
349     key = "CTYPE1";
350     expect  = "RA---TAN-SIP";
351     expect2 = "RA---SIN-SIP";
352     str = qfits_header_getstr(hdr, key);
353     str = qfits_pretty_string_r(str, pretty);
354     if (!str) {
355         ERROR("SIP header: no key \"%s\"", key);
356         return NULL;
357     }
358     is_tan = (strncmp(str, expect, strlen(expect)) == 0);
359     is_sin = (strncmp(str, expect2, strlen(expect2)) == 0);
360     if (!(is_tan || is_sin)) {
361         if (!tan_read_header(hdr, &(sip.wcstan))) {
362             ERROR("SIP: failed to read TAN header");
363             return NULL;
364         }
365         goto gohome;
366     }
367 
368     key = "CTYPE2";
369     if (is_sin) {
370         expect = "DEC--SIN-SIP";
371     } else {
372         expect = "DEC--TAN-SIP";
373     }
374     str = qfits_header_getstr(hdr, key);
375     str = qfits_pretty_string_r(str, pretty);
376     if (!str || strncmp(str, expect, strlen(expect))) {
377         ERROR("SIP header: incorrect key \"%s\": expected \"%s\", got \"%s\"", key, expect, str);
378         return NULL;
379     }
380 
381     if (!tan_read_header(hdr, &sip.wcstan)) {
382         ERROR("SIP: failed to read TAN header");
383         return NULL;
384     }
385 
386     sip.a_order  = qfits_header_getint(hdr, "A_ORDER", -1);
387     sip.b_order  = qfits_header_getint(hdr, "B_ORDER", -1);
388     sip.ap_order = qfits_header_getint(hdr, "AP_ORDER", 0);
389     sip.bp_order = qfits_header_getint(hdr, "BP_ORDER", 0);
390 
391     if ((sip.a_order == -1) ||
392         (sip.b_order == -1)) {
393         ERROR("SIP: failed to read polynomial orders (A_ORDER=%i, B_ORDER=%i, -1 means absent)\n",
394               sip.a_order, sip.b_order);
395         return NULL;
396     }
397     if ((sip.ap_order == 0) ||
398         (sip.bp_order == 0)) {
399         logverb("Warning: SIP: failed to read polynomial orders (A_ORDER=%i, B_ORDER=%i (-1 means absent), AP_ORDER=%i, BP_ORDER=%i, (0 means absent)\n",
400                 sip.a_order, sip.b_order, sip.ap_order, sip.bp_order);
401     }
402 
403     if ((sip.a_order > SIP_MAXORDER) ||
404         (sip.b_order > SIP_MAXORDER) ||
405         (sip.ap_order > SIP_MAXORDER) ||
406         (sip.bp_order > SIP_MAXORDER)) {
407         ERROR("SIP: polynomial orders (A=%i, B=%i, AP=%i, BP=%i) exceeds maximum of %i",
408               sip.a_order, sip.b_order, sip.ap_order, sip.bp_order, SIP_MAXORDER);
409         return NULL;
410     }
411 
412     skip_linear = FALSE;
413     skip_zero = FALSE;
414 
415     if (!read_polynomial(hdr, "A_%i_%i",  sip.a_order,  (double*)sip.a,  SIP_MAXORDER, skip_linear, skip_zero) ||
416         !read_polynomial(hdr, "B_%i_%i",  sip.b_order,  (double*)sip.b,  SIP_MAXORDER, skip_linear, skip_zero) ||
417         (sip.ap_order > 0 && !read_polynomial(hdr, "AP_%i_%i", sip.ap_order, (double*)sip.ap, SIP_MAXORDER, FALSE, FALSE)) ||
418         (sip.bp_order > 0 && !read_polynomial(hdr, "BP_%i_%i", sip.bp_order, (double*)sip.bp, SIP_MAXORDER, FALSE, FALSE))) {
419         ERROR("SIP: failed to read polynomial terms");
420         return NULL;
421     }
422 
423  gohome:
424     if (!dest)
425         dest = malloc(sizeof(sip_t));
426 
427     memcpy(dest, &sip, sizeof(sip_t));
428     return dest;
429 }
430 
check_tan_ctypes(char * ct1,char * ct2,anbool * is_sin)431 static int check_tan_ctypes(char* ct1, char* ct2, anbool* is_sin) {
432     const char* ra  = "RA---TAN";
433     const char* dec = "DEC--TAN";
434     const char* ra2  = "RA---SIN";
435     const char* dec2 = "DEC--SIN";
436     int NC = 8;
437     *is_sin = FALSE;
438     if (!ct1 || !ct2)
439         return -1;
440     if (strlen(ct1) < NC || strlen(ct2) < NC)
441         return -1;
442     if ((strncmp(ct1, ra, NC) == 0) && (strncmp(ct2, dec, NC) == 0))
443         return 0;
444     if ((strncmp(ct1, dec, NC) == 0) && (strncmp(ct2, ra, NC) == 0))
445         return 1;
446 
447     if ((strncmp(ct1, ra2, NC) == 0) && (strncmp(ct2, dec2, NC) == 0)) {
448         *is_sin = TRUE;
449         return 0;
450     }
451     if ((strncmp(ct1, dec2, NC) == 0) && (strncmp(ct2, ra2, NC) == 0)) {
452         *is_sin = TRUE;
453         return 1;
454     }
455     return -1;
456 }
457 
tan_read_header(const qfits_header * hdr,tan_t * dest)458 tan_t* tan_read_header(const qfits_header* hdr, tan_t* dest) {
459     tan_t tan;
460     double nil = -1e300;
461     char* ct1;
462     char* ct2;
463     int swap;
464     int W, H;
465     anbool is_sin;
466 
467     memset(&tan, 0, sizeof(tan_t));
468 
469     ct1 = fits_get_dupstring(hdr, "CTYPE1");
470     ct2 = fits_get_dupstring(hdr, "CTYPE2");
471     swap = check_tan_ctypes(ct1, ct2, &is_sin);
472     if (swap == -1) {
473         ERROR("TAN header: expected CTYPE1 = RA---TAN, CTYPE2 = DEC--TAN "
474               "(or vice versa), or RA---SIN, DEC--SIN or vice versa; "
475               "got CTYPE1 = \"%s\", CYTPE2 = \"%s\"\n",
476               ct1, ct2);
477     }
478     free(ct1);
479     free(ct2);
480     if (swap == -1)
481         return NULL;
482 
483     sip_get_image_size(hdr, &W, &H);
484     tan.imagew = W;
485     tan.imageh = H;
486 
487     {
488         const char* keys[] = { "CRVAL1", "CRVAL2", "CRPIX1", "CRPIX2",
489                                "CD1_1", "CD1_2", "CD2_1", "CD2_2" };
490         double* vals[] = { &(tan.crval[0]), &(tan.crval[1]),
491                            &(tan.crpix[0]), &(tan.crpix[1]),
492                            &(tan.cd[0][0]), &(tan.cd[0][1]),
493                            &(tan.cd[1][0]), &(tan.cd[1][1]) };
494         int i;
495         for (i=0; i<4; i++) {
496             *(vals[i]) = qfits_header_getdouble(hdr, keys[i], nil);
497             if (*(vals[i]) == nil) {
498                 ERROR("TAN header: missing or invalid value for \"%s\"", keys[i]);
499                 return NULL;
500             }
501         }
502         // Try CD
503         int gotcd = 1;
504         char* complaint = NULL;
505         for (i=4; i<8; i++) {
506             *(vals[i]) = qfits_header_getdouble(hdr, keys[i], nil);
507             if (*(vals[i]) == nil) {
508                 asprintf_safe(&complaint, "TAN header: missing or invalid value for key \"%s\"", keys[i]);
509                 gotcd = 0;
510                 break;
511             }
512         }
513         if (!gotcd) {
514             double cdelt1,cdelt2;
515             // Try CDELT
516             char* key = "CDELT1";
517             cdelt1 = qfits_header_getdouble(hdr, key, nil);
518             if (cdelt1 == nil) {
519                 ERROR("%s; also tried but didn't find \"%s\"", complaint, key);
520                 free(complaint);
521                 return NULL;
522             }
523             key = "CDELT2";
524             cdelt2 = qfits_header_getdouble(hdr, key, nil);
525             if (cdelt2 == nil) {
526                 ERROR("%s; also tried but didn't find \"%s\"", complaint, key);
527                 free(complaint);
528                 return NULL;
529             }
530             tan.cd[0][0] = cdelt1;
531             tan.cd[0][1] = 0.0;
532             tan.cd[1][0] = 0.0;
533             tan.cd[1][1] = cdelt2;
534         }
535     }
536 
537     if (swap == 1) {
538         double tmp;
539         tmp = tan.crval[0];
540         tan.crval[0] = tan.crval[1];
541         tan.crval[1] = tmp;
542         // swap CD1_1 <-> CD2_1
543         tmp = tan.cd[0][0];
544         tan.cd[0][0] = tan.cd[1][0];
545         tan.cd[1][0] = tmp;
546         // swap CD1_2 <-> CD2_2
547         tmp = tan.cd[0][1];
548         tan.cd[0][1] = tan.cd[1][1];
549         tan.cd[1][1] = tmp;
550     }
551 
552     tan.sin = is_sin;
553 
554     if (!dest)
555         dest = malloc(sizeof(tan_t));
556     memcpy(dest, &tan, sizeof(tan_t));
557     return dest;
558 }
559