Skip to content

Commit

Permalink
astrometry: added new solve-field command class
Browse files Browse the repository at this point in the history
  • Loading branch information
juliotux committed May 12, 2024
1 parent 5cd821e commit 01d8f53
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 180 deletions.
66 changes: 36 additions & 30 deletions astropop/astrometry/astrometrynet.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
improvements.
"""

from multiprocessing import Value
import os
import shutil
from subprocess import CalledProcessError
import copy
from packaging import version
from tempfile import NamedTemporaryFile, mkdtemp
import warnings

Expand All @@ -27,11 +29,37 @@

__all__ = ['AstrometrySolver', 'solve_astrometry_xy', 'solve_astrometry_image',
'solve_astrometry_framedata', 'create_xyls',
'AstrometryNetUnsolvedField']
'AstrometryNetUnsolvedField', 'SolveFieldCommand']


_solve_field = shutil.which('solve-field')


class SolveFieldCommand:
"""Astrometry.net solve field check and run command."""
_version = None

def __init__(self, command=_solve_field):
if isinstance(command, SolveFieldCommand):
self._version = command._version
command = command._command
elif command is None or not os.path.exists(command):
raise FileNotFoundError('solve-field command not found.')
self._command = command

@property
def version(self):
"""Return the version of the solve-field command."""
if self._version is None:
_, sout, _ = run_command([self._command, '--version'])
self._version = version.parse(sout[0])
return self._version

def run(self, *args, **kwargs):
"""Run the solve-field command."""
return run_command([self._command, *args], **kwargs)


_center_help = 'only search in indexes within `radius` of the field center ' \
'given by `ra` and `dec`'
solve_field_params = {
Expand Down Expand Up @@ -222,35 +250,13 @@
'To astrometry.cfg file. If no scale estimate is given, use these '
'limits on field width in deg.'
),
'inparallel': (
'<bool>',
'To astrometry.cfg file. Check indexes in parallel. Only enable it if '
'you have memory to store all indexes.'
),
'index': (
'<string or list(string)>',
'To astrometry.cfg file. Explicitly list the indices to load.'
' Disables ``autoindex``'
),
'autoindex': (
'<bool>',
'To astrometry.cfg file. Load any indices found in the directories '
'listed.'
),
'add_path': (
'index-dir': (
'<string or list(string)>',
'To astrometry.cfg file. Add a location of astrometry.net index files.'
'Add a location of astrometry.net index files.'
' ``astrometry-engine`` will search index files in all listed folders.'
' Additive, do not override defaults.'
),
'depths': (
'<list(int)>',
'To astrometry.cfg file. If no depths are given, use these.'
)
}
# These are the parameters that can be set in the astrometry.cfg file
_conf_file = ['inparallel', 'minwidth', 'maxwidth', 'depths',
'add_path', 'autoindex', 'index']


def get_options_help():
Expand Down Expand Up @@ -473,17 +479,17 @@ class AstrometrySolver():
Explicitly list the indices to load.
"""

def __init__(self, solve_field=None, config=None, config_file=None,
defaults=None, keep_files=False):
def __init__(self, solve_field=_solve_field, defaults=None,
keep_files=False):
# declare the defaults here to be safer
self._defaults = {'no-plots': None, 'overwrite': None}
if defaults is None:
defaults = {}
self._defaults.update(defaults)

self.config = self._read_config(config_file, config)

self._command = solve_field or _solve_field
self._command = SolveFieldCommand(solve_field)
if self._command.version < version.parse('0.95'):
raise ValueError('Astrometry.net version must be at least 0.95.')
self._keep = keep_files

def solve_field(self, filename, options=None, output_dir=None, **kwargs):
Expand Down
3 changes: 1 addition & 2 deletions astropop/py_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,8 @@ def proccess_out(line, std_l, loglevel):
std_l.append(line)
logger.log(loglevel, line)

# TODO: when deprecate py37, use shlex.join(args)
proc = await asyncio.create_subprocess_shell(
' '.join(shlex.quote(arg) for arg in args),
shlex.join(args),
limit=2**23, # 8 MB
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
Expand Down
181 changes: 33 additions & 148 deletions tests/test_astrometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import numpy as np
import os
from packaging.version import Version
from astroquery.skyview import SkyView
from astropy.coordinates import Angle, SkyCoord
from astropy.config import get_cache_dir
Expand All @@ -15,12 +16,13 @@
from astropy import units
from astropy.utils.data import download_file

from astropop.astrometry.astrometrynet import _solve_field, \
solve_astrometry_image, \
from astropop.astrometry.astrometrynet import solve_astrometry_image, \
solve_astrometry_xy, \
solve_astrometry_hdu, \
solve_astrometry_framedata, \
AstrometrySolver
AstrometrySolver, \
SolveFieldCommand, \
_solve_field
from astropop.astrometry.astrometrynet import _parse_angle, \
_parse_coordinates, \
_parse_crpix, \
Expand All @@ -45,7 +47,24 @@ def compare_wcs(wcs, nwcs):
"False)")


@pytest.mark.remote_data
class Test_SolveFieldCommand:
def test_empty_error(self):
with pytest.raises(FileNotFoundError,
match='solve-field command not found.'):
SolveFieldCommand(command=None)

def test_not_exists_error(self):
with pytest.raises(FileNotFoundError,
match='solve-field command not found.'):
SolveFieldCommand(command='not_exists')

def test_version(self):
s = SolveFieldCommand()
v = s.version
assert_is_instance(v, Version)


#@pytest.mark.remote_data
class Test_AstrometrySolver:
def get_image(self):
# return image name and index name
Expand Down Expand Up @@ -186,150 +205,16 @@ def test_parse_crpix_fails(self):
assert_equal(_parse_crpix({}), [])

@skip_astrometry
def test_read_cfg(self):
a = AstrometrySolver() # read the default configuration
cfg = a._read_config()
assert_not_in('index', cfg)
assert_false(cfg['inparallel'])
assert_not_in('', cfg)
assert_not_in('minwidth', cfg)
assert_not_in('maxwidth', cfg)
assert_equal(cfg['cpulimit'], '300')
assert_true(cfg['autoindex'])
assert_equal(len(cfg['add_path']), 1)

@skip_astrometry
def test_read_cfg_fname(self, tmpdir):
fname = tmpdir / 'test.cfg'
f = open(fname, 'w')
f.write("inparallel\n")
f.write(" minwidth 0.1 \n")
f.write(" maxwidth 180\n")
f.write("depths 10 20 30 40 50 60\n")
f.write("cpulimit 300\n")
f.write("\n\n")
f.write("# comment\n")
f.write("add_path /data # commented path\n")
f.write("add_path /data1\n")
f.write("#add_path /data2\n") # data2 will not be present
f.write("autoindex\n")
f.write("index index-219\n")
f.write("index index-220\n")
f.write("index index-221\n")
f.write("# index index-222\n") # 222 will not be present
f.close()

a = AstrometrySolver()
cfg = a._read_config(fname)
assert_not_equal("", cfg)
assert_true(cfg['inparallel'])
assert_equal(cfg['minwidth'], '0.1')
assert_equal(cfg['maxwidth'], '180')
assert_equal(cfg['depths'], [10, 20, 30, 40, 50, 60])
assert_equal(cfg['cpulimit'], '300')
assert_equal(cfg['add_path'], ['/data', '/data1'])
assert_equal(cfg['index'], ['index-219', 'index-220', 'index-221'])
assert_true(cfg['autoindex'])

@skip_astrometry
def test_read_cfg_with_options(self, tmpdir):
fname = tmpdir / 'test.cfg'
f = open(fname, 'w')
f.write("inparallel\n")
f.write(" minwidth 0.1 \n")
f.write(" maxwidth 180\n")
f.write("depths 10 20 30 40 50 60\n")
f.write("cpulimit 300\n")
f.write("\n\n")
f.write("# comment\n")
f.write("add_path /data # commented path\n")
f.write("add_path /data1\n")
f.write("#add_path /data2\n") # data2 will not be present
f.write("autoindex\n")
f.write("index index-219\n")
f.write("index index-220\n")
f.write("index index-221\n")
f.write("# index index-222\n") # 222 will not be present
f.close()

a = AstrometrySolver()
cfg = a._read_config(fname, {'depths': [10, 30, 50],
'add_path': '/data3',
'index': ['indx4', 'indx5']})
assert_not_equal("", cfg)
assert_true(cfg['inparallel'])
assert_equal(cfg['minwidth'], '0.1')
assert_equal(cfg['maxwidth'], '180')
assert_equal(cfg['depths'], [10, 30, 50])
assert_equal(cfg['cpulimit'], '300')
assert_equal(cfg['add_path'], ['/data', '/data1', '/data3'])
assert_equal(cfg['index'], ['index-219', 'index-220', 'index-221',
'indx4', 'indx5'])
assert_true(cfg['autoindex'])

@skip_astrometry
def test_write_config(self, tmpdir):
fname = tmpdir / 'test.cfg'

a = AstrometrySolver()
a.config = {'inparallel': False,
'autoindex': True,
'cpulimit': 300,
'minwidth': 0.1,
'maxwidth': 180,
'depths': [20, 40, 60],
'index': ['011', '012'],
'add_path': ['/path1', '/path2']}
a._write_config(fname)

with open(fname, 'r') as f:
for line in f.readlines():
assert_in(line.strip('\n'), ['autoindex', 'inparallel',
'cpulimit 300', 'minwidth 0.1',
'maxwidth 180', 'depths 20 40 60',
'index 011', 'index 012',
'add_path /path1',
'add_path /path2'])

@skip_astrometry
def test_pop_config(self):
a = AstrometrySolver()
options1 = {'inparallel': False,
'autoindex': True,
'cpulimit': 300,
'minwidth': 0.1,
'maxwidth': 180,
'depths': [20, 40, 60],
'index': ['011', '012'],
'add_path': ['/path1', '/path2'],
'ra': 0.0, 'dec': 0.0, 'radius': 1.0}
options, cfg = a._pop_config(options1)
assert_is_not(options1, options)
assert_equal(options['ra'], 0.0)
assert_equal(options['dec'], 0.0)
assert_equal(options['radius'], 1.0)
assert_equal(options['cpulimit'], 300)
assert_equal(cfg['inparallel'], False)
assert_equal(cfg['autoindex'], True)
assert_equal(cfg['minwidth'], 0.1)
assert_equal(cfg['maxwidth'], 180)
assert_equal(cfg['depths'], [20, 40, 60])
assert_equal(cfg['index'], ['011', '012'])
assert_equal(cfg['add_path'], ['/path1', '/path2'])

@skip_astrometry
def test_only_write_config_when_needed(self, tmpdir):
a = AstrometrySolver()
args = a._get_args(tmpdir/'1/', tmpdir/'1/fitsfile.fits',
{'ra': 0.0, 'dec': 0.0, 'radius': 1.0},
output_dir=tmpdir, correspond='test.correspond')
assert_not_in('--config', args)

args = a._get_args(tmpdir/'2/', tmpdir/'2/fitsfile.fits',
{'ra': 0.0, 'dec': 0.0, 'radius': 1.0,
'inparallel': False},
output_dir=tmpdir, correspond='test.correspond')
assert_in('--config', args)
def test_solve_field_version(self):
com = SolveFieldCommand()
com._version = Version('0.95')
# 0.95, no error
AstrometrySolver(solve_field=com)
com._version = Version('0.70')
# 0.70, error
with pytest.raises(ValueError,
match='Astrometry.net version must be at least 0.95.'):
AstrometrySolver(solve_field=com)

@skip_astrometry
def test_solve_astrometry_hdu(self, tmpdir):
Expand Down

0 comments on commit 01d8f53

Please sign in to comment.