Source code for astrobase.varclass.starfeatures

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# starfeatures - Waqas Bhatti (wbhatti@astro.princeton.edu) - Dec 2017
# License: MIT. See the LICENSE file for more details.

'''This calculates various features related to the color/proper-motion of stars.

All of the functions in this module require as input an 'objectinfo' dict. This
should usually be taken from a light curve file read into an `lcdict`. The
format and the minimum keys required are::

    {'objectid': the name of the object,
     'ra': the right ascension of the object in decimal degrees,
     'decl': the declination of the object in decimal degrees,
     'ndet': the number of observations of this object}

You can also provide magnitudes and proper motions of the object using the
following keys and the appropriate values in the `objectinfo` dict. These will
be used to calculate colors, total and reduced proper motion, etc.::

    'pmra' -> the proper motion in mas/yr in right ascension,
    'pmdecl' -> the proper motion in mas/yr in declination,
    'umag'  -> U mag		 -> colors: U-B, U-V, U-g
    'bmag'  -> B mag		 -> colors: U-B, B-V
    'vmag'  -> V mag		 -> colors: U-V, B-V, V-R, V-I, V-K
    'rmag'  -> R mag		 -> colors: V-R, R-I
    'imag'  -> I mag		 -> colors: g-I, V-I, R-I, B-I
    'jmag'  -> 2MASS J mag	 -> colors: J-H, J-K, g-J, i-J
    'hmag'  -> 2MASS H mag	 -> colors: J-H, H-K
    'kmag'  -> 2MASS Ks mag	 -> colors: g-Ks, H-Ks, J-Ks, V-Ks
    'sdssu' -> SDSS u mag	 -> colors: u-g, u-V
    'sdssg' -> SDSS g mag	 -> colors: g-r, g-i, g-K, u-g, U-g, g-J
    'sdssr' -> SDSS r mag	 -> colors: r-i, g-r
    'sdssi' -> SDSS i mag	 -> colors: r-i, i-z, g-i, i-J, i-W1
    'sdssz' -> SDSS z mag	 -> colors: i-z, z-W2, g-z
    'ujmag' -> UKIRT J mag	 -> colors: J-H, H-K, J-K, g-J, i-J
    'uhmag' -> UKIRT H mag	 -> colors: J-H, H-K
    'ukmag' -> UKIRT K mag	 -> colors: g-K, H-K, J-K, V-K
    'irac1' -> Spitzer IRAC1 mag -> colors: i-I1, I1-I2
    'irac2' -> Spitzer IRAC2 mag -> colors: I1-I2, I2-I3
    'irac3' -> Spitzer IRAC3 mag -> colors: I2-I3
    'irac4' -> Spitzer IRAC4 mag -> colors: I3-I4
    'wise1' -> WISE W1 mag	 -> colors: i-W1, W1-W2
    'wise2' -> WISE W2 mag	 -> colors: W1-W2, W2-W3
    'wise3' -> WISE W3 mag	 -> colors: W2-W3
    'wise4' -> WISE W4 mag	 -> colors: W3-W4

'''

#############
## LOGGING ##
#############

import logging
from astrobase import log_sub, log_fmt, log_date_fmt

DEBUG = False
if DEBUG:
    level = logging.DEBUG
else:
    level = logging.INFO
LOGGER = logging.getLogger(__name__)
logging.basicConfig(
    level=level,
    style=log_sub,
    format=log_fmt,
    datefmt=log_date_fmt,
)

LOGDEBUG = LOGGER.debug
LOGINFO = LOGGER.info
LOGWARNING = LOGGER.warning
LOGERROR = LOGGER.error
LOGEXCEPTION = LOGGER.exception


#############
## IMPORTS ##
#############

import gzip
import os.path

import numpy as np
from scipy.spatial import cKDTree, KDTree

from astropy.wcs import WCS


###################
## LOCAL IMPORTS ##
###################

from .. import magnitudes, coordutils
from ..services import dust, gaia, skyview, simbad


#########################
## COORDINATE FEATURES ##
#########################

[docs]def coord_features(objectinfo): '''Calculates object coordinates features, including: - galactic coordinates - total proper motion from pmra, pmdecl - reduced J proper motion from propermotion and Jmag Parameters ---------- objectinfo : dict This is an objectinfo dict from a light curve file read into an `lcdict`. The format and the minimum keys required are:: {'ra': the right ascension of the object in decimal degrees, 'decl': the declination of the object in decimal degrees, 'pmra': the proper motion in right ascension in mas/yr, 'pmdecl': the proper motion in declination in mas/yr, 'jmag': the 2MASS J mag of this object} Returns ------- dict A dict containing the total proper motion ''' retdict = {'propermotion': np.nan, 'gl':np.nan, 'gb':np.nan, 'rpmj':np.nan} if ('ra' in objectinfo and objectinfo['ra'] is not None and np.isfinite(objectinfo['ra']) and 'decl' in objectinfo and objectinfo['decl'] is not None and np.isfinite(objectinfo['decl'])): retdict['gl'], retdict['gb'] = coordutils.equatorial_to_galactic( objectinfo['ra'], objectinfo['decl'] ) if ('pmra' in objectinfo and objectinfo['pmra'] is not None and np.isfinite(objectinfo['pmra']) and 'pmdecl' in objectinfo and objectinfo['pmdecl'] is not None and np.isfinite(objectinfo['pmdecl']) and 'decl' in objectinfo and objectinfo['decl'] is not None and np.isfinite(objectinfo['decl'])): retdict['propermotion'] = coordutils.total_proper_motion( objectinfo['pmra'], objectinfo['pmdecl'], objectinfo['decl'] ) if ('jmag' in objectinfo and objectinfo['jmag'] is not None and np.isfinite(objectinfo['jmag']) and 'propermotion' in retdict and retdict['propermotion'] is not None and np.isfinite(retdict['propermotion'])): retdict['rpmj'] = coordutils.reduced_proper_motion( objectinfo['jmag'], retdict['propermotion'] ) return retdict
############################# ## COLOR FEATURE FUNCTIONS ## ############################# BANDPASSES_COLORS = { 'umag':{'dustkey':'CTIO U', 'label':'U', 'colors':[['umag-bmag','U - B'], ['umag-vmag','U - V'], ['umag-sdssg','U - g']]}, 'bmag':{'dustkey':'CTIO B', 'label':'B', 'colors':[['umag-bmag','U - B'], ['bmag-vmag','B - V']]}, 'vmag':{'dustkey':'CTIO V', 'label':'V', 'colors':[['umag-vmag','U - V'], ['bmag-vmag','B - V'], ['vmag-rmag','V - R'], ['vmag-imag','V - I'], ['vmag-kmag','V - K']]}, 'rmag':{'dustkey':'CTIO R', 'label':'R', 'colors':[['vmag-rmag','V - R'], ['rmag-imag','R - I']]}, 'imag':{'dustkey':'CTIO I', 'label':'I', 'colors':[['sdssg-imag','g - I'], ['vmag-imag','V - I'], ['rmag-imag','R - I'], ['bmag-imag','B - I']]}, 'jmag':{'dustkey':'2MASS J', 'label':'J', 'colors':[['jmag-hmag','J - H'], ['jmag-kmag','J - Ks'], ['sdssg-jmag','g - J'], ['sdssi-jmag','i - J']]}, 'hmag':{'dustkey':'2MASS H', 'label':'H', 'colors':[['jmag-hmag','J - H'], ['hmag-kmag','H - Ks']]}, 'kmag':{'dustkey':'2MASS Ks', 'label':'Ks', 'colors':[['sdssg-kmag','g - Ks'], ['vmag-kmag','V - Ks'], ['hmag-kmag','H - Ks'], ['jmag-kmag','J - Ks']]}, 'sdssu':{'dustkey':'SDSS u', 'label':'u', 'colors':[['sdssu-sdssg','u - g'], ['sdssu-vmag','u - V']]}, 'sdssg':{'dustkey':'SDSS g', 'label':'g', 'colors':[['sdssg-sdssr','g - r'], ['sdssg-sdssi','g - i'], ['sdssg-kmag','g - Ks'], ['sdssu-sdssg','u - g'], ['umag-sdssg','U - g'], ['sdssg-jmag','g - J']]}, 'sdssr':{'dustkey':'SDSS r', 'label':'r', 'colors':[['sdssr-sdssi','r - i'], ['sdssg-sdssr','g - r']]}, 'sdssi':{'dustkey':'SDSS i', 'label':'i', 'colors':[['sdssr-sdssi','r - i'], ['sdssi-sdssz','i - z'], ['sdssg-sdssi','g - i'], ['sdssi-jmag','i - J'], ['sdssi-wise1','i - W1']]}, 'sdssz':{'dustkey':'SDSS z', 'label':'z', 'colors':[['sdssi-sdssz','i - z'], ['sdssz-wise2','z - W2'], ['sdssg-sdssz','g - z']]}, 'ujmag':{'dustkey':'UKIRT J', 'label':'uJ', 'colors':[['ujmag-uhmag','uJ - uH'], ['ujmag-ukmag','uJ - uK'], ['sdssg-ujmag','g - uJ'], ['sdssi-ujmag','i - uJ']]}, 'uhmag':{'dustkey':'UKIRT H', 'label':'uH', 'colors':[['ujmag-uhmag','uJ - uH'], ['uhmag-ukmag','uH - uK']]}, 'ukmag':{'dustkey':'UKIRT K', 'label':'uK', 'colors':[['sdssg-ukmag','g - uK'], ['vmag-ukmag','V - uK'], ['uhmag-ukmag','uH - uK'], ['ujmag-ukmag','uJ - uK']]}, 'irac1':{'dustkey':'IRAC-1', 'label':'I1', 'colors':[['sdssi-irac1','i - I1'], ['irac1-irac2','I1 - I2']]}, 'irac2':{'dustkey':'IRAC-2', 'label':'I2', 'colors':[['irac1-irac2','I1 - I2'], ['irac2-irac3','I2 - I3']]}, 'irac3':{'dustkey':'IRAC-3', 'label':'I3', 'colors':[['irac3-irac4','I3 - I4']]}, 'irac4':{'dustkey':'IRAC-4', 'label':'I4', 'colors':[['irac3-irac4','I3 - I4']]}, 'wise1':{'dustkey':'WISE-1', 'label':'W1', 'colors':[['sdssi-wise1','i - W1'], ['wise1-wise2','W1 - W2']]}, 'wise2':{'dustkey':'WISE-2', 'label':'W2', 'colors':[['wise1-wise2','W1 - W2'], ['wise2-wise3','W2 - W3']]}, 'wise3':{'dustkey':None, 'label':'W3', 'colors':[['wise2-wise3','W2 - W3']]}, 'wise4':{'dustkey':None, 'label':'W4', 'colors':[['wise3-wise4','W3 - W4']]}, 'tessmag':{'dustkey':None, 'label':'T', 'colors':[ ['tessmag-jmag', 'T - J'], ['tessmag-kmag', 'T - Ks'], ]}, 'kepmag':{'dustkey':None, 'label':'Kp', 'colors':[ ['kepmag-jmag', 'Kp - J'], ['kepmag-kmag', 'Kp - Ks'] ]} } BANDPASS_LIST = [ 'umag','bmag','vmag','rmag','imag', 'jmag','hmag','kmag', 'sdssu','sdssg','sdssr','sdssi','sdssz', 'ujmag','uhmag','ukmag', 'irac1','irac2','irac3','irac4', 'wise1','wise2','wise3','wise4', 'tessmag', 'kepmag', ]
[docs]def color_features(in_objectinfo, deredden=True, custom_bandpasses=None, dust_timeout=10.0): '''Stellar colors and dereddened stellar colors using 2MASS DUST API: http://irsa.ipac.caltech.edu/applications/DUST/docs/dustProgramInterface.html Parameters ---------- in_objectinfo : dict This is a dict that contains the object's magnitudes and positions. This requires at least 'ra', and 'decl' as keys which correspond to the right ascension and declination of the object, and one or more of the following keys for object magnitudes:: 'umag' -> U mag -> colors: U-B, U-V, U-g 'bmag' -> B mag -> colors: U-B, B-V 'vmag' -> V mag -> colors: U-V, B-V, V-R, V-I, V-K 'rmag' -> R mag -> colors: V-R, R-I 'imag' -> I mag -> colors: g-I, V-I, R-I, B-I 'jmag' -> 2MASS J mag -> colors: J-H, J-K, g-J, i-J 'hmag' -> 2MASS H mag -> colors: J-H, H-K 'kmag' -> 2MASS Ks mag -> colors: g-Ks, H-Ks, J-Ks, V-Ks 'sdssu' -> SDSS u mag -> colors: u-g, u-V 'sdssg' -> SDSS g mag -> colors: g-r, g-i, g-K, u-g, U-g, g-J 'sdssr' -> SDSS r mag -> colors: r-i, g-r 'sdssi' -> SDSS i mag -> colors: r-i, i-z, g-i, i-J, i-W1 'sdssz' -> SDSS z mag -> colors: i-z, z-W2, g-z 'ujmag' -> UKIRT J mag -> colors: J-H, H-K, J-K, g-J, i-J 'uhmag' -> UKIRT H mag -> colors: J-H, H-K 'ukmag' -> UKIRT K mag -> colors: g-K, H-K, J-K, V-K 'irac1' -> Spitzer IRAC1 mag -> colors: i-I1, I1-I2 'irac2' -> Spitzer IRAC2 mag -> colors: I1-I2, I2-I3 'irac3' -> Spitzer IRAC3 mag -> colors: I2-I3 'irac4' -> Spitzer IRAC4 mag -> colors: I3-I4 'wise1' -> WISE W1 mag -> colors: i-W1, W1-W2 'wise2' -> WISE W2 mag -> colors: W1-W2, W2-W3 'wise3' -> WISE W3 mag -> colors: W2-W3 'wise4' -> WISE W4 mag -> colors: W3-W4 These are basically taken from the available reddening bandpasses from the 2MASS DUST service. If B, V, u, g, r, i, z aren't provided but 2MASS J, H, Ks are all provided, the former will be calculated using the 2MASS JHKs -> BVugriz conversion functions in :py:mod:`astrobase.magnitudes`. deredden : bool If True, will make sure all colors use dereddened mags where possible. custom_bandpasses : dict This is a dict used to define any custom bandpasses in the `in_objectinfo` dict you want to make this function aware of and generate colors for. Use the format below for this dict:: { '<bandpass_key_1>':{'dustkey':'<twomass_dust_key_1>', 'label':'<band_label_1>' 'colors':[['<bandkey1>-<bandkey2>', '<BAND1> - <BAND2>'], ['<bandkey3>-<bandkey4>', '<BAND3> - <BAND4>']]}, . ... . '<bandpass_key_N>':{'dustkey':'<twomass_dust_key_N>', 'label':'<band_label_N>' 'colors':[['<bandkey1>-<bandkey2>', '<BAND1> - <BAND2>'], ['<bandkey3>-<bandkey4>', '<BAND3> - <BAND4>']]}, } Where: `bandpass_key` is a key to use to refer to this bandpass in the `objectinfo` dict, e.g. 'sdssg' for SDSS g band `twomass_dust_key` is the key to use in the 2MASS DUST result table for reddening per band-pass. For example, given the following DUST result table (using http://irsa.ipac.caltech.edu/applications/DUST/):: |Filter_name|LamEff |A_over_E_B_V_SandF|A_SandF|A_over_E_B_V_SFD|A_SFD| |char |float |float |float |float |float| | |microns| |mags | |mags | CTIO U 0.3734 4.107 0.209 4.968 0.253 CTIO B 0.4309 3.641 0.186 4.325 0.221 CTIO V 0.5517 2.682 0.137 3.240 0.165 . . ... The `twomass_dust_key` for 'vmag' would be 'CTIO V'. If you want to skip DUST lookup and want to pass in a specific reddening magnitude for your bandpass, use a float for the value of `twomass_dust_key`. If you want to skip DUST lookup entirely for this bandpass, use None for the value of `twomass_dust_key`. `band_label` is the label to use for this bandpass, e.g. 'W1' for WISE-1 band, 'u' for SDSS u, etc. The 'colors' list contains color definitions for all colors you want to generate using this bandpass. this list contains elements of the form:: ['<bandkey1>-<bandkey2>','<BAND1> - <BAND2>'] where the the first item is the bandpass keys making up this color, and the second item is the label for this color to be used by the frontends. An example:: ['sdssu-sdssg','u - g'] dust_timeout : float The timeout to use when contacting the 2MASS DUST web service. Returns ------- dict An `objectinfo` dict with all of the generated colors, dereddened magnitude,s dereddened colors, as specified in the input args is returned. ''' objectinfo = in_objectinfo.copy() # this is the initial output dict outdict = { 'available_bands':[], 'available_band_labels':[], 'available_dereddened_bands':[], 'available_dereddened_band_labels':[], 'available_colors':[], 'available_color_labels':[], 'dereddened':False } # # get the BVugriz mags from the JHK mags if necessary # # FIXME: should these be direct dered mag_0 = f(J_0, H_0, K_0) instead? # Bilir+ 2008 uses dereddened colors for their transforms, should check if # we need to do so here if ('jmag' in objectinfo and objectinfo['jmag'] is not None and np.isfinite(objectinfo['jmag']) and 'hmag' in objectinfo and objectinfo['hmag'] is not None and np.isfinite(objectinfo['hmag']) and 'kmag' in objectinfo and objectinfo['kmag'] is not None and np.isfinite(objectinfo['kmag'])): if ('bmag' not in objectinfo or ('bmag' in objectinfo and objectinfo['bmag'] is None) or ('bmag' in objectinfo and not np.isfinite(objectinfo['bmag']))): objectinfo['bmag'] = magnitudes.jhk_to_bmag(objectinfo['jmag'], objectinfo['hmag'], objectinfo['kmag']) outdict['bmagfromjhk'] = True else: outdict['bmagfromjhk'] = False if ('vmag' not in objectinfo or ('vmag' in objectinfo and objectinfo['vmag'] is None) or ('vmag' in objectinfo and not np.isfinite(objectinfo['vmag']))): objectinfo['vmag'] = magnitudes.jhk_to_vmag(objectinfo['jmag'], objectinfo['hmag'], objectinfo['kmag']) outdict['vmagfromjhk'] = True else: outdict['vmagfromjhk'] = False if ('sdssu' not in objectinfo or ('sdssu' in objectinfo and objectinfo['sdssu'] is None) or ('sdssu' in objectinfo and not np.isfinite(objectinfo['sdssu']))): objectinfo['sdssu'] = magnitudes.jhk_to_sdssu(objectinfo['jmag'], objectinfo['hmag'], objectinfo['kmag']) outdict['sdssufromjhk'] = True else: outdict['sdssufromjhk'] = False if ('sdssg' not in objectinfo or ('sdssg' in objectinfo and objectinfo['sdssg'] is None) or ('sdssg' in objectinfo and not np.isfinite(objectinfo['sdssg']))): objectinfo['sdssg'] = magnitudes.jhk_to_sdssg(objectinfo['jmag'], objectinfo['hmag'], objectinfo['kmag']) outdict['sdssgfromjhk'] = True else: outdict['sdssgfromjhk'] = False if ('sdssr' not in objectinfo or ('sdssr' in objectinfo and objectinfo['sdssr'] is None) or ('sdssr' in objectinfo and not np.isfinite(objectinfo['sdssr']))): objectinfo['sdssr'] = magnitudes.jhk_to_sdssr(objectinfo['jmag'], objectinfo['hmag'], objectinfo['kmag']) outdict['sdssrfromjhk'] = True else: outdict['sdssrfromjhk'] = False if ('sdssi' not in objectinfo or ('sdssi' in objectinfo and objectinfo['sdssi'] is None) or ('sdssi' in objectinfo and not np.isfinite(objectinfo['sdssi']))): objectinfo['sdssi'] = magnitudes.jhk_to_sdssi(objectinfo['jmag'], objectinfo['hmag'], objectinfo['kmag']) outdict['sdssifromjhk'] = True else: outdict['sdssifromjhk'] = False if ('sdssz' not in objectinfo or ('sdssz' in objectinfo and objectinfo['sdssz'] is None) or ('sdssz' in objectinfo and not np.isfinite(objectinfo['sdssz']))): objectinfo['sdssz'] = magnitudes.jhk_to_sdssz(objectinfo['jmag'], objectinfo['hmag'], objectinfo['kmag']) outdict['sdsszfromjhk'] = True else: outdict['sdsszfromjhk'] = False # now handle dereddening if possible if deredden: try: # first, get the extinction table for this object extinction = dust.extinction_query(objectinfo['ra'], objectinfo['decl'], verbose=False, timeout=dust_timeout) except Exception as e: LOGERROR("deredden = True but 'ra', 'decl' keys not present " "or invalid in objectinfo dict, ignoring reddening...") extinction = None outdict['dereddened'] = False else: extinction = None outdict['dereddened'] = False # handle timeout from DUST service if not extinction: outdict['dereddened'] = False # go through the objectdict and pick out the mags we have available from the # BANDPASSES_COLORS dict # update our bandpasses_colors dict with any custom ones the user defined our_bandpasses_colors = BANDPASSES_COLORS.copy() our_bandpass_list = BANDPASS_LIST[::] if custom_bandpasses is not None and isinstance(custom_bandpasses, dict): our_bandpasses_colors.update(custom_bandpasses) # also update the list for key in custom_bandpasses: if key not in our_bandpass_list: our_bandpass_list.append(key) for mk in our_bandpass_list: if (mk in objectinfo and objectinfo[mk] is not None and np.isfinite(objectinfo[mk])): thisbandlabel = our_bandpasses_colors[mk]['label'] thisdustkey = our_bandpasses_colors[mk]['dustkey'] # add this to the outdict outdict[mk] = objectinfo[mk] outdict['available_bands'].append(mk) outdict['available_band_labels'].append(thisbandlabel) # # deredden if possible # # calculating dereddened mags: # A_x = m - m0_x where m is measured mag, m0 is intrinsic mag # m0_x = m - A_x # # so for two bands x, y: # intrinsic color (m_x - m_y)_0 = (m_x - m_y) - (A_x - A_y) if (deredden and extinction): outdict['dereddened'] = True # check if the dustkey is None, float, or str to figure out how # to retrieve the reddening if (thisdustkey is not None and isinstance(thisdustkey, str) and thisdustkey in extinction['Amag'] and np.isfinite(extinction['Amag'][thisdustkey]['sf11'])): outdict['extinction_%s' % mk] = ( extinction['Amag'][thisdustkey]['sf11'] ) elif (thisdustkey is not None and isinstance(thisdustkey, float)): outdict['extinction_%s' % mk] = thisdustkey else: outdict['extinction_%s' % mk] = 0.0 # apply the extinction outdict['dered_%s' % mk] = ( outdict[mk] - outdict['extinction_%s' % mk] ) outdict['available_dereddened_bands'].append('dered_%s' % mk) outdict['available_dereddened_band_labels'].append( thisbandlabel ) # get all the colors to generate for this bandpass for colorspec in BANDPASSES_COLORS[mk]['colors']: # only add this if the color's not there already if colorspec[0] not in outdict: colorkey, colorlabel = colorspec # look for the bands to make this color # if it's not found now, this should work when we come # around for the next bandpass for this color band1, band2 = colorkey.split('-') if ('dered_%s' % band1 in outdict and 'dered_%s' % band2 in outdict and np.isfinite(outdict['dered_%s' % band1]) and np.isfinite(outdict['dered_%s' % band2])): outdict[colorkey] = ( outdict['dered_%s' % band1] - outdict['dered_%s' % band2] ) outdict['available_colors'].append(colorkey) outdict['available_color_labels'].append(colorlabel) # handle no dereddening else: outdict['dereddened'] = False outdict['extinction_%s' % mk] = 0.0 outdict['dered_%s' % mk] = np.nan # get all the colors to generate for this bandpass for colorspec in our_bandpasses_colors[mk]['colors']: # only add this if the color's not there already if colorspec[0] not in outdict: colorkey, colorlabel = colorspec # look for the bands to make this color # if it's not found now, this should work when we come # around for the next bandpass for this color band1, band2 = colorkey.split('-') if (band1 in outdict and band2 in outdict and outdict[band1] is not None and outdict[band2] is not None and np.isfinite(outdict[band1]) and np.isfinite(outdict[band2])): outdict[colorkey] = ( outdict[band1] - outdict[band2] ) outdict['available_colors'].append(colorkey) outdict['available_color_labels'].append(colorlabel) # if this bandpass was not found in the objectinfo dict, ignore it else: outdict[mk] = np.nan return outdict
[docs]def mdwarf_subtype_from_sdsscolor(ri_color, iz_color): '''This calculates the M-dwarf subtype given SDSS `r-i` and `i-z` colors. Parameters ---------- ri_color : float The SDSS `r-i` color of the object. iz_color : float The SDSS `i-z` color of the object. Returns ------- (subtype, index1, index2) : tuple `subtype`: if the star appears to be an M dwarf, will return an int between 0 and 9 indicating its subtype, e.g. will return 4 for an M4 dwarf. If the object isn't an M dwarf, will return None `index1`, `index2`: the M-dwarf color locus value and spread of this object calculated from the `r-i` and `i-z` colors. ''' # calculate the spectral type index and the spectral type spread of the # object. sti is calculated by fitting a line to the locus in r-i and i-z # space for M dwarfs in West+ 2007 if np.isfinite(ri_color) and np.isfinite(iz_color): obj_sti = 0.875274*ri_color + 0.483628*(iz_color + 0.00438) obj_sts = -0.483628*ri_color + 0.875274*(iz_color + 0.00438) else: obj_sti = np.nan obj_sts = np.nan # possible M star if sti is >= 0.666 but <= 3.4559 if (np.isfinite(obj_sti) and np.isfinite(obj_sts) and (obj_sti > 0.666) and (obj_sti < 3.4559)): # decide which M subclass object this is if ((obj_sti > 0.6660) and (obj_sti < 0.8592)): m_class = 'M0' if ((obj_sti > 0.8592) and (obj_sti < 1.0822)): m_class = 'M1' if ((obj_sti > 1.0822) and (obj_sti < 1.2998)): m_class = 'M2' if ((obj_sti > 1.2998) and (obj_sti < 1.6378)): m_class = 'M3' if ((obj_sti > 1.6378) and (obj_sti < 2.0363)): m_class = 'M4' if ((obj_sti > 2.0363) and (obj_sti < 2.2411)): m_class = 'M5' if ((obj_sti > 2.2411) and (obj_sti < 2.4126)): m_class = 'M6' if ((obj_sti > 2.4126) and (obj_sti < 2.9213)): m_class = 'M7' if ((obj_sti > 2.9213) and (obj_sti < 3.2418)): m_class = 'M8' if ((obj_sti > 3.2418) and (obj_sti < 3.4559)): m_class = 'M9' else: m_class = None return m_class, obj_sti, obj_sts
[docs]def color_classification(colorfeatures, pmfeatures): '''This calculates rough star type classifications based on star colors in the ugrizJHK bands. Uses the output from `color_features` and `coord_features`. By default, `color_features` will use dereddened colors, as are expected by most relations here. Based on the color cuts from: - SDSS SEGUE (Yanny+ 2009) - SDSS QSO catalog (Schneider+ 2007) - SDSS RR Lyrae catalog (Sesar+ 2011) - SDSS M-dwarf catalog (West+ 2008) - Helmi+ 2003 - Bochanski+ 2014 Parameters ---------- colorfeatures : dict This is the dict produced by the `color_features` function. pmfeatures : dict This is the dict produced by the `coord_features` function. Returns ------- dict A dict containing all of the possible classes this object can belong to as a list in the `color_classes` key, and values of the various color indices used to arrive to that conclusion as the other keys. ''' possible_classes = [] if not colorfeatures: return possible_classes if not pmfeatures: return possible_classes # dered mags if ( ('dered_sdssu' in colorfeatures) and (colorfeatures['dered_sdssu'] is not None) and (np.isfinite(colorfeatures['dered_sdssu'])) ): u = colorfeatures['dered_sdssu'] else: u = np.nan if ( ('dered_sdssg' in colorfeatures) and (colorfeatures['dered_sdssg'] is not None) and (np.isfinite(colorfeatures['dered_sdssg'])) ): g = colorfeatures['dered_sdssg'] else: g = np.nan if ( ('dered_sdssr' in colorfeatures) and (colorfeatures['dered_sdssr'] is not None) and (np.isfinite(colorfeatures['dered_sdssr'])) ): r = colorfeatures['dered_sdssr'] else: r = np.nan if ( ('dered_sdssi' in colorfeatures) and (colorfeatures['dered_sdssi'] is not None) and (np.isfinite(colorfeatures['dered_sdssi'])) ): i = colorfeatures['dered_sdssi'] else: i = np.nan if ( ('dered_sdssz' in colorfeatures) and (colorfeatures['dered_sdssz'] is not None) and (np.isfinite(colorfeatures['dered_sdssz'])) ): z = colorfeatures['dered_sdssz'] else: z = np.nan if ( ('dered_jmag' in colorfeatures) and (colorfeatures['dered_jmag'] is not None) and (np.isfinite(colorfeatures['dered_jmag'])) ): j = colorfeatures['dered_jmag'] else: j = np.nan if ( ('dered_hmag' in colorfeatures) and (colorfeatures['dered_hmag'] is not None) and (np.isfinite(colorfeatures['dered_hmag'])) ): h = colorfeatures['dered_hmag'] else: h = np.nan if ( ('dered_kmag' in colorfeatures) and (colorfeatures['dered_kmag'] is not None) and (np.isfinite(colorfeatures['dered_kmag'])) ): k = colorfeatures['dered_kmag'] else: k = np.nan # measured mags if 'sdssu' in colorfeatures and colorfeatures['sdssu'] is not None: um = colorfeatures['sdssu'] else: um = np.nan if 'sdssg' in colorfeatures and colorfeatures['sdssg'] is not None: gm = colorfeatures['sdssg'] else: gm = np.nan if 'sdssr' in colorfeatures and colorfeatures['sdssr'] is not None: rm = colorfeatures['sdssr'] else: rm = np.nan if 'sdssi' in colorfeatures and colorfeatures['sdssi'] is not None: im = colorfeatures['sdssi'] else: im = np.nan if 'sdssz' in colorfeatures and colorfeatures['sdssz'] is not None: zm = colorfeatures['sdssz'] else: zm = np.nan if 'jmag' in colorfeatures and colorfeatures['jmag'] is not None: jm = colorfeatures['jmag'] else: jm = np.nan if 'hmag' in colorfeatures and colorfeatures['hmag'] is not None: hm = colorfeatures['hmag'] else: hm = np.nan if 'kmag' in colorfeatures and colorfeatures['kmag'] is not None: km = colorfeatures['kmag'] else: km = np.nan # reduced proper motion rpmj = pmfeatures['rpmj'] if np.isfinite(pmfeatures['rpmj']) else None # now generate the various color indices # color-gravity index if (np.isfinite(u) and np.isfinite(g) and np.isfinite(r) and np.isfinite(i) and np.isfinite(z)): v_color = 0.283*(u-g)-0.354*(g-r)+0.455*(r-i)+0.766*(i-z) else: v_color = np.nan # metallicity index p1 if (np.isfinite(u) and np.isfinite(g) and np.isfinite(r)): p1_color = 0.91*(u-g)+0.415*(g-r)-1.28 else: p1_color = np.nan # metallicity index l if (np.isfinite(u) and np.isfinite(g) and np.isfinite(r) and np.isfinite(i)): l_color = -0.436*u + 1.129*g - 0.119*r - 0.574*i + 0.1984 else: l_color = np.nan # metallicity index s if (np.isfinite(u) and np.isfinite(g) and np.isfinite(r)): s_color = -0.249*u + 0.794*g - 0.555*r + 0.124 else: s_color = np.nan # RR Lyrae ug and gr indexes if (np.isfinite(u) and np.isfinite(g) and np.isfinite(r)): d_ug = (u-g) + 0.67*(g-r) - 1.07 d_gr = 0.45*(u-g) - (g-r) - 0.12 else: d_ug, d_gr = np.nan, np.nan # check the M subtype m_subtype, m_sti, m_sts = mdwarf_subtype_from_sdsscolor(r-i, i-z) # now check if this is a likely M dwarf if m_subtype and rpmj and rpmj > 1.0: possible_classes.append('d' + m_subtype) # white dwarf if ( np.isfinite(u) and np.isfinite(g) and np.isfinite(r) and ((g-r) < -0.2) and ((g-r) > -1.0) and ((u-g) < 0.7) and ((u-g) > -1) and ((u-g+2*(g-r)) < -0.1) ): possible_classes.append('WD/sdO/sdB') # A/BHB/BStrg if ( np.isfinite(u) and np.isfinite(g) and np.isfinite(r) and ((u-g) < 1.5) and ((u-g) > 0.8) and ((g-r) < 0.2) and ((g-r) > -0.5) ): possible_classes.append('A/BHB/blustrg') # F turnoff/sub-dwarf if ( (np.isfinite(p1_color) and np.isfinite(p1_color) and np.isfinite(u) and np.isfinite(g) and np.isfinite(r) ) and (p1_color < -0.25) and (p1_color > -0.7) and ((u-g) < 1.4) and ((u-g) > 0.4) and ((g-r) < 0.7) and ((g-r) > 0.2) ): possible_classes.append('Fturnoff/sdF') # low metallicity if ( (np.isfinite(u) and np.isfinite(g) and np.isfinite(r) and np.isfinite(l_color)) and ((g-r) < 0.75) and ((g-r) > -0.5) and ((u-g) < 3.0) and ((u-g) > 0.6) and (l_color > 0.135) ): possible_classes.append('lowmetal') # low metallicity giants from Helmi+ 2003 if ( (np.isfinite(p1_color) and np.isfinite(s_color)) and (-0.1 < p1_color < 0.6) and (s_color > 0.05) ): possible_classes.append('lowmetalgiant') # F/G star if ( (np.isfinite(g) and np.isfinite(g) and np.isfinite(r)) and ((g-r) < 0.48) and ((g-r) > 0.2) ): possible_classes.append('F/G') # G dwarf if ( (np.isfinite(g) and np.isfinite(r)) and ((g-r) < 0.55) and ((g-r) > 0.48) ): possible_classes.append('dG') # K giant if ( (np.isfinite(u) and np.isfinite(g) and np.isfinite(r) and np.isfinite(i) and np.isfinite(l_color)) and ((g-r) > 0.35) and ((g-r) < 0.7) and (l_color > 0.07) and ((u-g) > 0.7) and ((u-g) < 4.0) and ((r-i) > 0.15) and ((r-i) < 0.6) ): possible_classes.append('gK') # AGB if ( (np.isfinite(u) and np.isfinite(g) and np.isfinite(r) and np.isfinite(s_color)) and ((u-g) < 3.5) and ((u-g) > 2.5) and ((g-r) < 1.3) and ((g-r) > 0.9) and (s_color < -0.06) ): possible_classes.append('AGB') # K dwarf if ( (np.isfinite(g) and np.isfinite(r)) and ((g-r) < 0.75) and ((g-r) > 0.55) ): possible_classes.append('dK') # M subdwarf if ( (np.isfinite(g) and np.isfinite(r) and np.isfinite(i)) and ((g-r) > 1.6) and ((r-i) < 1.3) and ((r-i) > 0.95) ): possible_classes.append('sdM') # M giant colors from Bochanski+ 2014 if ( (np.isfinite(j) and np.isfinite(h) and np.isfinite(k) and np.isfinite(g) and np.isfinite(i)) and ((j-k) > 1.02) and ((j-h) < (0.561*(j-k) + 0.46)) and ((j-h) > (0.561*(j-k) + 0.14)) and ((g-i) > (0.932*(i-k) - 0.872)) ): possible_classes.append('gM') # MS+WD pair if ( (np.isfinite(um) and np.isfinite(gm) and np.isfinite(rm) and np.isfinite(im)) and ((um-gm) < 2.25) and ((gm-rm) > -0.2) and ((gm-rm) < 1.2) and ((rm-im) > 0.5) and ((rm-im) < 2.0) and ((gm-rm) > (-19.78*(rm-im)+11.13)) and ((gm-rm) < (0.95*(rm-im)+0.5)) ): possible_classes.append('MSWD') # brown dwarf if ( (np.isfinite(um) and np.isfinite(gm) and np.isfinite(rm) and np.isfinite(im) and np.isfinite(zm)) and (zm < 19.5) and (um > 21.0) and (gm > 22.0) and (rm > 21.0) and ((im - zm) > 1.7) ): possible_classes.append('BD') # RR Lyrae candidate if ( (np.isfinite(u) and np.isfinite(g) and np.isfinite(r) and np.isfinite(i) and np.isfinite(z) and np.isfinite(d_ug) and np.isfinite(d_gr)) and ((u-g) > 0.98) and ((u-g) < 1.3) and (d_ug > -0.05) and (d_ug < 0.35) and (d_gr > 0.06) and (d_gr < 0.55) and ((r-i) > -0.15) and ((r-i) < 0.22) and ((i-z) > -0.21) and ((i-z) < 0.25) ): possible_classes.append('RRL') # QSO color if ( (np.isfinite(u) and np.isfinite(g) and np.isfinite(r)) and ( (((u-g) > -0.1) and ((u-g) < 0.7) and ((g-r) > -0.3) and ((g-r) < 0.5)) or ((u-g) > (1.6*(g-r) + 1.34)) ) ): possible_classes.append('QSO') return {'color_classes':possible_classes, 'v_color':v_color, 'p1_color':p1_color, 's_color':s_color, 'l_color':l_color, 'd_ug':d_ug, 'd_gr':d_gr, 'm_sti':m_sti, 'm_sts':m_sts}
[docs]def neighbor_gaia_features(objectinfo, lclist_kdtree, neighbor_radius_arcsec, gaia_matchdist_arcsec=3.0, verbose=True, gaia_submit_timeout=10.0, gaia_submit_tries=3, gaia_max_timeout=180.0, gaia_mirror=None, gaia_data_release='dr2', complete_query_later=True, search_simbad=False): '''Gets several neighbor, GAIA, and SIMBAD features: From the KD-Tree in the given light curve catalog the object is in: `lclist_kdtree`: - distance to closest neighbor in arcsec - total number of all neighbors within 2 x `neighbor_radius_arcsec` From the GAIA DR2 catalog: - distance to closest neighbor in arcsec - total number of all neighbors within 2 x `neighbor_radius_arcsec` - gets the parallax for the object and neighbors - calculates the absolute GAIA mag and `G-K` color for use in CMDs - gets the proper motion in RA/Dec if available From the SIMBAD catalog: - the name of the object - the type of the object Parameters ---------- objectinfo : dict This is the objectinfo dict from an object's light curve. This must contain at least the following keys:: {'ra': the right ascension of the object, 'decl': the declination of the object} lclist_kdtree : scipy.spatial.cKDTree object This is a KD-Tree built on the Cartesian xyz coordinates from (ra, dec) of all objects in the same field as this object. It is similar to that produced by :py:func:`astrobase.lcproc.catalogs.make_lclist`, and is used to carry out the spatial search required to find neighbors for this object. neighbor_radius_arcsec : float The maximum radius in arcseconds around this object to search for neighbors in both the light curve catalog and in the GAIA DR2 catalog. gaia_matchdist_arcsec : float The maximum distance in arcseconds to use for a GAIA cross-match to this object. verbose : bool If True, indicates progress and warns of problems. gaia_submit_timeout : float Sets the timeout in seconds to use when submitting a request to look up the object's information to the GAIA service. Note that if `fast_mode` is set, this is ignored. gaia_submit_tries : int Sets the maximum number of times the GAIA services will be contacted to obtain this object's information. If `fast_mode` is set, this is ignored, and the services will be contacted only once (meaning that a failure to respond will be silently ignored and no GAIA data will be added to the checkplot's objectinfo dict). gaia_max_timeout : float Sets the timeout in seconds to use when waiting for the GAIA service to respond to our request for the object's information. Note that if `fast_mode` is set, this is ignored. gaia_mirror : str This sets the GAIA mirror to use. This is a key in the `services.gaia.GAIA_URLS` dict which defines the URLs to hit for each mirror. gaia_data_release: {'dr2', 'edr3'} The Gaia data release to use for the query. This provides hints for which table to use for the GAIA mirror being queried. search_simbad : bool If this is True, searches for objects in SIMBAD at this object's location and gets the object's SIMBAD main ID, type, and stellar classification if available. Returns ------- dict Returns a dict with neighbor, GAIA, and SIMBAD features. ''' # kdtree search for neighbors in light curve catalog if ('ra' in objectinfo and 'decl' in objectinfo and objectinfo['ra'] is not None and objectinfo['decl'] is not None and (isinstance(lclist_kdtree, cKDTree) or isinstance(lclist_kdtree, KDTree))): ra, decl = objectinfo['ra'], objectinfo['decl'] cosdecl = np.cos(np.radians(decl)) sindecl = np.sin(np.radians(decl)) cosra = np.cos(np.radians(ra)) sinra = np.sin(np.radians(ra)) # this is the search distance in xyz unit vectors xyzdist = 2.0 * np.sin(np.radians(neighbor_radius_arcsec/3600.0)/2.0) # look up the coordinates for the closest 100 objects in the kdtree # within 2 x neighbor_radius_arcsec kdt_dist, kdt_ind = lclist_kdtree.query( [cosra*cosdecl, sinra*cosdecl, sindecl], k=100, distance_upper_bound=xyzdist ) # the first match is the object itself finite_distind = (np.isfinite(kdt_dist)) & (kdt_dist > 0) finite_dists = kdt_dist[finite_distind] nbrindices = kdt_ind[finite_distind] n_neighbors = finite_dists.size if n_neighbors > 0: closest_dist = finite_dists.min() closest_dist_arcsec = ( np.degrees(2.0*np.arcsin(closest_dist/2.0))*3600.0 ) closest_dist_nbrind = nbrindices[finite_dists == finite_dists.min()] resultdict = { 'neighbors':n_neighbors, 'nbrindices':nbrindices, 'distarcsec':np.degrees(2.0*np.arcsin(finite_dists/2.0))*3600.0, 'closestdistarcsec':closest_dist_arcsec, 'closestdistnbrind':closest_dist_nbrind, 'searchradarcsec':neighbor_radius_arcsec, } else: resultdict = { 'neighbors':0, 'nbrindices':np.array([]), 'distarcsec':np.array([]), 'closestdistarcsec':np.nan, 'closestdistnbrind':np.array([]), 'searchradarcsec':neighbor_radius_arcsec, } else: if verbose: LOGWARNING("one of ra, decl, kdtree is missing in " "objectinfo dict or lclistpkl, " "can't get observed neighbors") resultdict = { 'neighbors':np.nan, 'nbrindices':np.array([]), 'distarcsec':np.array([]), 'closestdistarcsec':np.nan, 'closestdistnbrind':np.array([]), 'searchradarcsec':neighbor_radius_arcsec, } # next, search for this object in GAIA if ('ra' in objectinfo and 'decl' in objectinfo and objectinfo['ra'] is not None and objectinfo['decl'] is not None): gaia_result = gaia.objectlist_conesearch( objectinfo['ra'], objectinfo['decl'], neighbor_radius_arcsec, verbose=verbose, timeout=gaia_submit_timeout, maxtimeout=gaia_max_timeout, maxtries=gaia_submit_tries, gaia_mirror=gaia_mirror, data_release=gaia_data_release, complete_query_later=complete_query_later ) if gaia_result: gaia_objlistf = gaia_result['result'] with gzip.open(gaia_objlistf,'rb') as infd: try: gaia_objlist = np.genfromtxt( infd, names=True, delimiter=',', dtype='U20,f8,f8,f8,f8,f8,f8,f8,f8,f8,f8,f8,f8', usecols=(0,1,2,3,4,5,6,7,8,9,10,11,12) ) except Exception as e: gaia_objlist = [] gaia_objlist = np.atleast_1d(gaia_objlist) if gaia_objlist.size > 0: # if we have GAIA results, we can get xypositions of all of # these objects on the object skyview stamp stampres = skyview.get_stamp(objectinfo['ra'], objectinfo['decl']) if (stampres and 'fitsfile' in stampres and stampres['fitsfile'] is not None and os.path.exists(stampres['fitsfile'])): stampwcs = WCS(stampres['fitsfile']) gaia_xypos = stampwcs.all_world2pix( np.column_stack((gaia_objlist['ra'], gaia_objlist['dec'])), 1 ) else: gaia_xypos = None # the first object is likely the match to the object itself if gaia_objlist['dist_arcsec'][0] < gaia_matchdist_arcsec: if gaia_objlist.size > 1: gaia_nneighbors = gaia_objlist[1:].size gaia_status = ( 'ok: object found with %s neighbors' % gaia_nneighbors ) # the first in each array is the object gaia_ids = gaia_objlist['source_id'] gaia_mags = gaia_objlist['phot_g_mean_mag'] gaia_parallaxes = gaia_objlist['parallax'] gaia_parallax_errs = gaia_objlist['parallax_error'] gaia_pmra = gaia_objlist['pmra'] gaia_pmra_err = gaia_objlist['pmra_error'] gaia_pmdecl = gaia_objlist['pmdec'] gaia_pmdecl_err = gaia_objlist['pmdec_error'] gaia_absolute_mags = magnitudes.absolute_gaia_magnitude( gaia_mags, gaia_parallaxes ) if ('kmag' in objectinfo and objectinfo['kmag'] is not None and np.isfinite(objectinfo['kmag'])): gaiak_colors = gaia_mags - objectinfo['kmag'] else: gaiak_colors = None gaia_dists = gaia_objlist['dist_arcsec'] gaia_closest_distarcsec = gaia_objlist['dist_arcsec'][1] gaia_closest_gmagdiff = ( gaia_objlist['phot_g_mean_mag'][0] - gaia_objlist['phot_g_mean_mag'][1] ) else: LOGWARNING('object found in GAIA at (%.3f,%.3f), ' 'but no neighbors' % (objectinfo['ra'], objectinfo['decl'])) gaia_nneighbors = 0 gaia_status = ( 'ok: object found but no neighbors' ) # the first in each array is the object gaia_ids = gaia_objlist['source_id'] gaia_mags = gaia_objlist['phot_g_mean_mag'] gaia_parallaxes = gaia_objlist['parallax'] gaia_parallax_errs = gaia_objlist['parallax_error'] gaia_pmra = gaia_objlist['pmra'] gaia_pmra_err = gaia_objlist['pmra_error'] gaia_pmdecl = gaia_objlist['pmdec'] gaia_pmdecl_err = gaia_objlist['pmdec_error'] gaia_absolute_mags = magnitudes.absolute_gaia_magnitude( gaia_mags, gaia_parallaxes ) if ('kmag' in objectinfo and objectinfo['kmag'] is not None and np.isfinite(objectinfo['kmag'])): gaiak_colors = gaia_mags - objectinfo['kmag'] else: gaiak_colors = None gaia_dists = gaia_objlist['dist_arcsec'] gaia_closest_distarcsec = np.nan gaia_closest_gmagdiff = np.nan # otherwise, the object wasn't found in GAIA for some reason else: LOGWARNING('no GAIA objects found within ' '%.3f arcsec of object position (%.3f, %.3f), ' 'closest object is at %.3f arcsec away' % (gaia_matchdist_arcsec, objectinfo['ra'], objectinfo['decl'], gaia_objlist['dist_arcsec'][0])) gaia_status = ('failed: no object within %.3f ' 'arcsec, closest = %.3f arcsec' % (gaia_matchdist_arcsec, gaia_objlist['dist_arcsec'][0])) gaia_nneighbors = np.nan gaia_ids = gaia_objlist['source_id'] gaia_mags = gaia_objlist['phot_g_mean_mag'] gaia_parallaxes = gaia_objlist['parallax'] gaia_parallax_errs = gaia_objlist['parallax_error'] gaia_pmra = gaia_objlist['pmra'] gaia_pmra_err = gaia_objlist['pmra_error'] gaia_pmdecl = gaia_objlist['pmdec'] gaia_pmdecl_err = gaia_objlist['pmdec_error'] gaia_absolute_mags = magnitudes.absolute_gaia_magnitude( gaia_mags, gaia_parallaxes ) if ('kmag' in objectinfo and objectinfo['kmag'] is not None and np.isfinite(objectinfo['kmag'])): gaiak_colors = gaia_mags - objectinfo['kmag'] else: gaiak_colors = None gaia_dists = gaia_objlist['dist_arcsec'] gaia_closest_distarcsec = np.nan gaia_closest_gmagdiff = np.nan # if there are no neighbors within neighbor_radius_arcsec # or this object is not covered by GAIA. return nothing else: LOGERROR('no GAIA objects at this ' 'position or GAIA query failed') gaia_status = ( 'failed: no GAIA objects at this ' 'position or GAIA query failed.' ) gaia_nneighbors = np.nan gaia_ids = None gaia_mags = None gaia_xypos = None gaia_parallaxes = None gaia_parallax_errs = None gaia_pmra = None gaia_pmra_err = None gaia_pmdecl = None gaia_pmdecl_err = None gaia_absolute_mags = None gaiak_colors = None gaia_dists = None gaia_closest_distarcsec = np.nan gaia_closest_gmagdiff = np.nan # update the resultdict with gaia stuff resultdict.update( {'gaia_status':gaia_status, 'gaia_neighbors':gaia_nneighbors, 'gaia_ids':gaia_ids, 'gaia_xypos':gaia_xypos, 'gaia_mags':gaia_mags, 'gaia_parallaxes':gaia_parallaxes, 'gaia_parallax_errs':gaia_parallax_errs, 'gaia_pmras':gaia_pmra, 'gaia_pmra_errs':gaia_pmra_err, 'gaia_pmdecls':gaia_pmdecl, 'gaia_pmdecl_errs':gaia_pmdecl_err, 'gaia_absolute_mags':gaia_absolute_mags, 'gaiak_colors':gaiak_colors, 'gaia_dists':gaia_dists, 'gaia_closest_distarcsec':gaia_closest_distarcsec, 'gaia_closest_gmagdiff':gaia_closest_gmagdiff} ) else: LOGERROR('GAIA query did not return a ' 'result for object at (%.3f, %.3f)' % (objectinfo['ra'], objectinfo['decl'])) resultdict.update( {'gaia_status':'failed: GAIA TAP query failed', 'gaia_neighbors':np.nan, 'gaia_ids':None, 'gaia_xypos':None, 'gaia_mags':None, 'gaia_parallaxes':None, 'gaia_parallax_errs':None, 'gaia_pmras':None, 'gaia_pmra_errs':None, 'gaia_pmdecls':None, 'gaia_pmdecl_errs':None, 'gaia_absolute_mags':None, 'gaiak_colors':None, 'gaia_dists':None, 'gaia_closest_distarcsec':np.nan, 'gaia_closest_gmagdiff':np.nan} ) else: LOGERROR("one or more of the 'ra', 'decl' keys " "are missing from the objectinfo dict, " "can't get GAIA or LC collection neighbor features") resultdict.update( {'gaia_status':'failed: no ra/decl for object', 'gaia_neighbors':np.nan, 'gaia_ids':None, 'gaia_xypos':None, 'gaia_mags':None, 'gaia_parallaxes':None, 'gaia_parallax_errs':None, 'gaia_pmras':None, 'gaia_pmra_errs':None, 'gaia_pmdecls':None, 'gaia_pmdecl_errs':None, 'gaia_absolute_mags':None, 'gaiak_colors':None, 'gaia_dists':None, 'gaia_closest_distarcsec':np.nan, 'gaia_closest_gmagdiff':np.nan} ) # finally, search for this object in SIMBAD if ('ra' in objectinfo and 'decl' in objectinfo and objectinfo['ra'] is not None and objectinfo['decl'] is not None and search_simbad): simbad_result = simbad.objectnames_conesearch( objectinfo['ra'], objectinfo['decl'], neighbor_radius_arcsec, verbose=verbose, timeout=gaia_submit_timeout, maxtimeout=gaia_max_timeout, maxtries=gaia_submit_tries, complete_query_later=complete_query_later ) else: simbad_result = None if (simbad_result and simbad_result['result'] and os.path.exists(simbad_result['result'])): with gzip.open(simbad_result['result'],'rb') as infd: try: simbad_objectnames = np.genfromtxt( infd, names=True, delimiter=',', dtype='U20,f8,f8,U20,U20,U20,i8,U600,f8', usecols=(0,1,2,3,4,5,6,7,8), comments='?', ) except Exception as e: simbad_objectnames = [] simbad_objectnames = np.atleast_1d(simbad_objectnames) if simbad_objectnames.size > 0: simbad_mainid = simbad_objectnames['main_id'].tolist() simbad_allids = simbad_objectnames['all_ids'].tolist() simbad_objtype = simbad_objectnames['otype_txt'].tolist() simbad_distarcsec = simbad_objectnames['dist_arcsec'].tolist() simbad_nmatches = len(simbad_mainid) simbad_mainid = [x.replace('"','') for x in simbad_mainid] simbad_allids = [x.replace('"','') for x in simbad_allids] simbad_objtype = [x.replace('"','') for x in simbad_objtype] resultdict.update({ 'simbad_nmatches':simbad_nmatches, 'simbad_mainid':simbad_mainid, 'simbad_objtype':simbad_objtype, 'simbad_allids':simbad_allids, 'simbad_distarcsec':simbad_distarcsec }) if simbad_nmatches > 1: resultdict['simbad_status'] = ( 'ok: multiple SIMBAD matches found' ) else: resultdict['simbad_status'] = 'ok: single SIMBAD match' # get the closest match if simbad_distarcsec[0] < gaia_matchdist_arcsec: resultdict.update({ 'simbad_best_mainid':simbad_mainid[0], 'simbad_best_objtype':simbad_objtype[0], 'simbad_best_allids':simbad_allids[0], 'simbad_best_distarcsec':simbad_distarcsec[0], 'simbad_status':'ok: object found within match radius' }) else: LOGWARNING('no SIMBAD objects found within ' '%.3f arcsec of object position (%.3f, %.3f), ' 'closest object: %s at %.3f arcsec away' % (gaia_matchdist_arcsec, objectinfo['ra'], objectinfo['decl'], simbad_mainid[0], simbad_distarcsec[0])) simbad_status = ('failed: no object within %.3f ' 'arcsec, closest = %.3f arcsec' % (gaia_matchdist_arcsec, simbad_distarcsec[0])) resultdict.update({ 'simbad_best_mainid':None, 'simbad_best_objtype':None, 'simbad_best_allids':None, 'simbad_best_distarcsec':None, 'simbad_status':simbad_status }) else: resultdict.update({ 'simbad_status':'failed: no SIMBAD matches found', 'simbad_nmatches':None, 'simbad_mainid':None, 'simbad_objtype':None, 'simbad_allids':None, 'simbad_distarcsec':None, 'simbad_best_mainid':None, 'simbad_best_objtype':None, 'simbad_best_allids':None, 'simbad_best_distarcsec':None, }) else: if search_simbad: simbad_status = 'failed: SIMBAD query failed' else: simbad_status = 'failed: SIMBAD query not tried' resultdict.update({ 'simbad_status':simbad_status, 'simbad_nmatches':None, 'simbad_mainid':None, 'simbad_objtype':None, 'simbad_allids':None, 'simbad_distarcsec':None, 'simbad_best_mainid':None, 'simbad_best_objtype':None, 'simbad_best_allids':None, 'simbad_best_distarcsec':None, }) return resultdict