import logging
import subprocess
from os import path, remove, rename
import tempfile
from textwrap import dedent
__all__ = ['call_astrometry', 'add_astrometry']
logger = logging.getLogger(__name__)
[docs]def call_astrometry(filename, sextractor=False,
custom_sextractor_config=False, feder_settings=True,
no_plots=True, minimal_output=True,
save_wcs=False, verify=None,
ra_dec=None, overwrite=False,
wcs_reference_image_center=True,
odds_ratio=None,
astrometry_config=None,
additional_args=None):
"""
Wrapper around astrometry.net solve-field.
Parameters
----------
sextractor : bool or str, optional
``True`` to use `sextractor`, or a ``str`` with the
path to sextractor.
custom_sextractor_config : bool, optional
If ``True``, use a sexractor configuration file customized for Feder
images.
feder_settings : bool, optional
Set True if you want to use plate scale appropriate for Feder
Observatory Apogee Alta U9 camera.
no_plots : bool, optional
``True`` to suppress astrometry.net generation of
plots (pngs showing object location and more)
minimal_output : bool, optional
If ``True``, suppress, as separate files, output of: WCS
header, RA/Dec object list, matching objects list, but see
also `save_wcs`
save_wcs : bool, optional
If ``True``, save WCS header even if other output is suppressed
with `minimial_output`
verify : str, optional
Name of a WCS header to be used as a first guess
for the astrometry fit; if this plate solution does not work
the solution is found as though `verify` had not been specified.
ra_dec : list or tuple of float
(RA, Dec); also limits search radius to 1 degree.
overwrite : bool, optional
If ``True``, perform astrometry even if astrometry.net files from a
previous run are present.
wcs_reference_image_center :
If ``True``, force the WCS reference point in the image to be the
image center.
odds_ratio : float, optional
The odds ratio to use for a successful solve. Default is to use the
default in `solve-field`.
astrometry_config : str, optional
Name of configuration file to use for SExtractor.
additional_args : str or list of str, optional
Additional arguments to pass to `solve-field`
"""
solve_field = ["solve-field"]
option_list = []
option_list.append("--obj 100")
if feder_settings:
option_list.append(
"--scale-low 0.5 --scale-high 0.6 --scale-units arcsecperpix")
if additional_args is not None:
if isinstance(additional_args, str):
add_ons = [additional_args]
else:
add_ons = additional_args
option_list.extend(add_ons)
if isinstance(sextractor, str):
option_list.append("--sextractor-path " + sextractor)
elif sextractor:
option_list.append("--use-sextractor")
if no_plots:
option_list.append("--no-plot")
if minimal_output:
option_list.append("--corr none --rdls none --match none")
if not save_wcs:
option_list.append("--wcs none")
if ra_dec is not None:
option_list.append("--ra %s --dec %s --radius 0.5" % ra_dec)
if overwrite:
option_list.append("--overwrite")
if wcs_reference_image_center:
option_list.append("--crpix-center")
options = " ".join(option_list)
solve_field.extend(options.split())
if custom_sextractor_config:
tmp_location = tempfile.mkdtemp()
param_location = path.join(tmp_location, 'default.param')
config_location = path.join(tmp_location, 'feder.config')
config_contents = SExtractor_config.format(param_file=param_location)
with open(config_location, 'w') as f:
f.write(config_contents)
with open(param_location, 'w') as f:
contents = """
X_IMAGE
Y_IMAGE
MAG_AUTO
FLUX_AUTO
"""
f.write(dedent(contents))
additional_solve_args = [
'--sextractor-config', config_location,
'--x-column', 'X_IMAGE',
'--y-column', 'Y_IMAGE',
'--sort-column', 'MAG_AUTO',
'--sort-ascending'
]
solve_field.extend(additional_solve_args)
if odds_ratio is not None:
solve_field.append('--odds-to-solve')
solve_field.append(odds_ratio)
if astrometry_config is not None:
solve_field.append('--config')
solve_field.append(astrometry_config)
# kludge to handle case when path of verify file contains a space--split
# above does not work for that case.
if verify is not None:
if verify:
solve_field.append("--verify")
solve_field.append("%s" % verify)
else:
solve_field.append("--no-verify")
solve_field.extend([filename])
print(' '.join(solve_field))
logger.info(' '.join(solve_field))
try:
solve_field_output = subprocess.check_output(solve_field,
stderr=subprocess.STDOUT)
return_status = 0
log_level = logging.DEBUG
except subprocess.CalledProcessError as e:
return_status = e.returncode
solve_field_output = 'Output from astrometry.net:\n' + str(e.output)
log_level = logging.WARN
logger.warning('Adding astrometry failed for %s', filename)
raise e
logger.log(log_level, solve_field_output)
return return_status
[docs]def add_astrometry(filename, overwrite=False, ra_dec=None,
note_failure=False, save_wcs=False,
verify=None, try_builtin_source_finder=False,
custom_sextractor=False,
odds_ratio=None,
astrometry_config=None,
camera='',
avoid_pyfits=False):
"""Add WCS headers to FITS file using astrometry.net
Parameters
----------
overwrite : bool, optional
Set ``True`` to overwrite the original file. If `False`,
the file astrometry.net generates is kept.
ra_dec : list or tuple of float or str
(RA, Dec) of field center as either decimal or sexagesimal; also
limits search radius to 1 degree.
note_failure : bool, optional
If ``True``, create a file with extension "failed" if astrometry.net
fails. The "failed" file contains the error messages genreated by
astrometry.net.
try_builtin_source_finder : bool
If true, try using astrometry.net's built-in source extractor if
sextractor fails.
save_wcs :
verify :
See :func:`call_astrometry`
camera : str, one of ['celestron', 'u9', 'cp16'], optional
Name of camera; determines the pixel scale used in the solved. Default
is to use `'u9'`.
avoid_pyfits : bool
Add arguments to solve-field to avoid calls to pyfits.BinTableHDU.
See https://groups.google.com/forum/#!topic/astrometry/AT21x6zVAJo
Returns
-------
bool
``True`` on success.
Notes
-----
Tries a couple strategies before giving up: first sextractor,
then, if that fails, astrometry.net's built-in source extractor.
It also cleans up after astrometry.net, keeping only the new FITS
file it generates, the .solved file, and, if desired, a ".failed" file
for fields which it fails to solve.
For more flexible invocation of astrometry.net, see :func:`call_astrometry`
"""
base, ext = path.splitext(filename)
# All are in arcsec per pixel, values are approximate
camera_pixel_scales = {
'celestron': 0.3,
'u9': 0.55,
'cp16': 0.55
}
if camera:
use_feder = False
scale = camera_pixel_scales[camera]
scale_options = ("--scale-low {low} --scale-high {high} "
"--scale-units arcsecperpix".format(low=0.8*scale, high=1.2 * scale))
else:
use_feder = True
scale_options = ''
if avoid_pyfits:
pyfits_options = '--no-remove-lines --uniformize 0'
else:
pyfits_options = ''
additional_opts = ' '.join([scale_options,
pyfits_options])
logger.info('BEGIN ADDING ASTROMETRY on {0}'.format(filename))
try:
logger.debug('About to call call_astrometry')
solved_field = (call_astrometry(filename,
sextractor=True,
ra_dec=ra_dec,
save_wcs=save_wcs, verify=verify,
custom_sextractor_config=custom_sextractor,
odds_ratio=odds_ratio,
astrometry_config=astrometry_config,
feder_settings=use_feder,
additional_args=additional_opts)
== 0)
except subprocess.CalledProcessError as e:
logger.debug('Failed with error')
failed_details = e.output
solved_field = False
if (not solved_field) and try_builtin_source_finder:
log_msg = 'Astrometry failed using sextractor, trying built-in '
log_msg += 'source finder'
logger.info(log_msg)
try:
solved_field = (call_astrometry(filename, ra_dec=ra_dec,
overwrite=True,
save_wcs=save_wcs, verify=verify)
== 0)
except subprocess.CalledProcessError as e:
failed_details = e.output
solved_field = False
if solved_field:
logger.info('Adding astrometry succeeded')
else:
logger.warning('Adding astrometry failed for file %s', filename)
if overwrite and solved_field:
logger.info('Overwriting original file with image with astrometry')
try:
rename(base + '.new', filename)
except OSError as e:
logger.error(e)
return False
# whether we succeeded or failed, clean up
try:
remove(base + '.axy')
except OSError:
pass
if solved_field:
try:
remove(base + '-indx.xyls')
remove(base + '.solved')
except OSError:
pass
if note_failure and not solved_field:
try:
f = open(base + '.failed', 'wb')
f.write(failed_details)
f.close()
except IOError as e:
logger.error('Unable to save output of astrometry.net %s', e)
pass
logger.info('END ADDING ASTROMETRY for %s', filename)
return solved_field
SExtractor_config = """
# Configuration file for SExtractor 2.19.5 based on default by EB 2014-11-26
#
# modification was to change DETECT_MINAREA and turn of filter convolution
#-------------------------------- Catalog ------------------------------------
PARAMETERS_NAME {param_file} # name of the file containing catalog contents
#------------------------------- Extraction ----------------------------------
DETECT_TYPE CCD # CCD (linear) or PHOTO (with gamma correction)
DETECT_MINAREA 15 # min. # of pixels above threshold
DETECT_THRESH 1.5 # <sigmas> or <threshold>,<ZP> in mag.arcsec-2
ANALYSIS_THRESH 1.5 # <sigmas> or <threshold>,<ZP> in mag.arcsec-2
FILTER N # apply filter for detection (Y or N)?
FILTER_NAME default.conv # name of the file containing the filter
DEBLEND_NTHRESH 32 # Number of deblending sub-thresholds
DEBLEND_MINCONT 0.005 # Minimum contrast parameter for deblending
CLEAN Y # Clean spurious detections? (Y or N)?
CLEAN_PARAM 1.0 # Cleaning efficiency
MASK_TYPE CORRECT # type of detection MASKing: can be one of
# NONE, BLANK or CORRECT
#------------------------------ Photometry -----------------------------------
PHOT_APERTURES 10 # MAG_APER aperture diameter(s) in pixels
PHOT_AUTOPARAMS 2.5, 3.5 # MAG_AUTO parameters: <Kron_fact>,<min_radius>
PHOT_PETROPARAMS 2.0, 3.5 # MAG_PETRO parameters: <Petrosian_fact>,
# <min_radius>
SATUR_LEVEL 50000.0 # level (in ADUs) at which arises saturation
SATUR_KEY SATURATE # keyword for saturation level (in ADUs)
MAG_ZEROPOINT 0.0 # magnitude zero-point
MAG_GAMMA 4.0 # gamma of emulsion (for photographic scans)
GAIN 0.0 # detector gain in e-/ADU
GAIN_KEY GAIN # keyword for detector gain in e-/ADU
PIXEL_SCALE 1.0 # size of pixel in arcsec (0=use FITS WCS info)
#------------------------- Star/Galaxy Separation ----------------------------
SEEING_FWHM 1.2 # stellar FWHM in arcsec
STARNNW_NAME default.nnw # Neural-Network_Weight table filename
#------------------------------ Background -----------------------------------
BACK_SIZE 64 # Background mesh: <size> or <width>,<height>
BACK_FILTERSIZE 3 # Background filter: <size> or <width>,<height>
BACKPHOTO_TYPE GLOBAL # can be GLOBAL or LOCAL
#------------------------------ Check Image ----------------------------------
CHECKIMAGE_TYPE NONE # can be NONE, BACKGROUND, BACKGROUND_RMS,
# MINIBACKGROUND, MINIBACK_RMS, -BACKGROUND,
# FILTERED, OBJECTS, -OBJECTS, SEGMENTATION,
# or APERTURES
CHECKIMAGE_NAME check.fits # Filename for the check-image
#--------------------- Memory (change with caution!) -------------------------
MEMORY_OBJSTACK 3000 # number of objects in stack
MEMORY_PIXSTACK 300000 # number of pixels in stack
MEMORY_BUFSIZE 1024 # number of lines in buffer
#----------------------------- Miscellaneous ---------------------------------
VERBOSE_TYPE NORMAL # can be QUIET, NORMAL or FULL
HEADER_SUFFIX .head # Filename extension for additional headers
WRITE_XML N # Write XML file (Y/N)?
XML_NAME sex.xml # Filename for XML output
"""