astropy / astroquery
1
# Licensed under a 3-clause BSD style license - see LICENSE.rst
2 1
"""
3
Access Sloan Digital Sky Survey database online.
4
"""
5 1
from __future__ import (absolute_import, division, print_function,
6
                        unicode_literals)
7 1
import io
8 1
import warnings
9 1
import numpy as np
10

11 1
from astropy import units as u
12 1
import astropy.coordinates as coord
13 1
from astropy.table import Table, Column
14

15 1
from ..query import BaseQuery
16 1
from . import conf
17 1
from ..utils import commons, async_to_sync, prepend_docstr_nosections
18 1
from ..exceptions import RemoteServiceError, NoResultsWarning
19 1
from .field_names import (photoobj_defs, specobj_defs,
20
                          crossid_defs, get_field_info)
21

22 1
__all__ = ['SDSS', 'SDSSClass']
23 1
__doctest_skip__ = ['SDSSClass.*']
24

25

26 1
sdss_arcsec_per_pixel = 0.396 * u.arcsec / u.pixel
27

28

29 1
@async_to_sync
30 1
class SDSSClass(BaseQuery):
31 1
    TIMEOUT = conf.timeout
32 1
    QUERY_URL_SUFFIX_DR_OLD = '/dr{dr}/en/tools/search/x_sql.asp'
33 1
    QUERY_URL_SUFFIX_DR_10 = '/dr{dr}/en/tools/search/x_sql.aspx'
34 1
    QUERY_URL_SUFFIX_DR_NEW = '/dr{dr}/en/tools/search/x_results.aspx'
35 1
    XID_URL_SUFFIX_OLD = '/dr{dr}/en/tools/crossid/x_crossid.aspx'
36 1
    XID_URL_SUFFIX_NEW = '/dr{dr}/en/tools/search/X_Results.aspx'
37 1
    IMAGING_URL_SUFFIX = ('{base}/dr{dr}/{instrument}/photoObj/frames/'
38
                          '{rerun}/{run}/{camcol}/'
39
                          'frame-{band}-{run:06d}-{camcol}-'
40
                          '{field:04d}.fits.bz2')
41 1
    SPECTRA_URL_SUFFIX = ('{base}/dr{dr}/{instrument}/spectro/redux/'
42
                          '{run2d}/spectra/{plate:04d}/'
43
                          'spec-{plate:04d}-{mjd}-{fiber:04d}.fits')
44

45 1
    TEMPLATES_URL = 'http://classic.sdss.org/dr7/algorithms/spectemplates/spDR2'
46
    # Cross-correlation templates from DR-7 - no clear way to look this up via
47
    # queries so we just name them explicitly here
48 1
    AVAILABLE_TEMPLATES = {'star_O': 0, 'star_OB': 1, 'star_B': 2,
49
                           'star_A': [3, 4], 'star_FA': 5, 'star_F': [6, 7],
50
                           'star_G': [8, 9], 'star_K': 10, 'star_M1': 11,
51
                           'star_M3': 12, 'star_M5': 13, 'star_M8': 14,
52
                           'star_L1': 15, 'star_wd': [16, 20, 21],
53
                           'star_carbon': [17, 18, 19], 'star_Ksubdwarf': 22,
54
                           'galaxy_early': 23, 'galaxy': [24, 25, 26],
55
                           'galaxy_late': 27, 'galaxy_lrg': 28, 'qso': 29,
56
                           'qso_bal': [30, 31], 'qso_bright': 32
57
                           }
58

59 1
    def query_crossid_async(self, coordinates, obj_names=None,
60
                            photoobj_fields=None, specobj_fields=None,
61
                            get_query_payload=False, timeout=TIMEOUT,
62
                            radius=5. * u.arcsec,
63
                            data_release=conf.default_release, cache=True):
64
        """
65
        Query using the cross-identification web interface.
66

67
        Parameters
68
        ----------
69
        coordinates : str or `astropy.coordinates` object or list of
70
            coordinates or `~astropy.table.Column` of coordinates The
71
            target(s) around which to search. It may be specified as a
72
            string in which case it is resolved using online services or as
73
            the appropriate `astropy.coordinates` object. ICRS coordinates
74
            may also be entered as strings as specified in the
75
            `astropy.coordinates` module.
76

77
            Example:
78
            ra = np.array([220.064728084,220.064728467,220.06473483])
79
            dec = np.array([0.870131920218,0.87013210119,0.870138329659])
80
            coordinates = SkyCoord(ra, dec, frame='icrs', unit='deg')
81
        radius : str or `~astropy.units.Quantity` object, optional The
82
            string must be parsable by `~astropy.coordinates.Angle`. The
83
            appropriate `~astropy.units.Quantity` object from
84
            `astropy.units` may also be used. Defaults to 2 arcsec.
85
        timeout : float, optional
86
            Time limit (in seconds) for establishing successful connection with
87
            remote server.  Defaults to `SDSSClass.TIMEOUT`.
88
        photoobj_fields : list, optional
89
            PhotoObj quantities to return. If photoobj_fields is None and
90
            specobj_fields is None then the value of fields is used
91
        specobj_fields : list, optional
92
            SpecObj quantities to return. If photoobj_fields is None and
93
            specobj_fields is None then the value of fields is used
94
        obj_names : str, or list or `~astropy.table.Column`, optional
95
            Target names. If given, every coordinate should have a
96
            corresponding name, and it gets repeated in the query result.
97
            It generates unique object names by default.
98
        get_query_payload : bool
99
            If True, this will return the data the query would have sent out,
100
            but does not actually do the query.
101
        data_release : int
102
            The data release of the SDSS to use.
103
        """
104

105 1
        if (not isinstance(coordinates, list) and
106
                not isinstance(coordinates, Column) and
107
                not (isinstance(coordinates, commons.CoordClasses) and
108
                     not coordinates.isscalar)):
109 0
            coordinates = [coordinates]
110

111 1
        if obj_names is None:
112 1
            obj_names = ['obj_{0}'.format(i) for i in range(len(coordinates))]
113 0
        elif len(obj_names) != len(coordinates):
114 0
            raise ValueError("Number of coordinates and obj_names should "
115
                             "be equal")
116

117 1
        if isinstance(radius, u.Quantity):
118 1
            radius = radius.to(u.arcmin).value
119
        else:
120 0
            try:
121 0
                float(radius)
122 0
            except TypeError:
123 0
                raise TypeError("radius should be either Quantity or "
124
                                "convertible to float.")
125

126 1
        sql_query = 'SELECT '
127

128 1
        if specobj_fields is None:
129 1
            if photoobj_fields is None:
130 1
                photoobj_fields = crossid_defs
131 1
            photobj_fields = ['p.{0}'.format(i) for i in photoobj_fields]
132 1
            photobj_fields.append('p.objID as obj_id')
133 1
            specobj_fields = []
134
        else:
135 0
            specobj_fields = ['s.{0}'.format(i) for i in specobj_fields]
136 0
            if photoobj_fields is not None:
137 0
                photobj_fields = ['p.{0}'.format(i) for i in photoobj_fields]
138 0
                photobj_fields.append('p.objID as obj_id')
139
            else:
140 0
                photobj_fields = []
141 0
                specobj_fields.append('s.objID as obj_id')
142

143 1
        sql_query += ', '.join(photobj_fields + specobj_fields)
144

145 1
        sql_query += ',dbo.fPhotoTypeN(p.type) as type \
146
                      FROM #upload u JOIN #x x ON x.up_id = u.up_id \
147
                      JOIN PhotoObjAll p ON p.objID = x.objID ORDER BY x.up_id'
148

149 1
        data = "obj_id ra dec \n"
150 1
        data += " \n ".join(['{0} {1} {2}'.format(obj_names[i],
151
                                                  coordinates[i].ra.deg,
152
                                                  coordinates[i].dec.deg)
153
                             for i in range(len(coordinates))])
154

155
        # firstcol is hardwired, as obj_names is always passed
156 1
        request_payload = dict(uquery=sql_query, paste=data,
157
                               firstcol=1,
158
                               format='csv', photoScope='nearPrim',
159
                               radius=radius,
160
                               photoUpType='ra-dec', searchType='photo')
161

162 1
        if data_release > 11:
163 1
            request_payload['searchtool'] = 'CrossID'
164

165 1
        if get_query_payload:
166 0
            return request_payload
167 1
        url = self._get_crossid_url(data_release)
168 1
        response = self._request("POST", url, params=request_payload,
169
                                 timeout=timeout, cache=cache)
170 1
        return response
171

172 1
    def query_region_async(self, coordinates, radius=2. * u.arcsec,
173
                           fields=None, spectro=False, timeout=TIMEOUT,
174
                           get_query_payload=False, photoobj_fields=None,
175
                           specobj_fields=None, field_help=False,
176
                           obj_names=None, data_release=conf.default_release,
177
                           cache=True):
178
        """
179
        Used to query a region around given coordinates. Equivalent to
180
        the object cross-ID from the web interface.
181

182
        Parameters
183
        ----------
184
        coordinates : str or `astropy.coordinates` object or list of
185
            coordinates or `~astropy.table.Column` of coordinates The
186
            target(s) around which to search. It may be specified as a
187
            string in which case it is resolved using online services or as
188
            the appropriate `astropy.coordinates` object. ICRS coordinates
189
            may also be entered as strings as specified in the
190
            `astropy.coordinates` module.
191

192
            Example:
193
            ra = np.array([220.064728084,220.064728467,220.06473483])
194
            dec = np.array([0.870131920218,0.87013210119,0.870138329659])
195
            coordinates = SkyCoord(ra, dec, frame='icrs', unit='deg')
196
        radius : str or `~astropy.units.Quantity` object, optional The
197
            string must be parsable by `~astropy.coordinates.Angle`. The
198
            appropriate `~astropy.units.Quantity` object from
199
            `astropy.units` may also be used. Defaults to 2 arcsec.
200
        fields : list, optional
201
            SDSS PhotoObj or SpecObj quantities to return. If None, defaults
202
            to quantities required to find corresponding spectra and images
203
            of matched objects (e.g. plate, fiberID, mjd, etc.).
204
        spectro : bool, optional
205
            Look for spectroscopic match in addition to photometric match? If
206
            True, objects will only count as a match if photometry *and*
207
            spectroscopy exist. If False, will look for photometric matches
208
            only.
209
        timeout : float, optional
210
            Time limit (in seconds) for establishing successful connection with
211
            remote server.  Defaults to `SDSSClass.TIMEOUT`.
212
        photoobj_fields : list, optional
213
            PhotoObj quantities to return. If photoobj_fields is None and
214
            specobj_fields is None then the value of fields is used
215
        specobj_fields : list, optional
216
            SpecObj quantities to return. If photoobj_fields is None and
217
            specobj_fields is None then the value of fields is used
218
        field_help: str or bool, optional
219
            Field name to check whether a valid PhotoObjAll or SpecObjAll
220
            field name. If `True` or it is an invalid field name all the valid
221
            field names are returned as a dict.
222
        obj_names : str, or list or `~astropy.table.Column`, optional
223
            Target names. If given, every coordinate should have a
224
            corresponding name, and it gets repeated in the query result.
225
        get_query_payload : bool
226
            If True, this will return the data the query would have sent out,
227
            but does not actually do the query.
228
        data_release : int
229
            The data release of the SDSS to use.
230

231
        Examples
232
        --------
233
        >>> from astroquery.sdss import SDSS
234
        >>> from astropy import coordinates as coords
235
        >>> co = coords.SkyCoord('0h8m05.63s +14d50m23.3s')
236
        >>> result = SDSS.query_region(co)
237
        >>> print(result[:5])
238
              ra           dec             objid        run  rerun camcol field
239
        ------------- ------------- ------------------- ---- ----- ------ -----
240
        2.02344282607 14.8398204075 1237653651835781245 1904   301      3   163
241
        2.02344283666 14.8398204143 1237653651835781244 1904   301      3   163
242
        2.02344596595 14.8398237229 1237652943176138867 1739   301      3   315
243
        2.02344596303 14.8398237521 1237652943176138868 1739   301      3   315
244
        2.02344772021 14.8398201105 1237653651835781243 1904   301      3   163
245

246
        Returns
247
        -------
248
        result : `~astropy.table.Table`
249
            The result of the query as a `~astropy.table.Table` object.
250

251
        """
252 1
        request_payload = self._args_to_payload(
253
            coordinates=coordinates, radius=radius, fields=fields,
254
            spectro=spectro, photoobj_fields=photoobj_fields,
255
            specobj_fields=specobj_fields, field_help=field_help,
256
            obj_names=obj_names, data_release=data_release)
257 1
        if get_query_payload or field_help:
258 1
            return request_payload
259

260 1
        url = self._get_query_url(data_release)
261 1
        response = self._request("GET", url, params=request_payload,
262
                                 timeout=timeout, cache=cache)
263 1
        return response
264

265 1
    def query_specobj_async(self, plate=None, mjd=None, fiberID=None,
266
                            fields=None, timeout=TIMEOUT,
267
                            get_query_payload=False, field_help=False,
268
                            data_release=conf.default_release, cache=True):
269
        """
270
        Used to query the SpecObjAll table with plate, mjd and fiberID values.
271

272
        At least one of ``plate``, ``mjd`` or ``fiberID`` parameters must be
273
        specified.
274

275
        Parameters
276
        ----------
277
        plate : integer, optional
278
            Plate number.
279
        mjd : integer, optional
280
            Modified Julian Date indicating the date a given piece of SDSS data
281
            was taken.
282
        fiberID : integer, optional
283
            Fiber number.
284
        fields : list, optional
285
            SDSS PhotoObj or SpecObj quantities to return. If None, defaults
286
            to quantities required to find corresponding spectra and images
287
            of matched objects (e.g. plate, fiberID, mjd, etc.).
288
        timeout : float, optional
289
            Time limit (in seconds) for establishing successful connection with
290
            remote server.  Defaults to `SDSSClass.TIMEOUT`.
291
        field_help: str or bool, optional
292
            Field name to check whether a valid PhotoObjAll or SpecObjAll
293
            field name. If `True` or it is an invalid field name all the valid
294
            field names are returned as a dict.
295
        get_query_payload : bool
296
            If True, this will return the data the query would have sent out,
297
            but does not actually do the query.
298
        data_release : int
299
            The data release of the SDSS to use.
300

301
        Examples
302
        --------
303
        >>> from astroquery.sdss import SDSS
304
        >>> result = SDSS.query_specobj(plate=2340,
305
        ...     fields=['ra', 'dec','plate', 'mjd', 'fiberID', 'specobjid'])
306
        >>> print(result[:5])
307
              ra           dec      plate  mjd  fiberID      specobjid
308
        ------------- ------------- ----- ----- ------- -------------------
309
        49.2020613611 5.20883041368  2340 53733      60 2634622337315530752
310
        48.3745360119 5.26557511598  2340 53733     154 2634648175838783488
311
        47.1604269095 5.48241410994  2340 53733     332 2634697104106219520
312
        48.6634992214 6.69459110287  2340 53733     553 2634757852123654144
313
        48.0759195428 6.18757403485  2340 53733     506 2634744932862027776
314

315
        Returns
316
        -------
317
        result : `~astropy.table.Table`
318
            The result of the query as an `~astropy.table.Table` object.
319

320
        """
321

322 1
        if plate is None and mjd is None and fiberID is None:
323 0
            raise ValueError('must specify at least one of '
324
                             '`plate`, `mjd` or `fiberID`')
325 1
        request_payload = self._args_to_payload(plate=plate, mjd=mjd,
326
                                                fiberID=fiberID,
327
                                                specobj_fields=fields,
328
                                                spectro=True,
329
                                                field_help=field_help,
330
                                                data_release=data_release)
331 1
        if get_query_payload or field_help:
332 0
            return request_payload
333

334 1
        url = self._get_query_url(data_release)
335 1
        response = self._request("GET", url, params=request_payload,
336
                                 timeout=timeout, cache=cache)
337 1
        return response
338

339 1
    def query_photoobj_async(self, run=None, rerun=301, camcol=None,
340
                             field=None, fields=None, timeout=TIMEOUT,
341
                             get_query_payload=False, field_help=False,
342
                             data_release=conf.default_release, cache=True):
343
        """
344
        Used to query the PhotoObjAll table with run, rerun, camcol and field
345
        values.
346

347
        At least one of ``run``, ``camcol`` or ``field`` parameters must be
348
        specified.
349

350
        Parameters
351
        ----------
352
        run : integer, optional
353
            Length of a strip observed in a single continuous image observing
354
            scan.
355
        rerun : integer, optional
356
            Reprocessing of an imaging run. Defaults to 301 which is the most
357
            recent rerun.
358
        camcol : integer, optional
359
            Output of one camera column of CCDs.
360
        field : integer, optional
361
            Part of a camcol of size 2048 by 1489 pixels.
362
        fields : list, optional
363
            SDSS PhotoObj or SpecObj quantities to return. If None, defaults
364
            to quantities required to find corresponding spectra and images
365
            of matched objects (e.g. plate, fiberID, mjd, etc.).
366
        timeout : float, optional
367
            Time limit (in seconds) for establishing successful connection with
368
            remote server.  Defaults to `SDSSClass.TIMEOUT`.
369
        field_help: str or bool, optional
370
            Field name to check whether a valid PhotoObjAll or SpecObjAll
371
            field name. If `True` or it is an invalid field name all the valid
372
            field names are returned as a dict.
373
        get_query_payload : bool
374
            If True, this will return the data the query would have sent out,
375
            but does not actually do the query.
376
        data_release : int
377
            The data release of the SDSS to use.
378

379
        Examples
380
        --------
381
        >>> from astroquery.sdss import SDSS
382
        >>> result = SDSS.query_photoobj(run=5714, camcol=6)
383
        >>> print(result[:5])
384
              ra           dec             objid        run  rerun camcol field
385
        ------------- ------------- ------------------- ---- ----- ------ -----
386
        30.4644529079 7.86460794626 1237670017266024498 5714   301      6    75
387
        38.7635496073 7.47083098197 1237670017269628978 5714   301      6   130
388
        22.2574304026 8.43175488904 1237670017262485671 5714   301      6    21
389
        23.3724928784 8.32576993103 1237670017262944491 5714   301      6    28
390
        25.4801226435 8.27642390025 1237670017263927330 5714   301      6    43
391

392
        Returns
393
        -------
394
        result : `~astropy.table.Table`
395
            The result of the query as a `~astropy.table.Table` object.
396

397
        """
398

399 1
        if run is None and camcol is None and field is None:
400 0
            raise ValueError('must specify at least one of '
401
                             '`run`, `camcol` or `field`')
402 1
        request_payload = self._args_to_payload(run=run, rerun=rerun,
403
                                                camcol=camcol, field=field,
404
                                                photoobj_fields=fields,
405
                                                spectro=False,
406
                                                field_help=field_help,
407
                                                data_release=data_release)
408 1
        if get_query_payload or field_help:
409 0
            return request_payload
410

411 1
        url = self._get_query_url(data_release)
412 1
        response = self._request("GET", url, params=request_payload,
413
                                 timeout=timeout, cache=cache)
414 1
        return response
415

416 1
    def __sanitize_query(self, stmt):
417
        """Remove comments and newlines from SQL statement."""
418 1
        fsql = ''
419 1
        for line in stmt.split('\n'):
420 1
            fsql += ' ' + line.split('--')[0]
421 1
        return fsql
422

423 1
    def query_sql_async(self, sql_query, timeout=TIMEOUT,
424
                        data_release=conf.default_release,
425
                        cache=True, **kwargs):
426
        """
427
        Query the SDSS database.
428

429
        Parameters
430
        ----------
431
        sql_query : str
432
            An SQL query
433
        timeout : float, optional
434
            Time limit (in seconds) for establishing successful connection with
435
            remote server.  Defaults to `SDSSClass.TIMEOUT`.
436
        data_release : int
437
            The data release of the SDSS to use.
438

439
        Examples
440
        --------
441
        >>> from astroquery.sdss import SDSS
442
        >>> query = "select top 10 \
443
                       z, ra, dec, bestObjID \
444
                     from \
445
                       specObj \
446
                     where \
447
                       class = 'galaxy' \
448
                       and z > 0.3 \
449
                       and zWarning = 0"
450
        >>> res = SDSS.query_sql(query)
451
        >>> print(res[:5])
452
            z         ra       dec         bestObjID
453
        --------- --------- --------- -------------------
454
        0.3000011 16.411075 4.1197892 1237678660894327022
455
        0.3000012 49.459411  0.847754 1237660241924063461
456
        0.3000027 156.25024 7.6586271 1237658425162858683
457
        0.3000027 256.99461 25.566255 1237661387086693265
458
         0.300003 175.65125  34.37548 1237665128003731630
459

460
        Returns
461
        -------
462
        result : `~astropy.table.Table`
463
            The result of the query as a `~astropy.table.Table` object.
464

465
        """
466

467 1
        request_payload = dict(cmd=self.__sanitize_query(sql_query),
468
                               format='csv')
469 1
        if data_release > 11:
470 1
            request_payload['searchtool'] = 'SQL'
471

472 1
        if kwargs.get('get_query_payload'):
473 0
            return request_payload
474

475 1
        url = self._get_query_url(data_release)
476 1
        response = self._request("GET", url, params=request_payload,
477
                                 timeout=timeout, cache=cache)
478 1
        return response
479

480 1
    def get_spectra_async(self, coordinates=None, radius=2. * u.arcsec,
481
                          matches=None, plate=None, fiberID=None, mjd=None,
482
                          timeout=TIMEOUT, get_query_payload=False,
483
                          data_release=conf.default_release, cache=True,
484
                          show_progress=True):
485
        """
486
        Download spectrum from SDSS.
487

488
        The query can be made with one the following groups of parameters
489
        (whichever comes first is used):
490

491
        - ``matches`` (result of a call to `query_region`);
492
        - ``coordinates``, ``radius``;
493
        - ``plate``, ``mjd``, ``fiberID``.
494

495
        See below for examples.
496

497
        Parameters
498
        ----------
499
        coordinates : str or `astropy.coordinates` object
500
            The target around which to search. It may be specified as a string
501
            in which case it is resolved using online services or as the
502
            appropriate `astropy.coordinates` object. ICRS coordinates may also
503
            be entered as strings as specified in the `astropy.coordinates`
504
            module.
505
        radius : str or `~astropy.units.Quantity` object, optional
506
            The string must be parsable by `~astropy.coordinates.Angle`. The
507
            appropriate `~astropy.units.Quantity` object from `astropy.units`
508
            may also be used. Defaults to 2 arcsec.
509
        matches : `~astropy.table.Table`
510
            Result of `query_region`.
511
        plate : integer, optional
512
            Plate number.
513
        mjd : integer, optional
514
            Modified Julian Date indicating the date a given piece of SDSS data
515
            was taken.
516
        fiberID : integer, optional
517
            Fiber number.
518
        timeout : float, optional
519
            Time limit (in seconds) for establishing successful connection with
520
            remote server.  Defaults to `SDSSClass.TIMEOUT`.
521
        get_query_payload : bool
522
            If True, this will return the data the query would have sent out,
523
            but does not actually do the query.
524
        data_release : int
525
            The data release of the SDSS to use. With the default server, this
526
            only supports DR8 or later.
527

528
        Returns
529
        -------
530
        list : list
531
            A list of context-managers that yield readable file-like objects.
532
            The function returns the spectra for only one of ``matches``, or
533
            ``coordinates`` and ``radius``, or ``plate``, ``mjd`` and
534
            ``fiberID``.
535

536
        Examples
537
        --------
538
        Using results from a call to `query_region`:
539

540
        >>> from astropy import coordinates as coords
541
        >>> from astroquery.sdss import SDSS
542
        >>> co = coords.SkyCoord('0h8m05.63s +14d50m23.3s')
543
        >>> result = SDSS.query_region(co, spectro=True)
544
        >>> spec = SDSS.get_spectra(matches=result)
545

546
        Using coordinates directly:
547

548
        >>> spec = SDSS.get_spectra(co)
549

550
        Fetch the spectra from all fibers on plate 751 with mjd 52251:
551

552
        >>> specs = SDSS.get_spectra(plate=751, mjd=52251)
553

554
        """
555

556 1
        if not matches:
557 1
            request_payload = self._args_to_payload(
558
                specobj_fields=['instrument', 'run2d', 'plate',
559
                                'mjd', 'fiberID'],
560
                coordinates=coordinates, radius=radius, spectro=True,
561
                plate=plate, mjd=mjd, fiberID=fiberID,
562
                data_release=data_release)
563 1
            if get_query_payload:
564 0
                return request_payload
565

566 1
            url = self._get_query_url(data_release)
567 1
            r = self._request("GET", url, params=request_payload,
568
                              timeout=timeout, cache=cache)
569

570 1
            matches = self._parse_result(r)
571 1
            if matches is None:
572 0
                warnings.warn("Query returned no results.", NoResultsWarning)
573 0
                return
574

575 1
        if not isinstance(matches, Table):
576 0
            raise TypeError("'matches' must be an astropy Table.")
577

578 1
        results = []
579 1
        for row in matches:
580 1
            linkstr = self.SPECTRA_URL_SUFFIX
581
            # _parse_result returns bytes (requiring a decode) for
582
            # - instruments
583
            # - run2d sometimes (#739)
584 1
            if isinstance(row['run2d'], bytes):
585 0
                run2d = row['run2d'].decode()
586
            else:
587 1
                run2d = row['run2d']
588 1
            link = linkstr.format(
589
                base=conf.sas_baseurl, dr=data_release,
590
                instrument=row['instrument'].lower(),
591
                run2d=run2d, plate=row['plate'],
592
                fiber=row['fiberID'], mjd=row['mjd'])
593

594 1
            results.append(commons.FileContainer(link,
595
                                                 encoding='binary',
596
                                                 remote_timeout=timeout,
597
                                                 show_progress=show_progress))
598

599 1
        return results
600

601 1
    @prepend_docstr_nosections(get_spectra_async.__doc__)
602 1
    def get_spectra(self, coordinates=None, radius=2. * u.arcsec,
603
                    matches=None, plate=None, fiberID=None, mjd=None,
604
                    timeout=TIMEOUT, cache=True,
605
                    data_release=conf.default_release,
606
                    show_progress=True):
607
        """
608
        Returns
609
        -------
610
        list : List of `~astropy.io.fits.HDUList` objects.
611

612
        """
613

614 1
        readable_objs = self.get_spectra_async(coordinates=coordinates,
615
                                               radius=radius, matches=matches,
616
                                               plate=plate, fiberID=fiberID,
617
                                               mjd=mjd, timeout=timeout,
618
                                               data_release=data_release,
619
                                               show_progress=show_progress)
620

621 1
        if readable_objs is not None:
622 1
            if isinstance(readable_objs, dict):
623 0
                return readable_objs
624
            else:
625 1
                return [obj.get_fits() for obj in readable_objs]
626

627 1
    def get_images_async(self, coordinates=None, radius=2. * u.arcsec,
628
                         matches=None, run=None, rerun=301, camcol=None,
629
                         field=None, band='g', timeout=TIMEOUT,
630
                         get_query_payload=False, cache=True,
631
                         data_release=conf.default_release,
632
                         show_progress=True):
633
        """
634
        Download an image from SDSS.
635

636
        Querying SDSS for images will return the entire plate. For subsequent
637
        analyses of individual objects
638

639
        The query can be made with one the following groups of parameters
640
        (whichever comes first is used):
641

642
        - ``matches`` (result of a call to `query_region`);
643
        - ``coordinates``, ``radius``;
644
        - ``run``, ``rerun``, ``camcol``, ``field``.
645

646
        See below for examples.
647

648
        Parameters
649
        ----------
650
        coordinates : str or `astropy.coordinates` object
651
            The target around which to search. It may be specified as a string
652
            in which case it is resolved using online services or as the
653
            appropriate `astropy.coordinates` object. ICRS coordinates may also
654
            be entered as strings as specified in the `astropy.coordinates`
655
            module.
656
        radius : str or `~astropy.units.Quantity` object, optional
657
            The string must be parsable by `~astropy.coordinates.Angle`. The
658
            appropriate `~astropy.units.Quantity` object from
659
            `astropy.units` may also be used. Defaults to 2 arcsec.
660
        matches : `~astropy.table.Table`
661
            Result of `query_region`.
662
        run : integer, optional
663
            Length of a strip observed in a single continuous image observing
664
            scan.
665
        rerun : integer, optional
666
            Reprocessing of an imaging run. Defaults to 301 which is the most
667
            recent rerun.
668
        camcol : integer, optional
669
            Output of one camera column of CCDs.
670
        field : integer, optional
671
            Part of a camcol of size 2048 by 1489 pixels.
672
        band : str, list
673
            Could be individual band, or list of bands.
674
            Options: ``'u'``, ``'g'``, ``'r'``, ``'i'``, or ``'z'``.
675
        timeout : float, optional
676
            Time limit (in seconds) for establishing successful connection with
677
            remote server.  Defaults to `SDSSClass.TIMEOUT`.
678
        cache : bool
679
            Cache the images using astropy's caching system
680
        get_query_payload : bool
681
            If True, this will return the data the query would have sent out,
682
            but does not actually do the query.
683
        data_release : int
684
            The data release of the SDSS to use.
685

686
        Returns
687
        -------
688
        list : List of `~astropy.io.fits.HDUList` objects.
689

690
        Examples
691
        --------
692
        Using results from a call to `query_region`:
693

694
        >>> from astropy import coordinates as coords
695
        >>> from astroquery.sdss import SDSS
696
        >>> co = coords.SkyCoord('0h8m05.63s +14d50m23.3s')
697
        >>> result = SDSS.query_region(co)
698
        >>> imgs = SDSS.get_images(matches=result)
699

700
        Using coordinates directly:
701

702
        >>> imgs = SDSS.get_images(co)
703

704
        Fetch the images from all runs with camcol 3 and field 164:
705

706
        >>> imgs = SDSS.get_images(camcol=3, field=164)
707

708
        Fetch only images from run 1904, camcol 3 and field 164:
709

710
        >>> imgs = SDSS.get_images(run=1904, camcol=3, field=164)
711

712
        """
713 1
        if not matches:
714 1
            request_payload = self._args_to_payload(
715
                fields=['run', 'rerun', 'camcol', 'field'],
716
                coordinates=coordinates, radius=radius, spectro=False, run=run,
717
                rerun=rerun, camcol=camcol, field=field,
718
                data_release=data_release)
719 1
            if get_query_payload:
720 0
                return request_payload
721

722 1
            url = self._get_query_url(data_release)
723 1
            r = self._request("GET", url, params=request_payload,
724
                              timeout=timeout, cache=cache)
725 1
            matches = self._parse_result(r)
726 1
            if matches is None:
727 0
                warnings.warn("Query returned no results.", NoResultsWarning)
728 0
                return
729 1
        if not isinstance(matches, Table):
730 0
            raise ValueError("'matches' must be an astropy Table")
731

732 1
        results = []
733 1
        for row in matches:
734 1
            for b in band:
735
                # Download and read in image data
736 1
                linkstr = self.IMAGING_URL_SUFFIX
737 1
                instrument = 'boss'
738 1
                if data_release > 12:
739 1
                    instrument = 'eboss'
740 1
                link = linkstr.format(base=conf.sas_baseurl, run=row['run'],
741
                                      dr=data_release, instrument=instrument,
742
                                      rerun=row['rerun'], camcol=row['camcol'],
743
                                      field=row['field'], band=b)
744

745 1
                results.append(commons.FileContainer(
746
                    link, encoding='binary', remote_timeout=timeout,
747
                    cache=cache, show_progress=show_progress))
748

749 1
        return results
750

751 1
    @prepend_docstr_nosections(get_images_async.__doc__)
752 1
    def get_images(self, coordinates=None, radius=2. * u.arcsec,
753
                   matches=None, run=None, rerun=301, camcol=None, field=None,
754
                   band='g', timeout=TIMEOUT, cache=True,
755
                   get_query_payload=False, data_release=conf.default_release,
756
                   show_progress=True):
757
        """
758
        Returns
759
        -------
760
        list : List of `~astropy.io.fits.HDUList` objects.
761

762
        """
763

764 1
        readable_objs = self.get_images_async(
765
            coordinates=coordinates, radius=radius, matches=matches, run=run,
766
            rerun=rerun, data_release=data_release, camcol=camcol, field=field,
767
            band=band, timeout=timeout, get_query_payload=get_query_payload,
768
            show_progress=show_progress)
769

770 1
        if readable_objs is not None:
771 1
            if isinstance(readable_objs, dict):
772 0
                return readable_objs
773
            else:
774 1
                return [obj.get_fits() for obj in readable_objs]
775

776 1
    def get_spectral_template_async(self, kind='qso', timeout=TIMEOUT,
777
                                    show_progress=True):
778
        """
779
        Download spectral templates from SDSS DR-2.
780

781
        Location: http://www.sdss.org/dr7/algorithms/spectemplates/
782

783
        There 32 spectral templates available from DR-2, from stellar spectra,
784
        to galaxies, to quasars. To see the available templates, do:
785

786
            from astroquery.sdss import SDSS
787
            print SDSS.AVAILABLE_TEMPLATES
788

789
        Parameters
790
        ----------
791
        kind : str, list
792
            Which spectral template to download? Options are stored in the
793
            dictionary astroquery.sdss.SDSS.AVAILABLE_TEMPLATES
794
        timeout : float, optional
795
            Time limit (in seconds) for establishing successful connection with
796
            remote server.  Defaults to `SDSSClass.TIMEOUT`.
797

798
        Examples
799
        --------
800
        >>> qso = SDSS.get_spectral_template(kind='qso')
801
        >>> Astar = SDSS.get_spectral_template(kind='star_A')
802
        >>> Fstar = SDSS.get_spectral_template(kind='star_F')
803

804
        Returns
805
        -------
806
        list : List of `~astropy.io.fits.HDUList` objects.
807

808
        """
809

810 1
        if kind == 'all':
811 0
            indices = list(np.arange(33))
812
        else:
813 1
            indices = self.AVAILABLE_TEMPLATES[kind]
814 1
            if type(indices) is not list:
815 1
                indices = [indices]
816

817 1
        results = []
818 1
        for index in indices:
819 1
            name = str(index).zfill(3)
820 1
            link = '%s-%s.fit' % (self.TEMPLATES_URL, name)
821 1
            results.append(commons.FileContainer(link,
822
                                                 remote_timeout=timeout,
823
                                                 encoding='binary',
824
                                                 show_progress=show_progress))
825

826 1
        return results
827

828 1
    @prepend_docstr_nosections(get_spectral_template_async.__doc__)
829 1
    def get_spectral_template(self, kind='qso', timeout=TIMEOUT,
830
                              show_progress=True):
831
        """
832
        Returns
833
        -------
834
        list : List of `~astropy.io.fits.HDUList` objects.
835

836
        """
837

838 1
        readable_objs = self.get_spectral_template_async(
839
            kind=kind, timeout=timeout, show_progress=show_progress)
840

841 1
        if readable_objs is not None:
842 1
            return [obj.get_fits() for obj in readable_objs]
843

844 1
    def _parse_result(self, response, verbose=False):
845
        """
846
        Parses the result and return either a `~astropy.table.Table` or
847
        `None` if no matches were found.
848

849
        Parameters
850
        ----------
851
        response : `requests.Response`
852
            Result of requests -> np.atleast_1d.
853

854
        Returns
855
        -------
856
        table : `~astropy.table.Table`
857

858
        """
859

860 1
        if 'error_message' in io.BytesIO(response.content):
861 0
            raise RemoteServiceError(response.content)
862 1
        arr = np.atleast_1d(np.genfromtxt(io.BytesIO(response.content),
863
                                          names=True, dtype=None,
864
                                          delimiter=',', skip_header=1,
865
                                          comments='#'))
866

867 1
        if len(arr) == 0:
868 0
            return None
869
        else:
870 1
            return Table(arr)
871

872 1
    def _args_to_payload(self, coordinates=None, radius=2. * u.arcsec,
873
                         fields=None, spectro=False,
874
                         plate=None, mjd=None, fiberID=None, run=None,
875
                         rerun=301, camcol=None, field=None,
876
                         photoobj_fields=None, specobj_fields=None,
877
                         field_help=None, obj_names=None,
878
                         data_release=conf.default_release):
879
        """
880
        Construct the SQL query from the arguments.
881

882
        Parameters
883
        ----------
884
        coordinates : str or `astropy.coordinates` object or list of
885
            coordinates or `~astropy.table.Column` or coordinates
886
            The target around which to search. It may be specified as a string
887
            in which case it is resolved using online services or as the
888
            appropriate `astropy.coordinates` object. ICRS coordinates may also
889
            be entered as strings as specified in the `astropy.coordinates`
890
            module.
891
        radius : str or `~astropy.units.Quantity` object, optional
892
            The string must be parsable by `~astropy.coordinates.Angle`. The
893
            appropriate `~astropy.units.Quantity` object from `astropy.units`
894
            may also be used. Defaults to 2 arcsec.
895
        fields : list, optional
896
            SDSS PhotoObj or SpecObj quantities to return. If None, defaults
897
            to quantities required to find corresponding spectra and images
898
            of matched objects (e.g. plate, fiberID, mjd, etc.).
899
        spectro : bool, optional
900
            Look for spectroscopic match in addition to photometric match? If
901
            True, objects will only count as a match if photometry *and*
902
            spectroscopy exist. If False, will look for photometric matches
903
            only. If ``spectro`` is True, it is possible to let coordinates
904
            undefined and set at least one of ``plate``, ``mjd`` or ``fiberID``
905
            to search using these fields.
906
        plate : integer, optional
907
            Plate number.
908
        mjd : integer, optional
909
            Modified Julian Date indicating the date a given piece of SDSS data
910
            was taken.
911
        fiberID : integer, optional
912
            Fiber number.
913
        run : integer, optional
914
            Length of a strip observed in a single continuous image observing
915
            scan.
916
        rerun : integer, optional
917
            Reprocessing of an imaging run. Defaults to 301 which is the most
918
            recent rerun.
919
        camcol : integer, optional
920
            Output of one camera column of CCDs.
921
        field : integer, optional
922
            Part of a camcol of size 2048 by 1489 pixels.
923
        photoobj_fields: list, optional
924
            PhotoObj quantities to return. If photoobj_fields is None and
925
            specobj_fields is None then the value of fields is used
926
        specobj_fields: list, optional
927
            SpecObj quantities to return. If photoobj_fields is None and
928
            specobj_fields is None then the value of fields is used
929
        field_help: str or bool, optional
930
            Field name to check whether it is a valid PhotoObjAll or SpecObjAll
931
            field name. If `True` or it is an invalid field name all the valid
932
            field names are returned as a dict.
933
        obj_names : str, or list or `~astropy.table.Column`, optional
934
            Target names. If given, every coordinate should have a
935
            corresponding name, and it gets repeated in the query result
936
        data_release : int
937
            The data release of the SDSS to use.
938

939
        Returns
940
        -------
941
        request_payload : dict
942

943
        """
944 1
        url = self._get_query_url(data_release)
945
        # TODO: replace this with something cleaner below
946 1
        photoobj_all = get_field_info(self, 'PhotoObjAll', url,
947
                                      self.TIMEOUT)['name']
948

949 1
        specobj_all = get_field_info(self, 'SpecObjAll', url,
950
                                     self.TIMEOUT)['name']
951

952 1
        if field_help:
953 1
            if field_help is True:
954 1
                ret = 0
955 1
            elif field_help:
956 1
                ret = 0
957 1
                if field_help in photoobj_all:
958 1
                    print("{0} is a valid 'photoobj_field'".format(field_help))
959 1
                    ret += 1
960 1
                if field_help in specobj_all:
961 1
                    print("{0} is a valid 'specobj_field'".format(field_help))
962 1
                    ret += 1
963 1
            if ret > 0:
964 1
                return
965
            else:
966 1
                if field_help is not True:
967 1
                    warnings.warn("{0} isn't a valid 'photobj_field' or "
968
                                  "'specobj_field' field, valid fields are"
969
                                  "returned.".format(field_help))
970 1
                return {'photoobj_all': photoobj_all,
971
                        'specobj_all': specobj_all}
972

973
        # Construct SQL query
974 1
        q_select = 'SELECT DISTINCT '
975 1
        q_select_field = []
976 1
        if photoobj_fields is None and specobj_fields is None:
977
            # Fields to return
978 1
            if fields is None:
979 1
                photoobj_fields = photoobj_defs
980 1
                if spectro:
981 1
                    specobj_fields = specobj_defs
982
            else:
983 1
                for sql_field in fields:
984 1
                    if (sql_field in photoobj_all
985
                            or sql_field.lower() in photoobj_all):
986 1
                        q_select_field.append('p.{0}'.format(sql_field))
987 0
                    elif (sql_field in specobj_all
988
                            or sql_field.lower() in specobj_all):
989 0
                        q_select_field.append('s.{0}'.format(sql_field))
990

991 1
        if photoobj_fields is not None:
992 1
            for sql_field in photoobj_fields:
993 1
                q_select_field.append('p.{0}'.format(sql_field))
994 1
        if specobj_fields is not None:
995 1
            for sql_field in specobj_fields:
996 1
                q_select_field.append('s.{0}'.format(sql_field))
997 1
        q_select += ', '.join(q_select_field)
998

999 1
        q_from = 'FROM PhotoObjAll AS p '
1000 1
        if spectro:
1001 1
            q_join = 'JOIN SpecObjAll s ON p.objID = s.bestObjID '
1002
        else:
1003 1
            q_join = ''
1004

1005 1
        q_where = 'WHERE '
1006 1
        if coordinates is not None:
1007 1
            if (not isinstance(coordinates, list) and
1008
                not isinstance(coordinates, Column) and
1009
                not (isinstance(coordinates, commons.CoordClasses) and
1010
                     not coordinates.isscalar)):
1011 1
                coordinates = [coordinates]
1012 1
            for n, target in enumerate(coordinates):
1013
                # Query for a region
1014 1
                target = commons.parse_coordinates(target).transform_to('fk5')
1015

1016 1
                ra = target.ra.degree
1017 1
                dec = target.dec.degree
1018 1
                dr = coord.Angle(radius).to('degree').value
1019 1
                if n > 0:
1020 1
                    q_where += ' or '
1021 1
                q_where += ('((p.ra between %g and %g) and '
1022
                            '(p.dec between %g and %g))'
1023
                            % (ra - dr, ra + dr, dec - dr, dec + dr))
1024 1
        elif spectro:
1025
            # Spectra: query for specified plate, mjd, fiberid
1026 1
            s_fields = ['s.%s=%d' % (key, val) for (key, val) in
1027
                        [('plate', plate), ('mjd', mjd), ('fiberid', fiberID)]
1028
                        if val is not None]
1029 1
            if s_fields:
1030 1
                q_where = 'WHERE (' + ' AND '.join(s_fields) + ')'
1031 1
        elif run or camcol or field:
1032
            # Imaging: query for specified run, rerun, camcol, field
1033 1
            p_fields = ['p.%s=%d' % (key, val) for (key, val) in
1034
                        [('run', run), ('camcol', camcol), ('field', field)]
1035
                        if val is not None]
1036 1
            if p_fields:
1037 1
                p_fields.append('p.rerun=%d' % rerun)
1038 1
                q_where = 'WHERE (' + ' AND '.join(p_fields) + ')'
1039

1040 1
        if not q_where:
1041 0
            if spectro:
1042 0
                raise ValueError('must specify at least one of `coordinates`, '
1043
                                 '`plate`, `mjd` or `fiberID`')
1044
            else:
1045 0
                raise ValueError('must specify at least one of `coordinates`, '
1046
                                 '`run`, `camcol` or `field`')
1047

1048 1
        sql = "{0} {1} {2} {3}".format(q_select, q_from, q_join, q_where)
1049

1050 1
        request_payload = dict(cmd=sql, format='csv')
1051

1052 1
        if data_release > 11:
1053 1
            request_payload['searchtool'] = 'SQL'
1054

1055 1
        return request_payload
1056

1057 1
    def _get_query_url(self, data_release):
1058 1
        if data_release < 10:
1059 1
            suffix = self.QUERY_URL_SUFFIX_DR_OLD
1060 1
        elif data_release == 10:
1061 1
            suffix = self.QUERY_URL_SUFFIX_DR_10
1062
        else:
1063 1
            suffix = self.QUERY_URL_SUFFIX_DR_NEW
1064

1065 1
        url = conf.skyserver_baseurl + suffix.format(dr=data_release)
1066 1
        self._last_url = url
1067 1
        return url
1068

1069 1
    def _get_crossid_url(self, data_release):
1070 1
        if data_release < 11:
1071 1
            suffix = self.XID_URL_SUFFIX_OLD
1072
        else:
1073 1
            suffix = self.XID_URL_SUFFIX_NEW
1074

1075 1
        url = conf.skyserver_baseurl + suffix.format(dr=data_release)
1076 1
        self._last_url = url
1077 1
        return url
1078

1079

1080 1
SDSS = SDSSClass()

Read our documentation on viewing source code .

Loading