Skip to content

Commit 0a6e016

Browse files
volodymyrssburnout87
authored andcommitted
Adding DESILegacySurvey module
1 parent 729fd4d commit 0a6e016

File tree

11 files changed

+436
-1
lines changed

11 files changed

+436
-1
lines changed

astroquery/desi/__init__.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Licensed under a 3-clause BSD style license - see LICENSE.rst
2+
3+
"""
4+
DESI LegacySurvery
5+
6+
https://www.legacysurvey.org/
7+
-------------------------
8+
9+
:author: Gabriele Barni ([email protected])
10+
"""
11+
12+
# Make the URL of the server, timeout and other items configurable
13+
# See <http://docs.astropy.org/en/latest/config/index.html#developer-usage>
14+
# for docs and examples on how to do this
15+
# Below is a common use case
16+
from astropy import config as _config
17+
18+
19+
class Conf(_config.ConfigNamespace):
20+
"""
21+
Configuration parameters for `astroquery.desi`.
22+
"""
23+
server = _config.ConfigItem(
24+
['https://portal.nersc.gov/cfs/cosmo/data/legacysurvey/',
25+
],
26+
'base url')
27+
28+
timeout = _config.ConfigItem(
29+
30,
30+
'Time limit for connecting to template_module server.')
31+
32+
33+
conf = Conf()
34+
35+
# Now import your public class
36+
# Should probably have the same name as your module
37+
from .core import DESILegacySurvey, DESILegacySurveyClass
38+
39+
__all__ = ['DESILegacySurvey', 'DESILegacySurveyClass',
40+
'Conf', 'conf',
41+
]

astroquery/desi/core.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Licensed under a 3-clause BSD style license - see LICENSE.rst
2+
import urllib.error
3+
import requests
4+
5+
import pyvo as vo
6+
import numpy as np
7+
8+
from astroquery.exceptions import NoResultsWarning
9+
from astroquery.query import BaseQuery
10+
from astroquery.utils import commons, async_to_sync
11+
12+
from . import conf
13+
14+
15+
__all__ = ['DESILegacySurvey', 'DESILegacySurveyClass']
16+
17+
18+
@async_to_sync
19+
class DESILegacySurveyClass(BaseQuery):
20+
URL = conf.server
21+
TIMEOUT = conf.timeout
22+
23+
def query_region(self, coordinates, radius, data_release=9):
24+
"""
25+
Queries a region around the specified coordinates.
26+
27+
Parameters
28+
----------
29+
coordinates : str or `astropy.coordinates`.
30+
coordinates around which to query
31+
radius : str or `astropy.units.Quantity`.
32+
the radius of the cone search
33+
34+
Returns
35+
-------
36+
response : `astropy.table.Table`
37+
"""
38+
39+
url = 'https://datalab.noirlab.edu/tap'
40+
tap_service = vo.dal.TAPService(url)
41+
qstr = "SELECT all * FROM ls_dr" + str(data_release) + ".tractor WHERE dec>" + str(coordinates.dec.deg - radius.deg) + " and dec<" + str(
42+
coordinates.dec.deg + radius.deg) + " and ra>" + str(coordinates.ra.deg - radius.deg / np.cos(coordinates.dec.deg * np.pi / 180.)) + " and ra<" + str(
43+
coordinates.ra.deg + radius.deg / np.cos(coordinates.dec.deg * np.pi / 180))
44+
45+
tap_result = tap_service.run_sync(qstr)
46+
tap_result = tap_result.to_table()
47+
# filter out duplicated lines from the table
48+
mask = tap_result['type'] != 'D'
49+
filtered_table = tap_result[mask]
50+
51+
return filtered_table
52+
53+
def get_images(self, position, data_release=9, pixels=None, radius=None, show_progress=True, image_band='g'):
54+
"""
55+
Returns
56+
-------
57+
A list of `astropy.io.fits.HDUList` objects.
58+
"""
59+
60+
image_size_arcsec = radius.arcsec
61+
pixsize = 2 * image_size_arcsec / pixels
62+
63+
image_url = 'https://www.legacysurvey.org/viewer/fits-cutout?ra=' + str(position.ra.deg) + '&dec=' + str(position.dec.deg) + '&size=' + str(
64+
pixels) + '&layer=ls-dr' + str(data_release) + '&pixscale=' + str(pixsize) + '&bands=' + image_band
65+
66+
file_container = commons.FileContainer(image_url, encoding='binary', show_progress=show_progress)
67+
68+
try:
69+
fits_file = file_container.get_fits()
70+
except (requests.exceptions.HTTPError, urllib.error.HTTPError) as e:
71+
# TODO not sure this is the most suitable exception
72+
raise NoResultsWarning(f"{str(e)} - Problem retrieving the file at the url: {str(image_url)}")
73+
74+
return [fits_file]
75+
76+
77+
DESILegacySurvey = DESILegacySurveyClass()

astroquery/desi/tests/__init__.py

Whitespace-only changes.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
ra,dec,objid,type
2+
166.10552527002142,38.20797162140221,877,PSF
3+
166.10328347620825,38.211862682863625,855,PSF
4+
166.1146193911762,38.20826292586543,991,PSF
5+
166.1138080401007,38.20883307659884,3705,DUP
6+
166.11382707824612,38.20885008952696,982,SER
7+
166.11779248975387,38.211159276708706,1030,PSF
8+
166.11865123008005,38.2147201669633,1039,PSF
90 KB
Binary file not shown.
16.9 KB
Binary file not shown.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Licensed under a 3-clause BSD style license - see LICENSE.rst
2+
import os
3+
4+
5+
def get_package_data():
6+
paths = [os.path.join('data', '*.txt'),
7+
os.path.join('data', '*.fits'),
8+
os.path.join('data', '*.fits.gz'),
9+
]
10+
return {'astroquery.desi.tests': paths}

astroquery/desi/tests/test_module.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import astropy.io.votable
2+
import pytest
3+
import os
4+
import numpy as np
5+
import pyvo as vo
6+
7+
from astropy.table import Table
8+
from astropy.io import fits
9+
from pyvo.dal import TAPResults
10+
from urllib import parse
11+
from contextlib import contextmanager
12+
13+
from ... import desi
14+
from ...utils.mocks import MockResponse
15+
from ...utils import commons
16+
17+
DATA_FILES = {
18+
'dummy_tap_table': 'dummy_table.txt',
19+
'dummy_tractor_fits': 'dummy_tractor.fits',
20+
'dummy_hdu_list_fits': 'hdu_list_images.fits'
21+
}
22+
23+
coords = commons.ICRSCoord('11h04m27s +38d12m32s')
24+
radius = commons.ArcminRadiusGenerator(0.5)
25+
pixels = 60
26+
data_release = 9
27+
emispheres_list = ['north', 'south']
28+
29+
30+
@pytest.fixture
31+
def patch_get(request):
32+
try:
33+
mp = request.getfixturevalue("monkeypatch")
34+
except AttributeError: # pytest < 3
35+
mp = request.getfuncargvalue("monkeypatch")
36+
37+
mp.setattr(desi.DESILegacySurvey, '_request', get_mockreturn)
38+
return mp
39+
40+
41+
@pytest.fixture
42+
def patch_get_readable_fileobj(request):
43+
@contextmanager
44+
def get_readable_fileobj_mockreturn(filename, **kwargs):
45+
file_obj = data_path(DATA_FILES['dummy_hdu_list_fits']) # TODO: add images option
46+
encoding = kwargs.get('encoding', None)
47+
f = None
48+
try:
49+
if encoding == 'binary':
50+
f = open(file_obj, 'rb')
51+
yield f
52+
else:
53+
f = open(file_obj, 'rb')
54+
yield f
55+
finally:
56+
if f is not None:
57+
f.close()
58+
59+
try:
60+
mp = request.getfixturevalue("monkeypatch")
61+
except AttributeError: # pytest < 3
62+
mp = request.getfuncargvalue("monkeypatch")
63+
64+
mp.setattr(commons, 'get_readable_fileobj',
65+
get_readable_fileobj_mockreturn)
66+
return mp
67+
68+
69+
@pytest.fixture
70+
def patch_tap(request):
71+
try:
72+
mp = request.getfixturevalue("monkeypatch")
73+
except AttributeError: # pytest < 3
74+
mp = request.getfuncargvalue("monkeypatch")
75+
76+
mp.setattr(vo.dal.TAPService, 'run_sync', tap_mockreturn)
77+
return mp
78+
79+
80+
def get_mockreturn(method, url, params=None, timeout=10, **kwargs):
81+
parsed_url = parse.urlparse(url)
82+
splitted_parsed_url = parsed_url.path.split('/')
83+
url_filename = splitted_parsed_url[-1]
84+
filename = None
85+
content = None
86+
if url_filename.startswith('tractor-'):
87+
filename = data_path(DATA_FILES['dummy_tractor_fits'])
88+
89+
if filename is not None:
90+
content = open(filename, 'rb').read()
91+
return MockResponse(content)
92+
93+
94+
def tap_mockreturn(url, params=None, timeout=10, **kwargs):
95+
content_table = Table.read(data_path(DATA_FILES['dummy_tap_table']),
96+
format='ascii.csv', comment='#')
97+
votable_table = astropy.io.votable.from_table(content_table)
98+
return TAPResults(votable_table)
99+
100+
101+
def data_path(filename):
102+
data_dir = os.path.join(os.path.dirname(__file__), 'data')
103+
return os.path.join(data_dir, filename)
104+
105+
106+
def compare_result_data(result, data):
107+
for col in result.colnames:
108+
if result[col].dtype.type is np.string_ or result[col].dtype.type is np.str_:
109+
assert np.array_equal(result[col], data[col])
110+
else:
111+
np.testing.assert_allclose(result[col], data[col])
112+
113+
114+
def image_tester(images, filetype):
115+
assert type(images) == list
116+
data = fits.open(data_path(DATA_FILES[filetype]))
117+
assert images[0][0].header == data[0].header
118+
assert np.array_equal(images[0][0].data, data[0].data)
119+
120+
121+
def test_coords_query_region(patch_tap, coords=coords, radius=radius):
122+
result = desi.DESILegacySurvey.query_region(coords, radius)
123+
data = Table.read(data_path(DATA_FILES['dummy_tap_table']),
124+
format='ascii.csv', comment='#')
125+
data['objid'] = data['objid'].astype(np.int64)
126+
compare_result_data(result, data)
127+
128+
129+
def test_coords_get_images(patch_get_readable_fileobj, dr=data_release):
130+
images_list = desi.DESILegacySurvey.get_images(coords, data_release=dr, radius=radius, pixels=pixels)
131+
132+
image_tester(images_list, 'dummy_hdu_list_fits')
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Licensed under a 3-clause BSD style license - see LICENSE.rst
2+
3+
import pytest
4+
from astropy.io.fits import HDUList
5+
from astroquery.exceptions import NoResultsWarning
6+
7+
8+
@pytest.mark.remote_data
9+
class TestLegacySurveyClass:
10+
11+
def test_query_region(self):
12+
import astroquery.desi
13+
from astropy.coordinates import SkyCoord
14+
from astropy.coordinates import Angle
15+
from astropy.table import Table
16+
17+
ra = Angle('11h04m27s', unit='hourangle').degree
18+
dec = Angle('+38d12m32s', unit='hourangle').degree
19+
coordinates = SkyCoord(ra, dec, unit='degree')
20+
21+
radius = Angle(5, unit='arcmin')
22+
23+
query1 = astroquery.desi.DESILegacySurvey.query_region(coordinates, radius=radius, data_release=9)
24+
25+
assert isinstance(query1, Table)
26+
27+
@pytest.mark.parametrize("valid_inputs", [True, False])
28+
def test_get_images(self, valid_inputs):
29+
import astroquery.desi
30+
from astropy.coordinates import SkyCoord
31+
from astropy.coordinates import Angle
32+
33+
if valid_inputs:
34+
ra = Angle('11h04m27s', unit='hourangle').degree
35+
dec = Angle('+38d12m32s', unit='hourangle').degree
36+
radius_input = 0.5 # arcmin
37+
pixels = 60
38+
else:
39+
ra = Angle('86.633212', unit='degree').degree
40+
dec = Angle('22.01446', unit='degree').degree
41+
radius_input = 3 # arcmin
42+
pixels = 1296000
43+
44+
pos = SkyCoord(ra, dec, unit='degree')
45+
radius = Angle(radius_input, unit='arcmin')
46+
47+
if valid_inputs:
48+
query1 = astroquery.desi.DESILegacySurvey.get_images(pos, data_release=9, radius=radius, pixels=pixels)
49+
assert isinstance(query1, list)
50+
assert isinstance(query1[0], HDUList)
51+
else:
52+
with pytest.raises(NoResultsWarning):
53+
astroquery.desi.DESILegacySurvey.get_images(pos, data_release=9, radius=radius, pixels=pixels)

astroquery/utils/commons.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,13 @@ def FK4CoordGenerator(*args, **kwargs):
4242
return coord.SkyCoord(*args, frame='fk4', **kwargs)
4343

4444

45+
def ArcminRadiusGenerator(*args, **kwargs):
46+
return coord.Angle(*args, unit='arcmin', **kwargs)
47+
48+
4549
ICRSCoord = coord.SkyCoord
4650
CoordClasses = (coord.SkyCoord, BaseCoordinateFrame)
4751

48-
4952
__all__ = ['send_request',
5053
'parse_coordinates',
5154
'TableList',

0 commit comments

Comments
 (0)