1 """
2 Code to obtain WCS headers for FITS files using astrometry.net
3
4 Astrometry.net has oodles of configurable parameters. Some of
5 them can be passed in via the solverParameters argument to getWCSFieldsFor,
6 a dictionary with keys including:
7
8 indices
9 (default: "index-\*.fits", meaning all indices in your default index dir)
10 The file names from anet's index directory you want to have used.
11 glob patterns are expanded, but no recursion takes place.
12
13 This could be something like::
14
15 ["index-4211.fits", "index-4210.fits", "index-4209.fits"]
16
17 for largeish images or::
18
19 ["index-4203-\*.fits", "index-4202-*.fits"]
20
21 for small ones. You can also give absolute path names for custom
22 indices that live, e.g., in your resource directory.
23
24 total_timelimit
25 (defaults to 600) -- number of seconds after which the anet run
26 should cancel itself.
27
28 tweak
29 (defaults to True) -- try to obtain a polynomic correction to the
30 entire image after obtaining a solution? This can go wrong in
31 particular for exposures with many objects, so you might want to
32 set it to off for such cases.
33
34 fields
35 (default to 1) -- FITS extension to work on
36
37 endob
38 (defaults to not given) -- last object to be processed. You don't want to
39 raise this too high. The library will only pass on 10 objects at a
40 time anyway, but going too high here will waste lots of time on images
41 that are probably not going to resolve anyway.
42
43 lower_pix
44 (defaults to not given) -- smallest permissible pixel size in arcsecs. If
45 at all possible, constrain this for much better results.
46
47 upper_pix
48 (defaults to not given) -- largest permissible pixel size in arcsecs.
49 See lower_pix.
50
51 plots
52 (defaults to False) -- generate all kinds of pretty plots?
53
54 downsample
55 (defaults to not given) -- factor to downsample the image before
56 trying to solve. This may be necessary when not using sextractor,
57 and it should be something like 2, 3, or 4.
58 """
59
60
61
62
63
64
65
66 from __future__ import print_function
67
68 import glob
69 import gzip
70 import os
71 import shutil
72 import subprocess
73
74 from gavo import base
75 from gavo import utils
76 from gavo.utils import fitstools
77 from gavo.utils import codetricks
78 from gavo.utils import pyfits
79
80 __docformat__ = "restructuredtext en"
81
82 anetPath = "/usr/local/astrometry/bin"
83 anetIndexPath = "/usr/local/astrometry/data"
84 solverBin = os.path.join(anetPath, "solve-field")
85 sextractorBin = "sextractor"
86
87
88 PARAM_DEFAULTS = {
89 "total_timelimit": 600,
90 "fields": "1",
91 "endob": None,
92 "lower_pix": None,
93 "upper_pix": None,
94 "tweak": True,
95 "pix_units": "app",
96 "plots": False,
97 "downsample": None,
98 }
99
100
103
106
109
112 Error.__init__(self, msg)
113 self.msg, self.retcode = msg, retcode
114
116 return "External program failure (%s). Program output: %s"%(
117 self.retcode, self.msg)
118
119
121 """links fName to "in.fits" in the sandbox.
122
123 If fName ends with .gz, the function assumes it's a gzipped file and
124 unzips it to in.fits instead.
125 """
126 srcName = os.path.join(os.getcwd(), fName)
127 destName = os.path.join(targDir, "img.fits")
128 if fName.endswith(".gz"):
129 with open(destName, "w") as destF:
130 utils.cat(gzip.open(srcName), destF)
131 else:
132 os.symlink(srcName, destName)
133
134
136 with open("lastCommand.log", "a") as logF:
137 logF.write("\n\n================\nNow running: %s\n\n"%
138 " ".join([cmd]+args))
139 logF.flush()
140 proc = subprocess.Popen([cmd]+args, stdout=logF,
141 stderr=subprocess.STDOUT)
142 proc.communicate()
143 if proc.returncode==-2:
144 raise KeyboardInterrupt("Child was siginted")
145 elif proc.returncode:
146 raise ShellCommandFailed(open("lastCommand.log").read(), proc.returncode)
147
148
150 if copyTo is not None:
151 try:
152 shutil.rmtree(copyTo)
153 except os.error:
154 pass
155 shutil.copytree(".", copyTo)
156
157
158
159
160 _ANET_SEX_STUB = """# sextractor control file for astrometry.net
161 CATALOG_TYPE FITS_1.0
162 # The output file name
163 CATALOG_NAME img.axy
164 # The name of the file containing _ANET_SEX_PARAM
165 PARAMETERS_NAME anet.param
166 FILTER_NAME anet.filter
167 """
168
169
170 _ANET_SEX_PARAM = """X_IMAGE
171 Y_IMAGE
172 MAG_ISO
173 FLUX_AUTO
174 ELONGATION
175 """
176
177
178 _ANET_SEX_FILTER = """CONV NORM
179 # 5x5 convolution mask of a gaussian PSF with FWHM = 2.0 pixels.
180 0.006319 0.040599 0.075183 0.040599 0.006319
181 0.040599 0.260856 0.483068 0.260856 0.040599
182 0.075183 0.483068 0.894573 0.483068 0.075183
183 0.040599 0.260856 0.483068 0.260856 0.040599
184 0.006319 0.040599 0.075183 0.040599 0.006319
185 """
186
187
195
196
198 """does source extraction using Sextractor.
199
200 If filterFunc is not None, it is called before sorting the extracted
201 objects. It must change the file named in the argument in place.
202 """
203 _runShellCommand(sextractorBin, ["-c", "anet.control", "img.fits"])
204 if filterFunc is not None:
205 filterFunc("img.axy")
206
207
208 _PARAMETER_MAP = [
209 ("total_timelimit", "--cpulimit"),
210 ("fields", "--fields"),
211 ("lower_pix", "--scale-low"),
212 ("upper_pix", "--scale-high"),
213 ("pix_units", "--scale-units"),
214 ("endob", "--depth"),
215 ("downsample", "--downsample"),
216 ]
217
219 if not actPars["tweak"]:
220 args.append("--no-tweak")
221 if not actPars["plots"]:
222 args.append("--no-plots")
223 for key, opthead in _PARAMETER_MAP:
224 if actPars[key] is not None:
225 args.extend([opthead, str(actPars[key])])
226
227
228 -def _solveField(fName, solverParameters, sexControl, objectFilter,
229 verbose):
230 """tries to solve an image anet's using solve-field.
231
232 See _resolve for the meaning of the arguments.
233 """
234 args = ["--continue"]
235 objectSource = "img.fits"
236 if sexControl is not None:
237 _createSextractorFiles(sexControl)
238 if sexControl==None:
239 pass
240 elif sexControl=="":
241 args.extend(["--use-sextractor", "--sextractor-path", sextractorBin])
242 else:
243 _extractSex(objectFilter)
244 width, height = fitstools.getPrimarySize("img.fits")
245 args.extend(["--x-column", "X_IMAGE",
246 "--y-column", "Y_IMAGE",
247 "--sort-column", "FLUX_AUTO",
248 "--use-sextractor",
249 "--width", str(width), "--height", str(height)])
250 objectSource = "img.axy"
251 args.append("--no-fits2fits")
252 args.append("-v")
253
254 if "indices" in solverParameters:
255 with open("backend.cfg", "w") as backendCfg:
256 for indF in solverParameters["indices"]:
257 fullPath = os.path.join(anetIndexPath, indF)
258 for fName in glob.glob(fullPath):
259 backendCfg.write("index %s\n"%fName)
260 backendCfg.write("inparallel")
261 args.extend(["--backend-config", "backend.cfg"])
262
263
264 actPars = PARAM_DEFAULTS.copy()
265 actPars.update(solverParameters)
266 _addArgsFor(actPars, args)
267
268 args.append(objectSource)
269 if verbose:
270 print("Running %s %s"%(solverBin, " ".join(args)))
271 _runShellCommand(solverBin, args)
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288 -def _resolve(fName, solverParameters={}, sexControl=None, objectFilter=None,
289 copyTo=None, verbose=False):
290 """runs the astrometric calibration pipeline.
291
292 solverParameters is a dictionary; most keys in it are simply turned
293 into command line options of solve-field.
294
295 This function litters the working directory with all kinds of files and does
296 not clean up, so you'd better run it in a sandbox.
297
298 It raises a NotSolved exception if no solution could be found; otherwise
299 you should find the solution in img.wcs.
300 """
301 if "_preparationFunction" in solverParameters:
302 solverParameters["_preparationFunction"]()
303 try:
304 _solveField(fName, solverParameters, sexControl, objectFilter, verbose)
305 except Exception:
306 _copyCurrentTree(copyTo)
307 raise
308 _copyCurrentTree(copyTo)
309 if os.path.exists("img.solved"):
310 return
311 raise NotSolved(fName)
312
313
315 return pyfits.getheader("img.wcs").cards
316
317
318 -def getWCSFieldsFor(fName, solverParameters, sexControl=None, objectFilter=None,
319 copyTo=None, verbose=False):
320 """returns a pyfits cardlist for the WCS fields on fName.
321
322 solverParameters is a dictionary mapping solver keys to their values,
323 sexScript is a script for SExtractor, and its presence means that
324 SExtractor should be used for source extraction rather than what anet
325 has built in. objectFilter is a function that is called with the
326 name of the FITS with the extracted sources. It can remove or add
327 sources to that file before astrometry.net tries to match.
328
329 To see what solverParameters are avaliable, check the module docstring.
330 """
331 try:
332 res = codetricks.runInSandbox(_feedFile, _resolve, _retrieveWCS,
333 fName, solverParameters=solverParameters, sexControl=sexControl,
334 objectFilter=objectFilter, copyTo=copyTo, verbose=verbose)
335 except NotSolved:
336 return None
337 return res
338