Skip to content

Commit ce2771a

Browse files
committed
Added docstring, examples, and tested
1 parent 52da66d commit ce2771a

2 files changed

Lines changed: 175 additions & 49 deletions

File tree

astroquery/astrometry_net/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,6 @@
2727
# Now import your public class
2828
# Should probably have the same name as your module
2929

30-
from .core import Astrometry, AstrometryClass
30+
from .core import AstrometryNet, AstrometryNetClass
3131

3232
__all__ = ['Astrometry', 'AstrometryClass']

astroquery/astrometry_net/core.py

Lines changed: 174 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,139 @@
1+
"""
2+
==============
3+
Astrometry.net
4+
==============
5+
Given an astropy `astropy.table.Table` as a source list, calculate the astrometric solution.
6+
Before querying astrometry.net you will have to register and obtain an api key from
7+
[http://nova.astrometry.net/].
8+
9+
Sample Use
10+
=========
11+
::
12+
13+
from astropy.table import Table
14+
from astroquery.astrometry_net import AstrometryNet
15+
16+
AstrometryNet.api_key = 'XXXXXXXXXXXXXXXX'
17+
18+
sources = Table.read('catalog.fits')
19+
20+
settings = {
21+
'scale_units': 'arcsecperpix',
22+
'scale_type': 'ul',
23+
'scale_lower': 0.20,
24+
'scale_upper': 0.30,
25+
'center_ra': 8.711675,
26+
'center_dec': -78.9810555556,
27+
'radius': 15,
28+
'parity': 0
29+
}
30+
31+
AstrometryNet.solve(sources, settings, 'X_IMAGE', 'Y_IMAGE', 'FWHM_IMAGE', 'FLAGS', 'FLUX_APER')
32+
33+
34+
Settings
35+
========
36+
In order to speed up the astrometric solution it is possible to pass a dictionary of settings
37+
to `astroquery.AstrometryNet.build_pacakge` or `astroquery.AstrometryNet.build_pacakge`.
38+
If no settings are passed to the build function then a set of default parameters will be
39+
used, although this will increase the time it takes astrometry.net to generate a solution.
40+
It is recommended to at least set the bounds of the pixel scale to a reasonable value.
41+
42+
Most of the following descriptions are taken directly from
43+
[astrometry.net](http://nova.astrometry.net/upload).
44+
45+
Scale
46+
-----
47+
It is important to set the pixel scale of the image as accurate as possible to increase the
48+
speed of astrometry.net. From astrometry.net: "Most digital-camera images are at least 10
49+
degrees wide; most professional-grade telescopes are narrower than 2 degrees."
50+
51+
Several parameters are available to set the pixel scale.
52+
53+
``scale_units``
54+
- Units used by pixel scale variables
55+
- Possible values:
56+
* ``arcsecperpix``: arcseconds per pixel
57+
* ``arcminwidth``: width of the field (in arcminutes)
58+
* ``degwidth``:width of the field (in degrees)
59+
* ``focalmm``: focal length of the lens (for 35mm film equivalent sensor)
60+
``scale_type``
61+
- Type of scale used
62+
- Possible values:
63+
* ``ul``: Set upper and lower bounds. If ``scale_type='ul'`` the parameters
64+
``scale_upper`` and ``scale_lower`` must be set to the upper and lower bounds
65+
of the pixel scale respectively
66+
* ``ev``: Set the estimated value with a given error. If ``scale_type='ev'`` the
67+
parameters ``scale_est`` and ``scale_err`` must be set to the estiimated value
68+
(in pix) and error (percentage) of the pixel scale.
69+
70+
Parity
71+
------
72+
Flipping an image reverses its "parity". If you point a digital camera at the sky and
73+
submit the JPEG, it probably has negative parity. If you have a FITS image, it probably
74+
has positive parity. Selecting the right parity will make the solving process run faster,
75+
but if in doubt just try both. ``parity`` can be set to the following values:
76+
- ``0``: positive parity
77+
- ``1``: negative parity
78+
- ``2``: try both
79+
80+
Star Positional Error
81+
---------------------
82+
When we find a matching "landmark", we check to see how many of the stars in your field
83+
match up with stars we know about. To do this, we need to know how much a star in your
84+
field could have moved from where it should be.
85+
86+
The unit for positional error is in pixels and is set by the key ``positional_error``.
87+
88+
Limits
89+
------
90+
In order to narrow down our search, you can supply a field center along with a radius.
91+
We will only search in indexes which fall within this area.
92+
93+
To set limits use all of the following parameters:
94+
``center_ra``: center RA of the field (in degrees)
95+
``center_dec``: center DEC of the field (in degrees)
96+
``radius``: how far the actual RA and DEC might be from the estimated values (in degrees)
97+
98+
Tweak
99+
-----
100+
By default we try to compute a SIP polynomial distortion correction with order 2.
101+
You can disable this by changing the order to 0, or change the polynomial order by setting
102+
``tweak_order``.
103+
104+
CRPIX Center
105+
------------
106+
By default the reference point (CRPIX) of the WCS we compute can be anywhere in your image,
107+
but often it's convenient to force it to be the center of the image. This can be set by choosing
108+
``crpix_center=True``.
109+
110+
License and Sharing
111+
-------------------
112+
The Astrometry.net [website](http://nova.astrometry.net/) allows users to upload images
113+
as well as catalogs to be used in generating an astrometric solution, so the site gives
114+
users the choice of licenses for their publically available images. Since the astroquery
115+
astrometry.net api is only uploading a source catalog the choice of ``public_visibility``,
116+
``allow_commercial_use``, and ``allow_modifications`` are not really relevant and can be left
117+
to their defaults, although their settings are described below
118+
119+
Visibility
120+
^^^^^^^^^^
121+
By default all catalogs uploaded are publically available. To change this use the setting
122+
``publicly_visible='n'``.
123+
124+
License
125+
^^^^^^^
126+
There are two parameters that describe setting a license:
127+
``allow_commercial_use``:
128+
Chooses whether or not an image uploaded to astrometry.net is licensed
129+
for commercial use. This can either be set to ``y``, ``n``, or ``d``, which
130+
uses the default license associated with the api key.
131+
``allow_modifications``:
132+
Whether or not images uploaded to astrometry.net are allowed to be modified by
133+
other users. This can either be set to ``y``, ``n``, or ``d``, which
134+
uses the default license associated with the api key.
135+
"""
136+
1137
# Licensed under a 3-clause BSD style license - see LICENSE.rst
2138
#from __future__ import print_function
3139

@@ -22,17 +158,18 @@
22158
import time
23159

24160
# export all the public classes and methods
25-
__all__ = ['Astrometry', 'AstrometryClass']
161+
__all__ = ['AstrometryNet', 'AstrometryNetClass']
26162

27163
# declare global variables and constants if any
28164

29165
# Now begin your main class
30166
# should be decorated with the async_to_sync imported previously
31167

168+
astrometry_net_url = 'http://nova.astrometry.net/'
32169
apiurl = 'http://nova.astrometry.net/api/'
33170

34171
@async_to_sync
35-
class AstrometryClass(BaseQuery):
172+
class AstrometryNetClass(BaseQuery):
36173

37174
"""
38175
Not all the methods below are necessary but these cover most of the common cases, new methods may be added if necessary, follow the guidelines at <http://astroquery.readthedocs.org/en/latest/api.html>
@@ -119,14 +256,14 @@ def build_request(self, catalog, settings={}, x_colname='x', y_colname='y'):
119256
# Upload the text file to request a WCS solution
120257
upload_url = apiurl + "upload"
121258
upload_args = {
259+
'publicly_visible': 'y',
122260
'allow_commercial_use': 'd',
123261
'allow_modifications': 'd',
124-
'publicly_visible': 'y',
125-
'scale_units': 'arcsecperpix',
262+
'scale_units': 'deg',
126263
'scale_type': 'ul',
127-
'scale_lower': 0.20, # arcsec/pix
128-
'scale_upper': 0.30, # arcsec/pix
129-
'parity': 0,
264+
'scale_lower': 0.1,
265+
'scale_upper': 180,
266+
'parity': 2,
130267
'session': session_string
131268
}
132269
upload_args.update(settings)
@@ -137,7 +274,6 @@ def build_request(self, catalog, settings={}, x_colname='x', y_colname='y'):
137274
# Ideally this could probably be re-written and improved
138275
#src_list = '\n'.join(['{0:.3f}\t{1:.3f}'.format(row[x_colname], row[y_colname])
139276
# for row in catalog])
140-
#print 'src_list:', src_list
141277

142278
temp_file = 'temp.cat'
143279
f = open(temp_file, 'w')
@@ -221,12 +357,9 @@ def submit(self, url, headers, data):
221357
from urllib2 import urlopen, Request
222358
import simplejson
223359

224-
print 'header:\n', headers
225-
print 'data:\n',data
226360
request = Request(url=url, headers=headers, data=data)
227361
f = urlopen(request)
228362
txt = f.read()
229-
print txt
230363
result = simplejson.loads(txt)
231364
stat = result.get('status')
232365
if stat == 'error':
@@ -245,12 +378,12 @@ def get_submit_status(self, subid, timeout=30):
245378
subid: str
246379
- subid returned by ``submit`` function
247380
timeout: int (optional):
248-
Maximum time to wait for a submission status
381+
- Maximum time to wait for a submission status
249382
250383
Result
251384
------
252-
jobid: str
253-
jobid astrometry.net has assigned to the submitted list
385+
jobs: str
386+
jobid's astrometry.net has assigned to the submitted list
254387
"""
255388
import math
256389
from urllib2 import urlopen, Request
@@ -259,8 +392,6 @@ def get_submit_status(self, subid, timeout=30):
259392
# Check submission status
260393
subcheck_url = apiurl + "submissions/" + str(subid)
261394

262-
print('subcheckurl', subcheck_url)
263-
264395
request = Request(url=subcheck_url)
265396
still_processing = True
266397
n_failed_attempts = 0
@@ -271,31 +402,32 @@ def get_submit_status(self, subid, timeout=30):
271402
txt = f.read()
272403
result = simplejson.loads(txt)
273404
if result['jobs'][0] is None:
274-
print('result is none')
275405
raise Exception()
276-
# print result
277406
still_processing = False
278407
except:
279-
print("Submission doesn't exist yet, sleeping for 5s.")
408+
log.info("Submission doesn't exist yet, sleeping for 5s.")
280409
time.sleep(5)
281410
n_failed_attempts += 1
282411
if n_failed_attempts > max_attempts:
283-
raise Exception("The submitted job has apparently timed out, exiting.")
412+
raise Exception(
413+
"The submitted job {0} has apparently timed out, exiting.".format(subid))
284414

285415
return result['jobs']
286416

287-
def get_wcs_file(self, jobs, timeout=90):
417+
def get_wcs_file(self, subid, jobs, timeout=90, timeout_error=True):
288418
"""
289419
Get a wcs file from astrometry.net given a jobid
290420
291421
Parameters
292422
----------
423+
subid: str
424+
- subid returned by ``submit`` function
293425
jobs: list
294426
List of jobs returned by `get_submit_status`
295427
timeout: int (optional):
296428
Maximum time to wait for an anstrometric solution before timing out
297-
298-
429+
timeout_error: bool (optional)
430+
Whether or not to raise an Exception if the request times out (default is False)
299431
Returns
300432
-------
301433
header: `astropy.io.fits.Header`
@@ -305,8 +437,6 @@ def get_wcs_file(self, jobs, timeout=90):
305437
from urllib2 import urlopen, Request
306438
import simplejson
307439

308-
print 'jobs', jobs
309-
310440
# Attempt to load wcs from astrometry.net
311441
n_jobs = len(jobs)
312442
still_processing = True
@@ -317,26 +447,27 @@ def get_wcs_file(self, jobs, timeout=90):
317447
time.sleep(5)
318448
for job_id in jobs:
319449
jobcheck_url = apiurl + "jobs/" + str(job_id)
320-
print(jobcheck_url)
321450
request = Request(url=jobcheck_url)
322451
f = urlopen(request)
323452
txt = f.read()
324453
result = simplejson.loads(txt)
325-
print("Checking astrometry.net job ID", job_id, result)
454+
log.info("Checking astrometry.net for job ID {0}".format(job_id))
326455
if result["status"] == "failure":
327-
print('failed')
456+
status_url = astrometry_net_url+"status/"+str(subid)
457+
log.error('job {0} failed. See {1} for details'.format(job_id, status_url))
328458
n_failed_jobs += 1
329459
jobs.remove(job_id)
330460
if result["status"] == "success":
331-
print('success')
461+
log.info('Job {0} executed successfully'.format(job_id))
332462
solved_job_id = job_id
333463
still_processing = False
334-
print(job_id, "SOLVED")
335464
n_failed_attempts += 1
336465

337466
if still_processing == True:
338-
raise Exception("Astrometry.net took too long to process, so we're exiting. " +
339-
"Try checking astrometry.net again later")
467+
log.error("Astrometry.net took too long to process job {0}, so we're exiting. " +
468+
"Try checking astrometry.net again later".format(jobs))
469+
if timeout_error:
470+
raise Exception("Astrometry.net took too long to process")
340471

341472
if still_processing == False:
342473
import wget
@@ -390,14 +521,14 @@ def get_wcs_file(self, jobs, timeout=90):
390521

391522

392523
wcs_image = wcs_filename
393-
wcs_hdu = pyfits.open(wcs_image)
524+
wcs_hdu = fits.open(wcs_image)
394525
wcs_header = wcs_hdu[0].header.copy()
395526
wcs_hdu.close()
396527

397528
return wcs_header
398529

399530
def solve(self, sources, settings, x_colname="x", y_colname="y", fwhm_colname=None,
400-
flag_colname=None, flux_colname=None, fwhm_std_cut=1, timeout=30):
531+
flag_colname=None, flux_colname=None, fwhm_std_cut=2, timeout=30):
401532
"""
402533
First draft of function to send a catalog or image to astrometry.net and
403534
return the astrometric solution.
@@ -456,34 +587,29 @@ def solve(self, sources, settings, x_colname="x", y_colname="y", fwhm_colname=No
456587
fwhm_upper = fwhm_med+fwhm_std_cut * fwhm_std
457588
catalog = catalog[(catalog[fwhm_colname]<fwhm_upper) &
458589
(catalog[fwhm_colname]>fwhm_lower)]
459-
#catalog = catalog.group_by(flux_colname)
460590
catalog.sort(flux_colname)
461591
catalog.reverse()
462592
# Only choose the top 50 sources
463593
if len(catalog)>50:
464594
catalog = catalog[:50]
465595

466-
print 'catalog', catalog
467-
468596
# Create a list of coordinates in a format that astrometry.net recognizes
469597
upload_kwargs = self.build_request(catalog, settings, x_colname, y_colname)
470-
471-
print upload_kwargs['data']
472-
598+
# Submit the job
599+
log.info('Submitting source list to astrometry.net')
473600
subid = self.submit(**upload_kwargs)
474-
475-
time.sleep(5)
476-
601+
log.info('Submission ID={0}'.format(subid))
602+
time.sleep(15)
603+
# Check that submission was successful
477604
jobs = self.get_submit_status(subid, 30)
478-
479-
time.sleep(5)
480-
481-
wcs_fits = self.get_wcs_file(jobs, timeout)
605+
log.info('jobs generated by submission: {0}'.format(jobs))
606+
# Get the header
607+
wcs_fits = self.get_wcs_file(subid, jobs, timeout, True)
482608

483609
return result
484610

485611
# the default tool for users to interact with is an instance of the Class
486-
Astrometry = AstrometryClass()
612+
AstrometryNet = AstrometryNetClass()
487613

488614
# once your class is done, tests should be written
489615
# See ./tests for examples on this

0 commit comments

Comments
 (0)