2
2
3
3
import numpy as np
4
4
5
- from astropy import wcs
5
+ from astropy import coordinates as coord
6
+ from astropy import wcs as fits_wcs
6
7
from astropy .io import fits
8
+ from astropy .modeling .models import (
9
+ Shift , Polynomial2D , Pix2Sky_TAN , RotateNative2Celestial , Mapping
10
+ )
11
+ from astropy .modeling .projections import AffineTransformation2D
12
+ from astropy import units
13
+
14
+ from gwcs .coordinate_frames import CelestialFrame , Frame2D
15
+ import gwcs
7
16
8
17
__all__ = ["wcs_from_file" ]
9
18
10
19
TEST_DIR = os .path .abspath (os .path .dirname (__file__ ))
11
20
DATA_DIR = os .path .join (TEST_DIR , 'data' )
12
21
13
22
14
- def wcs_from_file (filename , ext = None , return_data = False , crpix_shift = None ):
23
+ def wcs_from_file (filename , ext = None , return_data = False , crpix_shift = None ,
24
+ wcs_type = "fits" ):
15
25
"""
16
26
Read the WCS from a ".fits" file.
27
+
28
+ Parameters
29
+ ----------
30
+
31
+ filename : str
32
+ Name of the file to load WCS from.
33
+
34
+ ext : int, None, optional
35
+ Extension number to load the WCS from. When `None`, the WCS will be
36
+ loaded from the first extension containing a WCS.
37
+
38
+ return_data : bool, optional
39
+ When `True`, this function will return a tuple with first item
40
+ being the WCS and the second item being the image data array.
41
+
42
+ crpix_shift : tuple, None, optional
43
+ A tuple of two values to be added to header CRPIX values before
44
+ creating the WCS. This effectively introduces a constant shift
45
+ in the image coordinate system.
46
+
47
+ wcs_type : {"fits", "gwcs"}, optional
48
+ Return either a FITS WCS or a gwcs.
49
+
50
+ Returns
51
+ -------
52
+
53
+ WCS or tuple of WCS and image data
54
+
17
55
"""
18
56
full_file_name = os .path .join (DATA_DIR , filename )
19
57
path = os .path .join (DATA_DIR , full_file_name )
@@ -38,9 +76,12 @@ def wcs_from_file(filename, ext=None, return_data=False, crpix_shift=None):
38
76
hdr ["CRPIX1" ] += crpix_shift [0 ]
39
77
hdr ["CRPIX2" ] += crpix_shift [1 ]
40
78
41
- result = wcs .WCS (hdr , hdu )
79
+ result = fits_wcs .WCS (hdr , hdu )
42
80
result .array_shape = shape
43
81
82
+ if wcs_type == "gwcs" :
83
+ result = _gwcs_from_hst_fits_wcs (result )
84
+
44
85
if return_data :
45
86
result = (result , )
46
87
if not isinstance (return_data , (list , tuple )):
@@ -50,3 +91,99 @@ def wcs_from_file(filename, ext=None, return_data=False, crpix_shift=None):
50
91
result = result + data
51
92
52
93
return result
94
+
95
+
96
+ def _gwcs_from_hst_fits_wcs (w ):
97
+ # NOTE: this function ignores table distortions
98
+ def coeffs_to_poly (mat , degree ):
99
+ pol = Polynomial2D (degree = degree )
100
+ for i in range (mat .shape [0 ]):
101
+ for j in range (mat .shape [1 ]):
102
+ if 0 < i + j <= degree :
103
+ setattr (pol , f'c{ i } _{ j } ' , mat [i , j ])
104
+ return pol
105
+
106
+ nx , ny = w .pixel_shape
107
+ x0 , y0 = w .wcs .crpix - 1
108
+
109
+ cd = w .wcs .piximg_matrix
110
+
111
+ if w .sip is None :
112
+ # construct GWCS:
113
+ det2sky = (
114
+ (Shift (- x0 ) & Shift (- y0 )) |
115
+ Pix2Sky_TAN () | RotateNative2Celestial (* w .wcs .crval , 180 )
116
+ )
117
+ else :
118
+ cfx , cfy = np .dot (cd , [w .sip .a .ravel (), w .sip .b .ravel ()])
119
+ a = np .reshape (cfx , w .sip .a .shape )
120
+ b = np .reshape (cfy , w .sip .b .shape )
121
+ a [1 , 0 ] = cd [0 , 0 ]
122
+ a [0 , 1 ] = cd [0 , 1 ]
123
+ b [1 , 0 ] = cd [1 , 0 ]
124
+ b [0 , 1 ] = cd [1 , 1 ]
125
+
126
+ polx = coeffs_to_poly (a , w .sip .a_order )
127
+ poly = coeffs_to_poly (b , w .sip .b_order )
128
+
129
+ sip = Mapping ((0 , 1 , 0 , 1 )) | (polx & poly )
130
+
131
+ # construct GWCS:
132
+ det2sky = (
133
+ (Shift (- x0 ) & Shift (- y0 )) | sip |
134
+ Pix2Sky_TAN () | RotateNative2Celestial (* w .wcs .crval , 180 )
135
+ )
136
+
137
+ detector_frame = Frame2D (
138
+ name = "detector" ,
139
+ axes_names = ("x" , "y" ),
140
+ unit = (units .pix , units .pix )
141
+ )
142
+ sky_frame = CelestialFrame (
143
+ reference_frame = getattr (coord , w .wcs .radesys ).__call__ (),
144
+ name = w .wcs .radesys ,
145
+ unit = (units .deg , units .deg )
146
+ )
147
+ pipeline = [(detector_frame , det2sky ), (sky_frame , None )]
148
+ gw = gwcs .wcs .WCS (pipeline )
149
+ gw .array_shape = w .array_shape
150
+ gw .bounding_box = ((- 0.5 , nx - 0.5 ), (- 0.5 , ny - 0.5 ))
151
+
152
+ if w .sip is not None :
153
+ # compute inverse SIP and re-create output GWCS
154
+
155
+ # compute inverse SIP:
156
+ hdr = gw .to_fits_sip (
157
+ max_inv_pix_error = 1e-5 ,
158
+ inv_degree = None ,
159
+ npoints = 64 ,
160
+ crpix = w .wcs .crpix ,
161
+ projection = 'TAN' ,
162
+ verbose = False
163
+ )
164
+ winv = fits_wcs .WCS (hdr )
165
+ ap = winv .sip .ap .copy ()
166
+ bp = winv .sip .bp .copy ()
167
+ ap [1 , 0 ] += 1
168
+ bp [0 , 1 ] += 1
169
+ polx_inv = coeffs_to_poly (ap , winv .sip .ap_order )
170
+ poly_inv = coeffs_to_poly (bp , winv .sip .bp_order )
171
+ af = AffineTransformation2D (
172
+ matrix = np .linalg .inv (winv .wcs .piximg_matrix )
173
+ )
174
+
175
+ # set analytical inverses:
176
+ sip .inverse = af | Mapping ((0 , 1 , 0 , 1 )) | (polx_inv & poly_inv )
177
+
178
+ # construct GWCS:
179
+ det2sky = (
180
+ (Shift (- x0 ) & Shift (- y0 )) | sip |
181
+ Pix2Sky_TAN () | RotateNative2Celestial (* w .wcs .crval , 180 )
182
+ )
183
+
184
+ pipeline = [(detector_frame , det2sky ), (sky_frame , None )]
185
+ gw = gwcs .wcs .WCS (pipeline )
186
+ gw .array_shape = w .array_shape
187
+ gw .bounding_box = ((- 0.5 , nx - 0.5 ), (- 0.5 , ny - 0.5 ))
188
+
189
+ return gw
0 commit comments