1
# Licensed under a 3-clause BSD style license - see LICENSE.rst
2 1
import pprint
3 1
from bs4 import BeautifulSoup
4 1
from six.moves.urllib import parse as urlparse
5 1
import six
6 1
from astropy import units as u
7

8 1
from . import conf
9 1
from ..query import BaseQuery
10 1
from ..utils import prepend_docstr_nosections, commons, async_to_sync
11

12

13 1
__doctest_skip__ = [
14
    'SkyViewClass.get_images',
15
    'SkyViewClass.get_images_async',
16
    'SkyViewClass.get_image_list']
17

18

19 1
@async_to_sync
20 1
class SkyViewClass(BaseQuery):
21 1
    URL = conf.url
22

23 1
    def __init__(self):
24 1
        super(SkyViewClass, self).__init__()
25 1
        self._default_form_values = None
26

27 1
    def _get_default_form_values(self, form):
28
        """
29
        Return the already selected values of a given form (a BeautifulSoup
30
        form node) as a dict.
31
        """
32 1
        res = []
33 1
        for elem in form.find_all(['input', 'select']):
34
            # ignore the submit and reset buttons
35 1
            if elem.get('type') in ['submit', 'reset']:
36 1
                continue
37
            # check boxes: enabled boxes have the value "on" if not specified
38
            # otherwise. Found out by debugging, perhaps not documented.
39 1
            if (elem.get('type') == 'checkbox' and
40
                    elem.get('checked') in ["", "checked"]):
41 1
                value = elem.get('value', 'on')
42 1
                res.append((elem.get('name'), value))
43
            # radio buttons and simple input fields
44 1
            if elem.get('type') == 'radio' and\
45
                    elem.get('checked') in ["", "checked"] or\
46
                    elem.get('type') in [None, 'text']:
47 1
                res.append((elem.get('name'), elem.get('value')))
48
            # dropdown menu, multi-section possible
49 1
            if elem.name == 'select':
50 1
                for option in elem.find_all('option'):
51 1
                    if option.get('selected') == '':
52 1
                        value = option.get('value', option.text.strip())
53 1
                        res.append((elem.get('name'), value))
54 1
        return {k: v
55
                for (k, v) in res
56
                if v not in [None, u'None', u'null'] and v
57
                }
58

59 1
    def _generate_payload(self, input=None):
60
        """
61
        Fill out the form of the SkyView site and submit it with the
62
        values given in ``input`` (a dictionary where the keys are the form
63
        element's names and the values are their respective values).
64
        """
65 1
        if input is None:
66 0
            input = {}
67 1
        form_response = self._request('GET', self.URL)
68 1
        bs = BeautifulSoup(form_response.content, "html.parser")
69 1
        form = bs.find('form')
70
        # cache the default values to save HTTP traffic
71 1
        if self._default_form_values is None:
72 1
            self._default_form_values = self._get_default_form_values(form)
73
        # only overwrite payload's values if the `input` value is not None
74
        # to avoid overwriting of the form's default values
75 1
        payload = self._default_form_values.copy()
76 1
        for k, v in six.iteritems(input):
77 1
            if v is not None:
78 1
                payload[k] = v
79 1
        url = urlparse.urljoin(self.URL, form.get('action'))
80 1
        return url, payload
81

82 1
    def _submit_form(self, input=None, cache=True):
83 1
        url, payload = self._generate_payload(input=input)
84 1
        response = self._request('GET', url, params=payload, cache=cache)
85 1
        return response
86

87 1
    def get_images(self, position, survey, coordinates=None, projection=None,
88
                   pixels=None, scaling=None, sampler=None, resolver=None,
89
                   deedger=None, lut=None, grid=None, gridlabels=None,
90
                   radius=None, height=None, width=None, cache=True,
91
                   show_progress=True):
92
        """
93
        Query the SkyView service, download the FITS file that will be
94
        found and return a generator over the local paths to the
95
        downloaded FITS files.
96

97
        Note that the files will be downloaded when the generator will be
98
        exhausted, i.e. just calling this method alone without iterating
99
        over the result won't issue a connection to the SkyView server.
100

101
        Parameters
102
        ----------
103
        position : str
104
            Determines the center of the field to be retrieved. Both
105
            coordinates (also equatorial ones) and object names are
106
            supported. Object names are converted to coordinates via the
107
            SIMBAD or NED name resolver. See the reference for more info
108
            on the supported syntax for coordinates.
109
        survey : str or list of str
110
            Select data from one or more surveys. The number of surveys
111
            determines the number of resulting file downloads. Passing a
112
            list with just one string has the same effect as passing this
113
            string directly.
114
        coordinates : str
115
            Choose among common equatorial, galactic and ecliptic
116
            coordinate systems (``"J2000"``, ``"B1950"``, ``"Galactic"``,
117
            ``"E2000"``, ``"ICRS"``) or pass a custom string.
118
        projection : str
119
            Choose among the map projections (the value in parentheses
120
            denotes the string to be passed):
121

122
            Gnomonic (Tan), default value
123
                good for small regions
124
            Rectangular (Car)
125
                simplest projection
126
            Aitoff (Ait)
127
                Hammer-Aitoff, equal area projection good for all sky maps
128
            Orthographic (Sin)
129
                Projection often used in interferometry
130
            Zenith Equal Area (Zea)
131
                equal area, azimuthal projection
132
            COBE Spherical Cube (Csc)
133
                Used in COBE data
134
            Arc (Arc)
135
                Similar to Zea but not equal-area
136
        pixels : str
137
            Selects the pixel dimensions of the image to be produced. A
138
            scalar value or a pair of values separated by comma may be
139
            given. If the value is a scalar the number of width and height
140
            of the image will be the same. By default a 300x300 image is
141
            produced.
142
        scaling : str
143
            Selects the transformation between pixel intensity and
144
            intensity on the displayed image. The supported values are:
145
            ``"Log"``, ``"Sqrt"``, ``"Linear"``, ``"HistEq"``,
146
            ``"LogLog"``.
147
        sampler : str
148
            The sampling algorithm determines how the data requested will
149
            be resampled so that it can be displayed.
150
        resolver : str
151
            The name resolver allows to choose a name resolver to use when
152
            looking up a name which was passed in the ``position`` parameter
153
            (as opposed to a numeric coordinate value). The default choice
154
            is to call the SIMBAD name resolver first and then the NED
155
            name resolver if the SIMBAD search fails.
156
        deedger : str
157
            When multiple input images with different backgrounds are
158
            resampled the edges between the images may be apparent because
159
            of the background shift. This parameter makes it possible to
160
            attempt to minimize these edges by applying a de-edging
161
            algorithm. The user can elect to choose the default given for
162
            that survey, to turn de-edging off, or to use the default
163
            de-edging algorithm. The supported values are: ``"_skip_"`` to
164
            use the survey default, ``"skyview.process.Deedger"`` (for
165
            enabling de-edging), and ``"null"`` to disable.
166
        lut : str
167
            Choose from the color table selections to display the data in
168
            false color.
169
        grid : bool
170
            overlay a coordinate grid on the image if True
171
        gridlabels : bool
172
            annotate the grid with coordinates positions if True
173
        radius : `~astropy.units.Quantity` or None
174
            The radius of the specified field.  Overrides width and height.
175
        width : `~astropy.units.Quantity` or None
176
            The width of the specified field.  Must be specified
177
            with ``height``.
178
        height : `~astropy.units.Quantity` or None
179
            The height of the specified field.  Must be specified
180
            with ``width``.
181

182
        References
183
        ----------
184
        .. [1] http://skyview.gsfc.nasa.gov/current/help/fields.html
185

186
        Examples
187
        --------
188
        >>> sv = SkyView()
189
        >>> paths = sv.get_images(position='Eta Carinae',
190
        ...                       survey=['Fermi 5', 'HRI', 'DSS'])
191
        >>> for path in paths:
192
        ...     print('\tnew file:', path)
193

194
        Returns
195
        -------
196
        A list of `~astropy.io.fits.HDUList` objects.
197

198
        """
199 0
        readable_objects = self.get_images_async(position, survey, coordinates,
200
                                                 projection, pixels, scaling,
201
                                                 sampler, resolver, deedger,
202
                                                 lut, grid, gridlabels,
203
                                                 radius=radius, height=height,
204
                                                 width=width,
205
                                                 cache=cache,
206
                                                 show_progress=show_progress)
207 0
        return [obj.get_fits() for obj in readable_objects]
208

209 1
    @prepend_docstr_nosections(get_images.__doc__)
210 1
    def get_images_async(self, position, survey, coordinates=None,
211
                         projection=None, pixels=None, scaling=None,
212
                         sampler=None, resolver=None, deedger=None, lut=None,
213
                         grid=None, gridlabels=None, radius=None, height=None,
214
                         width=None, cache=True, show_progress=True):
215
        """
216
        Returns
217
        -------
218
        A list of context-managers that yield readable file-like objects
219
        """
220 0
        image_urls = self.get_image_list(position, survey, coordinates,
221
                                         projection, pixels, scaling, sampler,
222
                                         resolver, deedger, lut, grid,
223
                                         gridlabels, radius=radius,
224
                                         height=height, width=width,
225
                                         cache=cache)
226 0
        return [commons.FileContainer(url, encoding='binary',
227
                                      show_progress=show_progress)
228
                for url in image_urls]
229

230 1
    @prepend_docstr_nosections(get_images.__doc__, sections=['Returns', 'Examples'])
231 1
    def get_image_list(self, position, survey, coordinates=None,
232
                       projection=None, pixels=None, scaling=None,
233
                       sampler=None, resolver=None, deedger=None, lut=None,
234
                       grid=None, gridlabels=None, radius=None, width=None,
235
                       height=None, cache=True):
236
        """
237
        Returns
238
        -------
239
        list of image urls
240

241
        Examples
242
        --------
243
        >>> SkyView().get_image_list(position='Eta Carinae',
244
        ...                          survey=['Fermi 5', 'HRI', 'DSS'])
245
        [u'http://skyview.gsfc.nasa.gov/tempspace/fits/skv6183161285798_1.fits',
246
         u'http://skyview.gsfc.nasa.gov/tempspace/fits/skv6183161285798_2.fits',
247
         u'http://skyview.gsfc.nasa.gov/tempspace/fits/skv6183161285798_3.fits']
248
        """
249

250 1
        self._validate_surveys(survey)
251

252 1
        if radius is not None:
253 0
            size_deg = str(radius.to(u.deg).value)
254 1
        elif width and height:
255 0
            size_deg = "{0},{1}".format(width.to(u.deg).value,
256
                                        height.to(u.deg).value)
257 1
        elif width and height:
258 0
            raise ValueError("Must specify width and height if you "
259
                             "specify either.")
260
        else:
261 1
            size_deg = None
262

263 1
        input = {
264
            'Position': parse_coordinates(position),
265
            'survey': survey,
266
            'Deedger': deedger,
267
            'lut': lut,
268
            'projection': projection,
269
            'gridlabels': '1' if gridlabels else '0',
270
            'coordinates': coordinates,
271
            'scaling': scaling,
272
            'grid': grid,
273
            'resolver': resolver,
274
            'Sampler': sampler,
275
            'imscale': size_deg,
276
            'size': size_deg,
277
            'pixels': pixels}
278 1
        response = self._submit_form(input, cache=cache)
279 1
        urls = self._parse_response(response)
280 1
        return urls
281

282 1
    def _parse_response(self, response):
283 1
        bs = BeautifulSoup(response.content, "html.parser")
284 1
        urls = []
285 1
        for a in bs.find_all('a'):
286 1
            if a.text == 'FITS':
287 1
                href = a.get('href')
288 1
                urls.append(urlparse.urljoin(response.url, href))
289 1
        return urls
290

291 1
    @property
292
    def survey_dict(self):
293 1
        if not hasattr(self, '_survey_dict'):
294

295 1
            response = self._request('GET', self.URL, cache=False)
296 1
            page = BeautifulSoup(response.content, "html.parser")
297 1
            surveys = page.findAll('select', {'name': 'survey'})
298

299 1
            self._survey_dict = {
300
                sel['id']: [x.text for x in sel.findAll('option')]
301
                for sel in surveys
302
                if 'overlay' not in sel['id']
303
            }
304

305 1
        return self._survey_dict
306

307 1
    @property
308
    def _valid_surveys(self):
309
        # Return a flat list of all valid surveys
310 1
        return [x for v in self.survey_dict.values() for x in v]
311

312 1
    def _validate_surveys(self, surveys):
313 1
        if not isinstance(surveys, list):
314 0
            surveys = [surveys]
315

316 1
        for sv in surveys:
317 1
            if sv not in self._valid_surveys:
318 1
                raise ValueError("Survey is not among the surveys hosted "
319
                                 "at skyview.  See list_surveys or "
320
                                 "survey_dict for valid surveys.")
321

322 1
    def list_surveys(self):
323
        """
324
        Print out a formatted version of the survey dict
325
        """
326 0
        pprint.pprint(self.survey_dict)
327

328

329 1
def parse_coordinates(position):
330 1
    coord = commons.parse_coordinates(position)
331 1
    return coord.fk5.to_string()
332

333

334 1
SkyView = SkyViewClass()

Read our documentation on viewing source code .

Loading