def get_gaia_data(coordinates, radius=20 * u.arcmin, mag_limit=16.): gaia_data = Gaia.cone_search(coordinates, radius) gaia_data = gaia_data.get_results() p = np.where(gaia_data['phot_g_mean_mag'] <= mag_limit)[0] gaia_data = gaia_data[p] gaia_data.sort('phot_g_mean_mag') return gaia_data
def draw_fc(fc_params, bk_image=None, bk_lam=None, do_pdf=False, do_png=False): ''' The finding chart master plotting function. Args: fc_params: a dictionnary containing the OB parameters bk_image: the background image as string, either a SkyView entry, or local filename bk_lam: the wavelength of the chart as a string do_pdf (bool): save a pdf file, in addition to jpg do_png (bool): save a png file, in addition to jpg Returns: The finding chart filename as a string ''' # Load all the fields dictionaries fields = fcm_id.get_fields_dict(fc_params) # Get the radius and center of the charts (left_radius, right_radius) = fcm_id.get_chart_radius(fc_params) (left_center, right_center) = fcm_id.get_chart_center(fc_params) # Get the background image in place (fn_bk_image_L, survey_L, bk_lam_L) = get_bk_image(bk_image, bk_lam, left_center, left_radius, fc_params) # If this is not the DSS2 Red, then download this as well for the right-hand-side plot if not (survey_L == 'DSS2 Red'): (fn_bk_image_R, survey_R, bk_lam_R) = get_bk_image('DSS2 Red', None, right_center, right_radius, fc_params) else: (fn_bk_image_R, survey_R, bk_lam_R) = copy.deepcopy( (fn_bk_image_L, survey_L, bk_lam_L)) # Start the plotting plt.close(1) fig1 = plt.figure( 1, figsize=(14.17, 7)) #14.17 inches, at 50% = 1 full page plot in A&A. ax1 = aplpy.FITSFigure(fn_bk_image_L, figure=fig1, north=fcm_m.set_North, subplot=[0.12, 0.12, 0.35, 0.76]) ax1.show_grayscale(invert=True, stretch='linear', pmin=fcm_id.get_pmin(survey_L)) ax2 = aplpy.FITSFigure(fn_bk_image_R, figure=fig1, north=fcm_m.set_North, subplot=[0.59, 0.12, 0.38, 0.8]) ax2.show_grayscale(invert=True, stretch='linear', pmin=fcm_id.get_pmin(survey_R)) ax1.recenter(left_center.ra, left_center.dec, radius=left_radius / 3600.) ax2.recenter(right_center.ra, right_center.dec, radius=right_radius / 3600.) # I will be adding stuff to the left-hand-side plot ... start collecting them ax1_legend_items = [] # Do I have the epoch of observation for the background image ? ''' try: bk_obsdate = fits.getval(fn_bk_image, 'DATE-OBS') # If not specified, assume UTC time zone for bk image if bk_obsdate.tzinfo is None: # #warnings.warn(' "--obsdate" timezone not specified. Assuming UTC.') bk_obsdate = bk_obsdate.replace(tzinfo=pytz.utc) nowm_time = Time(bk_obsdate) pm_track_time = (fcm_obsdate - bk_obsdate).total_seconds()*u.s except: # If not, just shows the default length set in fcmaker_metadata nowm_time = Time(fcm_m.obsdate) - fcm_m.pm_track_time pm_track_time = fcm_m.pm_track_time ''' # Just keep things simple. Same lookback time for all charts. nowm_time = Time(fcm_m.obsdate) - fcm_m.pm_track_time #pm_track_time = fcm_m.pm_track_time # If yes, then let's show where are the fastest stars moved from/to. #if do_GAIA_pm and (fcm_m.min_abs_GAIA_pm >=0): if (fcm_m.min_abs_GAIA_pm >= 0): # Query GAIA print( ' Querying GAIA DR2 to look for high proper motion stars in the area ...' ) # Make it a sync search, because I don't think I need more than 2000 targets ... j = Gaia.cone_search(left_center, right_radius * u.arcsec, verbose=False) r = j.get_results() selected = np.sqrt(r['pmra']**2 + r['pmdec']**2 ) * u.mas / u.yr > fcm_m.min_abs_GAIA_pm # Show the fastest stars past_tracks = [] future_tracks = [] # Need to propagate their coords to the fc epoch and the obstime for s in range(len(r['ra'][selected])): star = SkyCoord( ra=r['ra'][selected][s], dec=r['dec'][selected][s], obstime=Time(r['ref_epoch'][selected][s], format='decimalyear'), frame='icrs', unit=(u.deg, u.deg), pm_ra_cosdec=r['pmra'][selected][s] * u.mas / u.yr, pm_dec=r['pmdec'][selected][s] * u.mas / u.yr, # I must specify a generic distance to the target, # if I want to later on propagate the proper motions distance=fcm_m.default_pm_d, ) now = star.apply_space_motion(new_obstime=Time(fcm_m.obsdate)) nowm = star.apply_space_motion(new_obstime=nowm_time) past_tracks += [ np.array([[nowm.ra.deg, now.ra.deg], [nowm.dec.deg, now.dec.deg]]) ] for ax in [ax1, ax2]: ax.show_markers([now.ra.deg], [now.dec.deg], marker='.', color='crimson', facecolor='crimson', edgecolor='crimson') #if len(r['ra'][selected])>0: # # Prepare a dedicated legend entry # ax1_legend_items += [mlines.Line2D([], [],color='crimson', # markerfacecolor='crimson', # markeredgecolor='crimson', # linestyle='-', # linewidth=0.75, # marker='.', # #markersize=10, # label='PM* (track$=-$%.1f yr)' % (pm_track_time.to(u.yr).value)) ] for ax in [ax1, ax2]: ax.show_lines(past_tracks, color='crimson', linewidth=0.75, linestyle='-') # Query UCAC2 via Vizier over the finding chart area, to look for suitable Guide Stars print(' Querying UCAC2 via Vizier to look for possible Guide Stars ...') Vizier.ROW_LIMIT = 10000 gs_table = Vizier.query_region( right_center, radius=right_radius * u.arcsec, inner_radius=fcm_id.get_inner_GS_search(fc_params) * u.arcsec, catalog="UCAC2") gs_table = gs_table[gs_table.keys()[0]] # Turn the table into a list of SkyCoord, that I can clean as I go. # Only select guide stars in the nominal GS mag range gs_list = [ SkyCoord( ra=line['RAJ2000'], dec=line['DEJ2000'], obstime=Time('J2000'), equinox='J2000', frame='icrs', unit=(u.deg, u.deg), pm_ra_cosdec=line['pmRA'] / 1000. * u.mas / u.yr, pm_dec=line['pmDE'] / 1000. * u.mas / u.yr, # Assume a fixed distance, so that I can then propagate proper motions distance=100. * u.pc).apply_space_motion(new_obstime=Time(fcm_m.obsdate)) for line in gs_table if fcm_m.gs_mag[0] <= line['UCmag'] <= fcm_m.gs_mag[1] ] # Here, I will show all the ephemeris points I have prepared, ahead of the # observations (If I have any) if len(fc_params['ephem_points_past']) > 0: ax1.show_markers( [item.ra.deg for item in fc_params['ephem_points_past']], [item.dec.deg for item in fc_params['ephem_points_past']], marker='*', color='crimson', s=100, facecolor='none', edgecolor='crimson') # Prepare a dedicated legend entry ax1_legend_items += [ mlines.Line2D( [], [], color='crimson', markerfacecolor='none', markeredgecolor='crimson', linestyle='', #linewidth=0.75, marker='*', #markersize=10, label='Target ($\Delta T=-%.0f$ min)' % (fc_params['ephem_past_delta'].total_seconds() / 60.)) ] if len(fc_params['ephem_points_future']) > 0: ax1.show_markers( [item.ra.deg for item in fc_params['ephem_points_future']], [item.dec.deg for item in fc_params['ephem_points_future']], marker='*', color='crimson', s=100, facecolor='crimson', edgecolor='crimson') # Prepare a dedicated legend entry ax1_legend_items += [ mlines.Line2D( [], [], color='crimson', markerfacecolor='crimson', markeredgecolor='crimson', linestyle='', #linewidth=0.75, marker='*', #markersize=10, label='Target ($\Delta T=+%.0f$ min)' % (fc_params['ephem_future_delta'].total_seconds() / 60.)) ] # Show the observation footprint for f in fields: # Call the function that will plot all the important stuff for this field. fcm_id.plot_field(ax1, ax2, fc_params, fields[f]) # Keep all the possible guide stars in the area. gs_list = [ star for star in gs_list if (fields[f][2].separation(star) > (fcm_id.get_inner_GS_search(fc_params) / 3600. * u.deg)) and ( fields[f][2].separation(star) < (fcm_id.get_GS_outer_radius(fc_params) / 3600. * u.deg)) ] # Show all the suitable Guide Star in the area if len(gs_list) > 0: ax2.show_markers([np.array(star.ra) for star in gs_list], [np.array(star.dec) for star in gs_list], marker='o', edgecolor='crimson', s=50, linewidth=1.0) else: warnings.warn('Watch out ... no suitable Guide Star found in UCAC2 !') # Add orientation arrows to the large view plot add_orient(ax2, right_center, radius=(right_radius * 0.82) * u.arcsec, arrow_width=(right_radius * 0.82) / 4.5 * u.arcsec, usetex=fcm_m.fcm_usetex) add_orient(ax1, left_center, radius=(left_radius * 0.82) * u.arcsec, arrow_width=(left_radius * 0.82) / 4.5 * u.arcsec, usetex=fcm_m.fcm_usetex) # Add a scale bar (scl, scll) = fcm_id.get_scalebar(fc_params['inst'], ins_mode=fc_params['ins_mode']) ax1.add_scalebar(scl) # Length in degrees ax1.scalebar.show(scl, label=scll, corner='bottom left', color='k', frame=1) ax1.scalebar.set_font_size(12) scl2 = np.floor(right_radius / 60 / 6) / 60. scll2 = r'%.0f$^{\prime}$' % (scl2 * 60) ax2.add_scalebar(scl2) ax2.scalebar.show(scl2, label=scll2, corner='bottom left', color='k', frame=1) ax2.scalebar.set_font_size(12) for ax in [ax1, ax2]: ax.scalebar.set_linewidth(2) # Fine tune things a bit further, just because I can ... for ax in [ax1, ax2]: ax.tick_labels.set_xformat('hh:mm:ss') ax.axis_labels.set_xpad(10) ax.ticks.set_linewidth(1.5) ax.set_tick_size(10) ax.set_tick_color('k') ax.axis_labels.set_xtext('R.A. [J2000]') ax1.axis_labels.set_ytext('Dec. [J2000]') ax1.axis_labels.set_ypad(-10) ax2.axis_labels.set_ytext('') # Add the required OB information to comply with ESO requirements ... # ... and make the life of the night astronomer a lot easier ! ax1.add_label(0.0, 1.11, 'Run ID: ' + fc_params['prog_id'] + ' | ' + fc_params['pi'], relative=True, horizontalalignment='left', size=14) # Fix some bugs for anyone not using LaTeX ... sigh ... if fcm_m.fcm_usetex: lab = fc_params['ob_name'].replace('_', '\_') else: lab = fc_params['ob_name'] ax1.add_label(0.0, 1.06, 'OB: %i | %s' % (fc_params['ob_id'], lab), relative=True, horizontalalignment='left', size=14) #ax1.add_label(0.0,1.08, r'$\lambda_{fc}$: %s (%s)' % (bk_lam_L, survey_L), relative=True, # horizontalalignment='left') # Display the Wavelength of the plots ax1.add_label(0.02, 0.965, r'%s (%s)' % (bk_lam_L, survey_L), relative=True, fontsize=12, horizontalalignment='left', verticalalignment='center', bbox=dict(edgecolor='w', facecolor='w', alpha=0.85)) ax2.add_label(0.02, 0.965, r'%s (%s)' % (bk_lam_R, survey_R), relative=True, fontsize=12, horizontalalignment='left', verticalalignment='center', bbox=dict(edgecolor='w', facecolor='w', alpha=0.85)) # Add a legend (if warranted) for the left plot if len(ax1_legend_items) > 0: ax1._ax1.legend( handles=ax1_legend_items, bbox_to_anchor=(-0.03, 0.82, 0.4, .1), #loc='lower right', ncol=1, #mode="expand", borderaxespad=0., fontsize=10, borderpad=0.3, handletextpad=0., handlelength=2.0) # Start keeping track of any tags I need to show tag_string = r' ' # Show the obsdate ax1.add_label(1.0, 1.02, r'Obs. date: ' + datetime.strftime(fcm_m.obsdate, '%Y-%m-%d %H:%M %Z'), relative=True, color='k', horizontalalignment='right', fontsize=11) # Show the OB tags if 'moving_target' in fc_params['tags']: tag_string += '$\leadsto$ ' if 'parallactic_angle' in fc_params['tags']: tag_string += '$\measuredangle$ ' if len(tag_string) > 1: # only show the tag if it has a non-zero length ax1.add_label( -0.18, 1.08, tag_string, relative=True, color='k', horizontalalignment='center', verticalalignment='center', fontsize=30, bbox=dict(edgecolor='k', facecolor='lightsalmon', alpha=1, linewidth=1, boxstyle="sawtooth,pad=0.2,tooth_size=0.075"), ) # Finally include the version of fcmaker in there ax1.add_label(1.01, 0.00, r'Created with fcmaker v%s' % (fcm_m.__version__), relative=True, horizontalalignment='left', verticalalignment='bottom', fontsize=10, rotation=90) # Save it all, both jpg for upload to p2, and pdf for nice quality. fn_out = os.path.join( fcm_m.plot_loc, fc_params['ob_name'].replace(' ', '_') + '_' + survey_L.replace(' ', '-')) fig1.savefig(fn_out + '.jpg') if do_pdf: fig1.savefig(fn_out + '.pdf') if do_png: fig1.savefig(fn_out + '.png') plt.close() return fn_out + '.jpg'
def gaia_cmd(target, sources, catalog='eDR3', plot=True): '''Authors: Patrick Tamburo, Boston University, May 2021 Purpose: Queries Gaia for sources and creates a CMD for them. Adds Gaia M_G and BP-RP to the source csv file. Inputs: target (str): The target's full 2MASS name. sources (pd.DataFrame): DataFrame of sources to be tracked in the field. These sources must have RA and Dec values. catalog (str, optional): The name of the Gaia catalog you wish to query, 'DR2' or 'eDR3'. plot (bool, optional): Whether or not to save the CMD to the object's 'sources' directory. red_only (bool, optional): Whether or not to limit the retained sources to the reddest stars in the field. Outputs: sources (pd.DataFrame): DataFrame of sources, with M_G and BP-RP columns added. Also updates the target_and references_source_detection.csv file in the object's 'sources' directory. TODO: None. ''' pines_path = pat.utils.pines_dir_check() short_name = pat.utils.short_name_creator(target) #Set the Gaia data release to query. if catalog == 'DR2': Gaia.MAIN_GAIA_TABLE = 'gaiadr2.gaia_source' elif catalog == 'eDR3': Gaia.MAIN_GAIA_TABLE = 'gaiadr3.gaia_source' else: raise ValueError('catalog must be DR2 or eDR3.') num_s = len(sources) bp_rp = np.zeros(num_s) #Gaia Bp - Rp color p_mas = np.zeros(num_s) #Parallax in mas M_G = np.zeros(num_s) #Absolute Gaia Rp print('Querying sources in Gaia {}.'.format(catalog)) for i in range(num_s): ra = sources['Source Detect RA'][i] dec = sources['Source Detect Dec'][i] c = SkyCoord(ra=ra, dec=dec, unit=(u.deg, u.deg)) #Query Gaia query_gaia = Gaia.cone_search(c, radius=5 * u.arcsec) result_gaia = query_gaia.get_results() if len(result_gaia) == 0: bp_rp[i] = np.nan p_mas[i] = np.nan M_G[i] = np.nan print( 'No source found in Gaia {} at ({:1.4f},{:1.4f}). Returning NaNs.' .format(catalog, ra, dec)) else: bp_rp[i] = result_gaia['bp_rp'][0] m_G = result_gaia['phot_g_mean_mag'][0] p_mas[i] = result_gaia['parallax'][0] dist_pc = 1 / (p_mas[i] / 1000) M_G[i] = m_G + 5 - 5 * np.log10(dist_pc) if plot: plt.figure(figsize=(6, 9)) plt.scatter(bp_rp, M_G) plt.title(sources['Name'][0] + ' field CMD', fontsize=18) plt.xlabel('G$_{BP}$ - G$_{RP}$', fontsize=16) plt.ylabel('M$_G$', fontsize=16) plt.xlim(-1, 5) plt.ylim(-5, 16) plt.gca().invert_yaxis() plt.tight_layout() plt.savefig(pines_path / ('Objects/' + short_name + '/sources/gaia_cmd.png'), dpi=300) sources['M_G'] = M_G sources['bp_rp'] = bp_rp sources.to_csv(pines_path / ('Objects/' + short_name + '/sources/target_and_references_source_detection.csv'), index=0, na_rep='NaN') return sources
def make_gaia_image(skycoord, fov, sampling, fwhm, obsdate=dup.parse("2018 07 01 00:00:00 UTC"), fn=os.path.join('.', 'pseudo_gaia.fits')): ''' Given a coordinate, use the Gaia catalogue to create a mock image of the sky. Args: skycoord: the astropy SkyCoord of the center of the image fov: size of the image to create, in astropy.units sampling: pixel size of the image to create, in astropy.units fwhm: fwhm of the fake stars in the image, in astropy.units obsdate: date of the observation (to propagate the motion of the stars) as datetime.datetime object fn: relative path + FITS filename Returns: The FITS filename ''' # Ok, assemble an array of the suitable size nx = int(np.floor((fov / sampling).value)) im = np.zeros((nx, nx)) # Query GAIA print(' Querying GAIA to create a pseudo-image of the sky ...') # Make it a sync search, because I don't think I need more than 2000 targets ... j = Gaia.cone_search(skycoord, fov, verbose=False) r = j.get_results() nstars = len(r) # TODO: well, what if I do need more than 2000 targets ? # Issue a warning for now ... if nstars == 2000: warnings.warn( ' Reached query limit of 2k stars. Gaia image will not be complete.' ) # Create a very basic header for the FITS file hdr = fits.Header() hdr.set('CRPIX1', nx / 2, 'X reference pixel') hdr.set('CRPIX2', nx / 2, 'Y reference pixel') hdr.set('CRVAL1', skycoord.ra.deg, 'Reference longitude') hdr.set('CRVAL2', skycoord.dec.deg, 'Reference latitude') hdr.set('CTYPE1', 'RA---TAN', 'Coordinates -- projection') hdr.set('CTYPE2', 'DEC--TAN', 'Coordinates -- projection') hdr.set('CDELT1', -sampling.to(u.deg).value, 'X scale') # mind the sign ! hdr.set('CDELT2', sampling.to(u.deg).value, 'Y scale') hdr.set('RADESYS', 'ICRS', 'Coordinate system') hdr.set('EQUINOX', 2000.0, 'Epoch of the equinox') hdr['COMMENT'] = 'Artificial sky image reconstructed from the Gaia catalogue.' hdr['COMMENT'] = 'Number of stars: %i' % (nstars) hdr['COMMENT'] = 'FWHM: %i mas' % (int(fwhm.to(u.mas).value)) hdr['COMMENT'] = 'Bandpass: 285.5-908 THz ' hdr['COMMENT'] = 'Epoch: %s' % (obsdate.strftime('%Y-%h-%d %H:%M:%S UTC')) hdr['COMMENT'] = 'Created %s with fcmaker v%s' % ( datetime.utcnow().strftime('%Y-%h-%d %H:%M:%S UTC'), fcm_m.__version__) hdr['COMMENT'] = 'See http://fpavogt.github.io/fcmaker for details.' # Save the empty FITS file for now hdu = fits.PrimaryHDU(im, header=hdr) hdul = fits.HDUList([hdu]) hdul.writeto(fn, overwrite=True) # Re-extract the WCS info w = WCS(fn) # Prepare some variables x, y = np.meshgrid(np.arange(0, nx, 1), np.arange(0, nx, 1)) sigma = (fwhm / sampling).value / (2 * np.sqrt(2 * np.log(2))) # For each star, add a Gaussian of the suitable size for s in range(nstars): # First, create a SkyCoord entry star = SkyCoord( ra=r['ra'][s], dec=r['dec'][s], obstime=Time(r['ref_epoch'][s], format='decimalyear'), frame='icrs', unit=(u.deg, u.deg), pm_ra_cosdec=r['pmra'][s] * u.mas / u.yr, pm_dec=r['pmdec'][s] * u.mas / u.yr, # I must specify a generic distance to the target, # if I want to later on propagate the proper motions distance=fcm_m.default_pm_d, ) # Then, propagate the star at the time of the observation star_now = star.apply_space_motion(new_obstime=Time(obsdate)) # Get the image coordinate of the star star_coords = w.wcs_world2pix([star_now.ra.deg], [star_now.dec.deg], 0) # The distance of all pixels to the star d = np.sqrt((x - star_coords[0])**2 + (y - star_coords[1])**2) # Add the star to the array ... scale it as a function of its flux im += r['phot_g_mean_flux'][s] * np.exp(-( (d)**2 / (2.0 * (fwhm / sampling).value**2))) # Save the final FITS file hdu = fits.PrimaryHDU(im, header=hdr) hdul = fits.HDUList([hdu]) hdul.writeto(fn, overwrite=True) return fn