Package gavo :: Package helpers :: Module anet
[frames] | no frames]

Source Code for Module gavo.helpers.anet

  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  #c Copyright 2008-2019, the GAVO project 
 61  #c 
 62  #c This program is free software, covered by the GNU GPL.  See the 
 63  #c COPYING file in the source distribution. 
 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   
101 -class Error(base.Error):
102 pass
103
104 -class NotSolved(Error):
105 pass
106
107 -class ObjectNotFound(Error):
108 pass
109
110 -class ShellCommandFailed(Error):
111 - def __init__(self, msg, retcode):
112 Error.__init__(self, msg) 113 self.msg, self.retcode = msg, retcode
114
115 - def __str__(self):
116 return "External program failure (%s). Program output: %s"%( 117 self.retcode, self.msg)
118 119
120 -def _feedFile(targDir, fName, **ignored):
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
135 -def _runShellCommand(cmd, args):
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
149 -def _copyCurrentTree(copyTo):
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 # Minimal configuration for sextractor for anet use. 159 # Do not override values in here in your sexConfigs 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 # export column spec for sextractor; referenced in _ANET_SEX_STUB 170 _ANET_SEX_PARAM = """X_IMAGE 171 Y_IMAGE 172 MAG_ISO 173 FLUX_AUTO 174 ELONGATION 175 """ 176 177 # Blatantly stolen from anet... 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
188 -def _createSextractorFiles(sexControl):
189 with open("anet.control", "w") as f: 190 f.write(_ANET_SEX_STUB+sexControl) 191 with open("anet.param", "w") as f: 192 f.write(_ANET_SEX_PARAM) 193 with open("anet.filter", "w") as f: 194 f.write(_ANET_SEX_FILTER)
195 196
197 -def _extractSex(filterFunc=None):
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
218 -def _addArgsFor(actPars, args):
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") # leaks into tmp as of 0.36 otherwise 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 # staggered solving: This seemed like a good idea at some point but 274 # now probably no longer is. 275 # minInd, maxInd = int(actPars["startob"]), int(actPars["endob"]) 276 # curStart = minInd 277 # while True: 278 # if curStart+10>maxInd: 279 # break 280 # args.extend(["--depth", "%s-%s"%(curStart+1, curStart+15), "img.fits"]) 281 # _runShellCommand(solverBin, args) 282 # if os.path.exists("img.solved"): 283 # break 284 # curStart += 5 285 # args[-3:] = [] 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
314 -def _retrieveWCS(srcDir, fName, **ignored):
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