Hubble Source Catalog SWEEPS Proper Motion Notebook

Contents

Hubble Source Catalog SWEEPS Proper Motion Notebook#

2019 - 2022, Steve Lubow, Rick White, Trenton McKinney#

This notebook shows how to access the new proper motions available for the SWEEPS field in version 3.1 of the Hubble Source Catalog. Data tables in MAST CasJobs are queried from Python using the mastcasjobs module. Additional information is available on the SWEEPS Proper Motions help page.

Instructions:#

  • Complete the initialization steps described below.

  • Run the notebook to completion.

  • Modify and rerun any sections of the Table of Contents below.

Running the notebook from top to bottom takes about 7 minutes (depending on the speed of your computer).

Table of Contents#

  • Intialization

  • Properties of Full Catalog

    • Sky Coverage

    • Proper Motion Distributions

    • Visit Distribution

    • Time Coverage Distributions

    • Magnitude Distributions

    • Color Magnitude Diagram

    • Detection Positions

  • Good Photometric Objects

  • Science Applications

    • Proper Motions on the CMD

    • Proper Motions in Bulge Versus Disk

    • White Dwarfs

    • QSO Candidates

    • High Proper Motion Objects

    • HLA Cutout Images for Selected Objects

Initialization #

Install Python modules#

  1. This notebook requires the use of Python 3.

  2. Modules can be installed with conda, if using the Anaconda distribution of python, or with pip.

    • If you are using conda, do not install / update / remove a module with pip, that exists in a conda channel.

    • If a module is not available with conda, then it’s okay to install it with pip

  3. Install mastcasjobs and casjobs with pip:

pip install mastcasjobs

Set up your CasJobs account information#

You must have a MAST Casjobs account. Note that MAST Casjobs accounts are independent of SDSS Casjobs accounts.

For easy startup, you can optionally set the environment variables CASJOBS_USERID and/or CASJOBS_PW with your Casjobs account information. The Casjobs user ID and password are what you enter when logging into Casjobs.

This script prompts for your Casjobs user ID and password during initialization if the environment variables are not defined.

import astropy
import time
import sys
import os
import requests
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from matplotlib.colors import LogNorm
from pathlib import Path

# check that version of mastcasjobs is new enough
# we are using some features not in version 0.0.1
from pkg_resources import get_distribution
from packaging.version import Version as V

assert V(get_distribution("mastcasjobs").version) >= V('0.0.2'), """
A newer version of mastcasjobs is required.
Update mastcasjobs to current version using this command:
pip install --upgrade git+git://github.com/rlwastro/mastcasjobs@master
"""

import mastcasjobs

from PIL import Image
from io import BytesIO

## For handling ordinary astropy Tables
from astropy.table import Table
from astropy.io import fits, ascii

from fastkde import fastKDE
from scipy.interpolate import RectBivariateSpline
from astropy.modeling import models, fitting

# There are a number of relatively unimportant warnings that 
# show up, so for now, suppress them:
import warnings
warnings.filterwarnings("ignore")

# set width for pprint
astropy.conf.max_width = 150
/tmp/ipykernel_2008/609964975.py:14: DeprecationWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html
  from pkg_resources import get_distribution
# set universal matplotlib parameters
plt.rcParams.update({'font.size': 16})
HSCContext = "HSCv3"

Set up Casjobs environment.

import getpass
if not os.environ.get('CASJOBS_USERID'):
    os.environ['CASJOBS_USERID'] = input('Enter Casjobs UserID:')
if not os.environ.get('CASJOBS_PW'):
    os.environ['CASJOBS_PW'] = getpass.getpass('Enter Casjobs password:')

Create table in MyDB with selected SWEEPS objects#

Note that the query restricts the sample to matches with at least 10 detections in each of F606W and F814W. This can be modified depending on the science goals.

This uses an existing MyDB.SWEEPS table if it already exists in your CasJobs account. If you want to change the query, either change the name of the output table or drop the table to force it to be recreated. Usually the query completes in about a minute, but if the database server is heavily loaded then it can take much longer.

DBtable = "SWEEPS"
jobs = mastcasjobs.MastCasJobs(context="MyDB")

try:
    print(f"Retrieving table MyDB.{DBtable} (if it exists)")
    tab = jobs.fast_table(DBtable, verbose=True)
except ValueError:
    print(f"Table MyDB.{DBtable} not found, running query to create it")

    # drop table if it already exists
    jobs.drop_table_if_exists(DBtable)

    # get main information
    query = f"""
        select a.ObjID,  RA=a.raMean, Dec=a.decMean, RAerr=a.raMeanErr, Decerr=a.decMeanErr,
            c.NumFilters, c.NumVisits,
            a_f606w=i1.MagMed,  a_f606w_n=i1.n, a_f606w_mad=i1.MagMAD,
            a_f814w=i2.MagMed, a_f814w_n=i2.n, a_f814w_mad=i2.MagMAD,
            bpm=a.pmLat, lpm=a.pmLon, bpmerr=a.pmLatErr, lpmerr=a.pmLonErr,
            pmdev=sqrt(pmLonDev*pmLonDev+pmLatDev*pmLatDev),
            yr=(a.epochMean - 47892)/365.25+1990, 
            dT=(a.epochEnd-a.epochStart)/365.25,
            yrStart=(a.epochStart - 47892)/365.25+1990,
            yrEnd=(a.epochEnd - 47892)/365.25+1990
        into mydb.{DBtable}
        from AstromProperMotions a join AstromSumMagAper2 i1 on 
             i1.ObjID=a.ObjID and i1.n >=10 and i1.filter ='F606W' and i1.detector='ACS/WFC'
         join AstromSumMagAper2 i2 on 
             i2.ObjID=a.ObjID and i2.n >=10 and i2.filter ='F814W' and i2.detector='ACS/WFC'
         join AstromSumPropMagAper2Cat c on a.ObjID=c.ObjID
    """

    t0 = time.time()
    jobid = jobs.submit(query, task_name="SWEEPS", context=HSCContext)
    print("jobid=", jobid)
    results = jobs.monitor(jobid)
    print(f"Completed in {(time.time()-t0):.1f} sec")
    print(results)

    # slower version using CasJobs output queue
    # tab = jobs.get_table(DBtable, verbose=False)
    
    # fast version using special MAST Casjobs service
    tab = jobs.fast_table(DBtable, verbose=True)
Retrieving table MyDB.SWEEPS (if it exists)
13.2 s: Retrieved 157.86MB table MyDB.SWEEPS
19.4 s: Converted to 443932 row table
tab
Table length=443932
ObjIDRADecRAerrDecerrNumFiltersNumVisitsa_f606wa_f606w_na_f606w_mada_f814wa_f814w_na_f814w_madbpmlpmbpmerrlpmerrpmdevyrdTyrStartyrEnd
int64float64float64float64float64int32int32float64int32float64float64int32float64float64float64float64float64float64float64float64float64float64
4000709002286269.7911379669984-29.2061561874114230.69648186245280990.273006233080014124722.127399444580078470.02160072326660156221.13010025024414470.01679992675781252.087558644949346-7.7382723294303710.388545822763860070.221156733689812372.8871545181336922013.300790214705811.3719145413717642003.43617966200852014.8080942033803
4000709002287269.7955922590832-29.2061516314949860.240202167603863430.1852481139121781624721.508499145507812470.02999877929687520.69930076599121470.023900985717773438-2.8930568503344967-0.78985838465552450.13165847900535780.124621856958779961.4746766326637852013.300790214705811.3719145413717642003.43617966200852014.8080942033803
4000709002288269.81608933789283-29.2061551966411950.30406841310206710.285040758620025624721.654399871826172470.0365009307861328120.85770034790039470.0171012878417968754.65866649193795-3.20988045803437850.139311721836511830.206480976047816261.95703573227134632013.300790214705811.3719145413717642003.43617966200852014.8080942033803
4000709002289269.8259694163096-29.206156688407510.35643254265220670.3954220029733366324719.79170036315918470.02820014953613281219.06909942626953470.019300460815429688-0.45662407290928664-2.09090500454338320.157581759523336530.27638812821949082.24152384993776852013.300790214705811.3719145413717642003.43617966200852014.8080942033803
4000709002290269.83486415728754-29.2061552669836430.162996398391985380.1406283940781183624620.566649436950684460.01594924926757812519.847750663757324460.0148010253906254.459275526783969-2.04336323443438860.178997279438553310.185035944688353961.00919709070525572013.51523827019923.00678224901781552011.80131195436252014.8080942033803
4000709002291269.83512411344606-29.20616352447980.182825831051080720.209350365068154124620.17770004272461460.02894973754882812519.489749908447266460.036399841308593754.090870144734149-8.0594731583940720.204463511521890520.262062125296961931.2930500270273292013.51523827019923.00678224901781552011.80131195436252014.8080942033803
4000709002292269.7964913295107-29.206187344833110.304911023972265270.2678449677785108624720.83639907836914470.0223999023437520.088300704956055470.02230072021484375-1.7001866534338244-5.9639674627591590.148141617998445470.19205176816533741.96717614892876542013.300790214705811.3719145413717642003.43617966200852014.8080942033803
4000709002293269.7872745304419-29.2062578288523371.3518855043600481.046061418947137824625.99174976348877460.085249900817871124.204350471496582460.05684947967529297-2.290436263458843-9.5968385596236270.7793734808164730.64853540325800878.5180455742088712013.320615015201611.3719145413717642003.43617966200852014.8080942033803
4000709002294269.80888716219647-29.2061891896260021.41345961187521031.251804780286295324725.465349197387695460.0810499191284179723.27630043029785470.0321006774902343754.381302303073221-4.7018558763948480.47844287930018571.02193637753253526.4696545651479532013.300790214705811.3719145413717642003.43617966200852014.8080942033803
4000709002295269.8234425365187-29.2061884736616260.355978575621609231.587710441067255823624.13195037841797360.0494003295898437523.0174503326416360.0381002426147460944.48015296877619-8.0009851082285050.50708762440657380.72865533809519864.1411578720269952013.17425026376711.2987153728356782003.43617966200852014.7348950348442
..................................................................
4000946339500269.6899686412092-29.276978451867841.75817441277581680.935409107408077421023.770350456237793100.0608997344970703122.6496000289917100.037400245666503906-4.433870501660679-1.67011670319295422.29307144543402861.37664593465365684.2984304123552942013.90004365082272.19134921116230432012.49901610545182014.6903653166141
4000946380565269.71004704275265-29.2583186194187231.43372735707951641.47165523030859921123.99720001220703110.0373001098632812522.51849937438965110.0200004577636718751.6111158133637464-6.7456632868311521.96316916518413591.7202803635641434.4095240844353392013.90025407658032.30944127475487142012.49901610545182014.8084573802066
4000946404892269.67530078168915-29.2471627342426751.37639069491067261.613389460139535321224.929399490356445120.0292005538940429723.21535015106201120.06190013885498047-4.240018447223827-5.3013959214746952.32404892866457272.46465178107854675.0340409696458412013.37253173202112.04586304942736242012.3890313766822014.4348944261094
4000946417296269.7016328152704-29.2460704665937433.1308309826369583.18514411287760721125.600500106811523110.2341995239257812524.028099060058594110.25039863586425780.3265864932598421-6.4913946479921566.3967154596976412.57826528259230478.8294470986630282013.77311224404482.019486200362752012.67087911625122014.6903653166141
4000949430259269.722986295747-29.211971120172651.3619639637737381.15890561289665531524.502249717712402140.0661497116088867223.928850173950195140.0568504333496093754.745787093281551-3.41492243483594750.98752439816221490.60066867391504575.0810874604947742004.5674490817626.2125723619169572004.143782912162010.356355274077
4000949692413269.847657491686-29.2134644371428682.36780346719586682.022459781954067321024.422550201416016100.1117000579833984422.581549644470215100.02095031738281251.6834992526734194-14.4904675061560653.47406421445087954.4926563980115825.9461131883186352013.9553403396031.50985393087614852013.29824027250422014.8080942033803
4000949719295269.8230388680129-29.200721201818856.92562390115529252.4988461075181621025.977850914001465100.1077995300292968824.156999588012695100.03194999694824219-0.009307948981069264-9.2555808485828021.85811783298020821.524192442646531415.4735455528271032012.470060074062510.7687844081356622003.43617966200852014.2049640701443
4000979902333269.804403240861-29.1928132654553962.38599567764471271.39284851594804221025.71024990081787100.0653505325317382824.112099647521973100.054700851440429690.7550276872362829-4.77459070250673453.62576861309534771.4617815676884975.0650346984665922013.65899151197162.53086655097392082012.20402848387042014.7348950348442
4000979908546269.8156665822647-29.1898545163315260.91437485936164812.123819200708503721225.4466495513916120.1591005325317382823.336549758911133120.06319999694824219-7.2055855532918205-9.6832928336088782.6182646869419673.07248080976638835.2462673095450552013.83403361107031.60174311365878672013.20635108972152014.8080942033803
4000980227788269.7998513044728-29.197077719265191.84948598148885651.599149165537078421224.400450706481934120.1038999557495117222.93809986114502120.020649909973144530.15278595605010709-7.1721525185359610.6919345570902570.493731637979113745.76197440994000852012.656872142421411.3719145413717642003.43617966200852014.8080942033803

Properties of Full Catalog #

Sky Coverage #

fig, ax = plt.subplots(figsize=(12, 10))
ax.scatter('RA', 'Dec', data=tab, s=1, alpha=0.1)
ax.set(xlabel='RA', ylabel='Dec', title=f'{len(tab)} stars in SWEEPS')
ax.invert_xaxis()
../../../_images/4df95ae2946dee9752f143624f97ae47812ce61a142cd2171bfe97d6b2875edb.png

Proper Motion Histograms #

Proper motion histograms for lon and lat#

bin = 0.2
hrange = (-20, 20)
bincount = int((hrange[1]-hrange[0])/bin + 0.5) + 1

fig, ax = plt.subplots(figsize=(12, 10))
for col, label in zip(['lpm', 'bpm'], ['Longitude', 'Latitude']):
    ax.hist(col, data=tab, range=hrange, bins=bincount, label=label, histtype='step', linewidth=2)
ax.set(xlabel='Proper motion [mas/yr]', ylabel=f'Number [in {bin:.2} mas bins]', title=f'{len(tab):,} stars in SWEEPS')
_ = ax.legend()
../../../_images/a265da411b87c3168cf3122ed6cb9c244c3cb7d6f99ac2ddc5aef3de728c3403.png

Proper motion error cumulative histogram for lon and lat#

bin = 0.01
hrange = (0, 2)
bincount = int((hrange[1]-hrange[0])/bin + 0.5) + 1

fig, ax = plt.subplots(figsize=(12, 10))
for col, label in zip(['lpmerr', 'bpmerr'], ['Longitude Error', 'Latitude Error']):
    ax.hist(col, data=tab, range=hrange, bins=bincount, label=label, histtype='step', cumulative=1, linewidth=2)
ax.set(xlabel='Proper motion error [mas/yr]', ylabel=f'Cumulative number [in {bin:0.2} mas bins]', title=f'{len(tab):,} stars in SWEEPS')
_ = ax.legend(loc='upper left')
../../../_images/3f0efe401ad77b251eba526eb5e856d2fb595ffc61c566cfc7411e2dc1966f1c.png

Proper motion error log histogram for lon and lat#

bin = 0.01
hrange = (0, 6)
bincount = int((hrange[1]-hrange[0])/bin + 0.5) + 1

fig, ax = plt.subplots(figsize=(12, 10))
for col, label in zip(['lpmerr', 'bpmerr'], ['Longitude Error', 'Latitude Error']):
    ax.hist(col, data=tab, range=hrange, bins=bincount, label=label, histtype='step', linewidth=2)
ax.set(xlabel='Proper motion error [mas/yr]', ylabel=f'Number [in {bin:0.2} mas bins]', title=f'{len(tab):,} stars in SWEEPS', yscale='log')
_ = ax.legend(loc='upper right')
../../../_images/7bf7e3d163ece492d91c8372db4085e5932da4edfc55902505f68e0502f337dd.png

Proper motion error as a function of dT#

Exclude objects with dT near zero, and to improve the plotting add a bit of random noise to spread out the quanitized time values.

# restrict to sources with dT > 1 year
dtmin = 1.0
w = np.where(tab['dT'] > dtmin)[0]
if ('rw' not in locals()) or len(rw) != len(w):
    rw = np.random.random(len(w))

x = np.array(tab['dT'][w]) + 0.5*(rw-0.5)
y = np.log(np.array(tab['lpmerr'][w]))

# Calculate the point density
t0 = time.time()
myPDF, axes = fastKDE.pdf(x.flatten(), y.flatten(), numPoints=2**9+1)
print("kde took {:.1f} sec".format(time.time()-t0))

# interpolate to get z values at points
finterp = RectBivariateSpline(axes[1], axes[0], myPDF)
z = finterp(y, x, grid=False)

# Sort the points by density, so that the densest points are plotted last
idx = z.argsort()
xs, ys, zs = x[idx], y[idx], z[idx]

# select a random subset of points in the most crowded regions to speed up plotting
wran = np.where(np.random.random(len(zs))*zs < 0.05)[0]
print("Plotting {} of {} points".format(len(wran), len(zs)))
xs = xs[wran]
ys = ys[wran]
zs = zs[wran]
kde took 1.6 sec
Plotting 177872 of 442682 points
fig, ax = plt.subplots(figsize=(12, 10))
sc = ax.scatter(xs, np.exp(ys), c=zs, s=2, edgecolors='none', cmap='plasma')
ax.set(xlabel='Date range [yr]', ylabel='Proper motion error [mas/yr]',
       title=f'{len(tab):,} stars in SWEEPS\nLongitude PM error', yscale='log')
_ = fig.colorbar(sc, ax=ax)
../../../_images/1ebbcb160b1701fbb7780c95e52914f5029a175984e90086b55c88ea1d2dae22.png

Proper motion error log histogram for lon and lat#

Divide sample into points with \(<6\) years of data and points with more than 6 years of data.

bin = 0.01
hrange = (0, 6)
bincount = int((hrange[1]-hrange[0])/bin + 0.5) + 1

tsplit = 6
dmaglim = 0.05

wmag = np.where((tab['a_f606w_mad'] < dmaglim) & (tab['a_f814w_mad'] < dmaglim))[0]
w1 = wmag[tab['dT'][wmag] <= tsplit]
w2 = wmag[tab['dT'][wmag] > tsplit]
fig, (ax1, ax2) = plt.subplots(nrows=2, ncols=1, figsize=(12, 12), sharey=True, tight_layout=True)

for ax, w in zip([ax1, ax2], [w1, w2]):
    for col, label in zip(['lpmerr', 'bpmerr'], ['Longitude Error', 'Latitude Error']):
        data = tab[w]
        ax.hist(col, data=data, range=hrange, bins=bincount, label=label, histtype='step', linewidth=2)
        
ax1.set(xlabel='Proper motion error [mas/yr]', ylabel=f'Number [in {bin:0.2} mas bins]',
        title=f'{len(w1):,} stars in SWEEPS with dT < {tsplit} yrs, dmag < {dmaglim}', yscale='log')
ax2.set(xlabel='Proper motion error [mas/yr]', ylabel=f'Number [in {bin:0.2} mas bins]',
        title=f'{len(w2):,} stars in SWEEPS with dT > {tsplit} yrs, dmag < {dmaglim}', yscale='log')

ax1.legend()
ax2.legend()
<matplotlib.legend.Legend at 0x7f3fa96cb110>
../../../_images/ece72efa81ec21bc011070452a5b28530ddb88ac2eebec1d0255092ab8616075.png

Number of Visits Histogram #

bin = 1
hrange = (0, 130)
bincount = int((hrange[1]-hrange[0])/bin + 0.5) + 1

fig, ax = plt.subplots(figsize=(12, 10))
ax.hist('NumVisits', data=tab, range=hrange, bins=bincount, label='Number of visits ', histtype='step', linewidth=2)
ax.set(xlabel='Number of visits', ylabel='Number of objects', title=f'{len(tab):,} stars in SWEEPS')
_ = ax.margins(x=0)
../../../_images/d7cc9532261cc513a82283dd34923f42577d3c6e5b52bfb6fa3874a655dbc5cb.png

Time Histograms #

First plot histogram of observation dates.

bin = 1
hrange = (2000, 2020)
bincount = int((hrange[1]-hrange[0])/bin + 0.5) + 1

fig, ax = plt.subplots(figsize=(12, 10))
ax.hist('yr', data=tab, range=hrange, bins=bincount, label='year ', histtype='step', linewidth=2)
ax.set(xlabel='mean detection epoch (year)', ylabel='Number of objects', title=f'{len(tab):,} stars in SWEEPS')
ax.set_xticks(ticks=range(2000, 2021, 2))
_ = ax.margins(x=0)
../../../_images/ba3688186e46f8989d1394ddce61090efca56679f68c54c53811cc63bb8ab0b7.png

Then plot histogram of observation duration for the objects.

bin = 0.25
hrange = (0, 15)
bincount = int((hrange[1]-hrange[0])/bin + 0.5) + 1

fig, ax = plt.subplots(figsize=(12, 10))
ax.hist('dT', data=tab, range=hrange, bins=bincount, label='year ', histtype='step', linewidth=2)
_ = ax.set(xlabel='time span (years)', ylabel='Number of objects', title=f'{len(tab):,} stars in SWEEPS', yscale='log')
../../../_images/60e6900a9838c83003202d35f981ff1623b06b370a932ccc238882eaa21dd5ce.png

Magnitude Histograms #

Aper2 magnitude histograms for F606W and F814W#

bin = 0.025
hrange = (16, 28)
bincount = int((hrange[1]-hrange[0])/bin + 0.5) + 1

fig, ax = plt.subplots(figsize=(12, 10))
for col, label in zip(['a_f606w', 'a_f814w'], ['F606W', 'F814W']):
    ax.hist(col, data=tab, range=hrange, bins=bincount, label=label, histtype='step', linewidth=2)
ax.set(xlabel='magnitude', ylabel=f'Number [in {bin:0.2} mas bins]', title=f'{len(tab):,} stars in SWEEPS')
_ = ax.legend(loc='upper right')
../../../_images/d2861ea9354180d61c7d4d5497205322274bf7e1a58fc7391bffc83e6a591996.png

Aper2 magnitude error histograms for F606W and F814W#

bin = 0.001
hrange = (0, 0.2)
bincount = int((hrange[1]-hrange[0])/bin + 0.5) + 1

fig, ax = plt.subplots(figsize=(12, 10))
for col, label in zip(['a_f606w_mad', 'a_f814w_mad'], ['F606W', 'F814W']):
    ax.hist(col, data=tab, range=hrange, bins=bincount, label=label, histtype='step', linewidth=2)
ax.set(xlabel='magnitude error (median absolute deviation)', ylabel=f'Number of stars[in {bin:0.2} magnitude bins]',
       title=f'{len(tab):,} stars in SWEEPS')
_ = ax.legend(loc='upper right')
../../../_images/1837761cd2e71eb9b59daba5e3315388fa8b416aac05f16ab4e95d03d70c25d4.png

Color-Magnitude Diagram #

Color-magnitude diagram#

Plot the color-magnitude diagram for the ~440k points retrieved from the database. This uses fastkde to compute the kernel density estimate for the crowded plot, which is very fast. See https://pypi.org/project/fastkde/ for instructions – or just do
pip install fastkde

f606w = tab['a_f606w']
f814w = tab['a_f814w']
RminusI = f606w-f814w

# Calculate the point density
w = np.where((RminusI > -1) & (RminusI < 4))[0]
x = np.array(RminusI[w])
y = np.array(f606w[w])
t0 = time.time()
myPDF, axes = fastKDE.pdf(x, y, numPoints=2**10+1)
print("kde took {:.1f} sec".format(time.time()-t0))

# interpolate to get z values at points
finterp = RectBivariateSpline(axes[1], axes[0], myPDF)
z = finterp(y, x, grid=False)

# Sort the points by density, so that the densest points are plotted last
idx = z.argsort()
xs, ys, zs = x[idx], y[idx], z[idx]

# select a random subset of points in the most crowded regions to speed up plotting
wran = np.where(np.random.random(len(zs))*zs < 0.05)[0]
print(f"Plotting {len(wran)} of {len(zs)} points")
xs = xs[wran]
ys = ys[wran]
zs = zs[wran]
kde took 1.8 sec
Plotting 147935 of 443502 points
fig, ax = plt.subplots(figsize=(12, 10))
sc = ax.scatter(xs, ys, c=zs, s=2, edgecolors='none', cmap='plasma')
ax.set(xlabel='A_F606W - A_F814W', ylabel='A_F606W', title=f'{len(x):,} stars in SWEEPS')
ax.invert_yaxis()
fig.colorbar(sc, ax=ax)
<matplotlib.colorbar.Colorbar at 0x7f3fa4e43510>
../../../_images/25c8477005c18e6aac267b7759faa42bdd8a2e762b1cd5d3dba6913b2ac5ccea.png

Detection Positions #

Define a function to plot the PM fit for an object.

# define function
def positions(Obj, jobs=None):
    """
    input parameter Obj is the value of the ObjID 
    optional jobs parameter re-uses casjobs jobs variable
    output plots change in (lon, lat) as a function of time
    overplots proper motion fit
    provides number of objects and magnitude/color information
    """
    if not jobs:
        jobs = mastcasjobs.MastCasJobs(context=HSCContext)

    # execute these as "system" queries so they don't fill up your Casjobs history

    # get the measured positions as a function of time
    query = f"""SELECT dT, dLon, dLat 
        from AstromSourcePositions where ObjID={Obj}
        order by dT
        """
    pos = jobs.quick(query, context=HSCContext, task_name="SWEEPS/Microlensing", astropy=True, system=True)
    
    # get the PM fit parameters
    query = f"""SELECT pmlon, pmlonerr, pmlat, pmlaterr
        from AstromProperMotions where ObjID={Obj}
        """
    pm = jobs.quick(query, context=HSCContext, task_name="SWEEPS/Microlensing", astropy=True, system=True)
    
    lpm = pm['pmlon'][0]
    bpm = pm['pmlat'][0]
    
    # get the intercept for the proper motion fit referenced to the start time
    # time between mean epoch and zero (ref) epoch (years)

    # get median magnitudes and colors for labeling
    query = f"""SELECT a_f606w=i1.MagMed, a_f606_m_f814w=i1.MagMed-i2.MagMed
        from AstromSumMagAper2 i1 
        join AstromSumMagAper2 i2 on i1.ObjID=i2.ObjID 
        where i1.ObjID={Obj} and i1.filter='f606w' and i2.filter='f814w' 
        """
    phot = jobs.quick(query, context=HSCContext, task_name="SWEEPS/Microlensing", astropy=True, system=True)
    f606w = phot['a_f606w'][0]
    f606wmf814w = phot['a_f606_m_f814w'][0]

    x = pos['dT']
    # xpm = np.linspace(0, max(x), 10)
    xpm = np.array([x.min(), x.max()])
        
    y1 = pos['dLon']
    ypm1 = lpm*xpm
    
    y2 = pos['dLat']
    ypm2 = bpm*xpm

    # plot figure
    fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(6, 3), tight_layout=True)
    
    ax1.scatter(x, y1, s=10)
    ax1.plot(xpm, ypm1, '-r')
    
    ax2.scatter(x, y2, s=10)
    ax2.plot(xpm, ypm2, '-r')
    
    ax1.set_xlabel('dT (yrs)', fontsize=10)
    ax1.set_ylabel('dLon (mas)', fontsize=10)
    ax2.set_xlabel('dT (yrs)', fontsize=10)
    ax2.set_ylabel('dLat (mas)', fontsize=10)
    
    fig.suptitle(f"ObjID {Obj}"
                 f"\n{len(x)} detections, (lpm, bpm) = ({lpm:.1f}, {bpm:.1f}) mas/yr"
                 f"\n(f606w, f606w-f814w) = ({f606w:.1f}, {f606wmf814w:.1f})", size=10)
    
    plt.show()
    plt.close()

Plot positions of objects that are detected in more than 90 visits with a median absolute deviation from the fit of less than 1.5 mas and proper motion error less than 1.0 mas/yr.

n = tab['NumVisits']
dev = tab['pmdev']
objid = tab['ObjID']
lpmerr0 = np.array(tab['lpmerr'])
bpmerr0 = np.array(tab['bpmerr'])
wi = np.where((dev < 1.5) & (n > 90) & (np.sqrt(bpmerr0**2+lpmerr0**2) < 1.0))[0]
print(f"Plotting {len(wi)} objects")
for o in objid[wi]:
    positions(o, jobs=jobs)
Plotting 21 objects
../../../_images/c0730046247e311aa8f3893af4507a93bc9313cfa67826f56d989f819269aaf5.png ../../../_images/1e966de1d44d17e00aba98dc1401e6bcb72893beb6ed6864e18b0f0a194ec5c1.png ../../../_images/ab3a28f7d9ceffdc70c5a32dcd6ec11eb8eac7f507b9eed1b39895fa591535ad.png ../../../_images/e6b453426e408ecd3ef7425f81c83d9c6f8991f66230bc691f937f451dbad6cf.png ../../../_images/302a3e32927a5cb03db4a2ed502d660726242d09a17f5284f361176525f49ceb.png ../../../_images/42ab8355944be88cf5ee590f649954836577ae5fe3ae4b9493ba2439afe53852.png ../../../_images/b53a6f7d651a76e8be31d390f7d1509c7c87c18adb7cb9fc582e48055cc0dfaa.png ../../../_images/c9d1fd0c725ec3631fb54250e99e51b7d6de46b10bc6064541258e20fbf4bbfa.png ../../../_images/6b0cf99fd2c940aa205da82f31c20dbcf8045b547abe8e31c2ea6b4df4c9e9a5.png ../../../_images/5cac2c9ad8f99c30b39bfd7c5f1d2eb5c1610ff3fb71292164cc562a5ecac75e.png ../../../_images/f2eaf0dd434ac313efcfbf1825f1c1f25f616f1309abe69cf1760868d1f674fb.png ../../../_images/cb775ad16fd2d5d21602fdd575c6136807eac31eb2393ffb2f8405d2c5ad7a0c.png ../../../_images/1f3e2e29dde3e8f30c3c4658ece23470dfead221e6dfbc249c52c285373675f2.png ../../../_images/995a5e1b1bca5103aa6e6172c80a41983819fd444c99876010e39289c461eefb.png ../../../_images/ab9f2e04e4732e46e65097c6c4b1f01ccab96268c196bfd184cbfd274b118348.png ../../../_images/0a30749a2ea132b3440a04045fc9ecf604511c3af4203936f76e014c68056201.png ../../../_images/653fd35f162a5e6f88596027d2cdf1cfe23c99da9d54e51ca12925267872a9f5.png ../../../_images/c8285bede2cfacaea077359af6225a55ba9ee5f25a06f034f9cc3015fca50f8e.png ../../../_images/0c15614caad768f60d520ef2a0bf578e57254dc5c8385b6b61107f9086e95abe.png ../../../_images/08c3f1baebda839f853294fb0677bbe0ce6b046c99b6ac2c7ff51fed709374b4.png ../../../_images/5a9dc031d9b46f42d1a18513056fc277a3fc924ae393f1ff26d9a67a561f807e.png

Good Photometric Objects #

Look at photometric error distribution to pick out good photometry objects as a function of magnitude #

The photometric error is mainly a function of magnitude. We make a cut slightly above the typical error to exclude objects that have poor photometry. (In the SWEEPS field, that most often is the result of blending and crowding.)

f606w = tab['a_f606w']
f814w = tab['a_f814w']
RminusI = f606w-f814w

w = np.where((RminusI > -1) & (RminusI < 4))[0]
f606w_mad = tab['a_f606w_mad']
f814w_mad = tab['a_f814w_mad']

t0 = time.time()
# Calculate the point density
x1 = np.array(f606w[w])
y1 = np.array(f606w_mad[w])
y1log = np.log(y1)
myPDF1, axes1 = fastKDE.pdf(x1, y1log, numPoints=2**10+1)
finterp = RectBivariateSpline(axes1[1], axes1[0], myPDF1)
z1 = finterp(y1log, x1, grid=False)
# Sort the points by density, so that the densest points are plotted last
idx = z1.argsort()
xs1, ys1, zs1 = x1[idx], y1[idx], z1[idx]

# select a random subset of points in the most crowded regions to speed up plotting
wran = np.where(np.random.random(len(zs1))*zs1 < 0.05)[0]
print(f"Plotting {len(wran)} of {len(zs1)} points")
xs1 = xs1[wran]
ys1 = ys1[wran]
zs1 = zs1[wran]

x2 = np.array(f814w[w])
y2 = np.array(f814w_mad[w])
y2log = np.log(y2)
myPDF2, axes2 = fastKDE.pdf(x2, y2log, numPoints=2**10+1)
finterp = RectBivariateSpline(axes2[1], axes2[0], myPDF2)
z2 = finterp(y2log, x2, grid=False)
idx = z2.argsort()
xs2, ys2, zs2 = x2[idx], y2[idx], z2[idx]
print(f"{(time.time()-t0):.1f} s: completed kde")

# select a random subset of points in the most crowded regions to speed up plotting
wran = np.where(np.random.random(len(zs2))*zs2 < 0.05)[0]
print(f"Plotting {len(wran)} of {len(zs2)} points")
xs2 = xs2[wran]
ys2 = ys2[wran]
zs2 = zs2[wran]

xr = (18, 27)
xx = np.arange(501)*(xr[1]-xr[0])/500.0 + xr[0]
xcut1 = 24.2
xnorm1 = 0.03
xcut2 = 23.0
xnorm2 = 0.03

# only plot a subset of the points to speed things up
qsel = 3
xs1 = xs1[::qsel]
ys1 = ys1[::qsel]
zs1 = zs1[::qsel]
xs2 = xs2[::qsel]
ys2 = ys2[::qsel]
zs2 = zs2[::qsel]
Plotting 274435 of 443502 points
4.4 s: completed kde
Plotting 250645 of 443502 points
fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(15, 8), tight_layout=True)

ax1.scatter(xs1, ys1, c=zs1, s=2, edgecolors='none', cmap='plasma')
ax1.plot(xx, xnorm1 * (1. + 10.**(0.4*(xx-xcut1))), linewidth=2.0,
         label=f'${xnorm1:.2f} (1+10^{{0.4(M-{xcut1:.1f})}})$')

ax2.scatter(xs2, ys2, c=zs2, s=2, edgecolors='none', cmap='plasma')
ax2.plot(xx, xnorm2 * (1. + 10.**(0.4*(xx-xcut2))), linewidth=2.0,
         label=f'${xnorm2:.2f} (1+10^{{0.4(M-{xcut2:.1f})}})$')

ax1.legend(loc='upper left')
ax2.legend(loc='upper left')

ax1.set(xlabel='F606W', ylabel='F606W_MAD', yscale='log')
ax2.set(xlabel='F814W', ylabel='F814W_MAD', yscale='log')
[Text(0.5, 0, 'F814W'), Text(0, 0.5, 'F814W_MAD'), None]
../../../_images/cd651f33094e9e9a5938ec135fe3926396171984a8bd788d7d84b20b95d7c14d.png

Define function to apply noise cut and plot color-magnitude diagram with cut#

Note that we reduce the R-I range to 0-3 here because there are very few objects left bluer than R-I = 0 or redder than R-I = 3.

def noisecut(tab, factor=1.0):
    """Return boolean array with noise cut in f606w and f814w using model
    factor is normalization factor to use (>1 means allow more noise)
    """
    f606w = tab['a_f606w']
    f814w = tab['a_f814w']
    f606w_mad = tab['a_f606w_mad']
    f814w_mad = tab['a_f814w_mad']
    
    # noise model computed above
    xcut_f606w = 24.2
    xnorm_f606w = 0.03 * factor
    xcut_f814w = 23.0
    xnorm_f814w = 0.03 * factor
    return ((f606w_mad < xnorm_f606w*(1+10.0**(0.4*(f606w-xcut_f606w))))
            & (f814w_mad < xnorm_f814w*(1+10.0**(0.4*(f814w-xcut_f814w)))))
# low-noise objects
good = noisecut(tab, factor=1.0)

# Calculate the point density
w = np.where((RminusI > 0) & (RminusI < 3) & good)[0]
x = np.array(RminusI[w])
y = np.array(f606w[w])
t0 = time.time()
myPDF, axes = fastKDE.pdf(x, y, numPoints=2**10+1)
print(f"kde took {(time.time()-t0):.1f} sec")

# interpolate to get z values at points
finterp = RectBivariateSpline(axes[1], axes[0], myPDF)
z = finterp(y, x, grid=False)

# Sort the points by density, so that the densest points are plotted last
idx = z.argsort()
xs, ys, zs = x[idx], y[idx], z[idx]

# select a random subset of points in the most crowded regions to speed up plotting
wran = np.where(np.random.random(len(zs))*zs < 0.075)[0]
print(f"Plotting {len(wran)} of {len(zs)} points")
xs = xs[wran]
ys = ys[wran]
zs = zs[wran]
kde took 1.5 sec
Plotting 129618 of 333525 points
fig, ax = plt.subplots(figsize=(12, 10))

sc = ax.scatter(xs, ys, c=zs, s=2, edgecolors='none', cmap='plasma')
ax.set(xlabel='A_F606W - A_F814W', ylabel='A_F606W',
       title=f'{len(x):,} stars in SWEEPS', xlim=(-1, 4), ylim=(27.5, 17.5))
_ = fig.colorbar(sc, ax=ax)
../../../_images/74bb9fde3edf1fa1a0e2cdd646fad249b0dd69e9960eb9f52070bc6d5a5cedb2.png

Science Applications #

Proper Motions of Good Objects #

Average proper motion in color-magnitude bins#

# good defined above
f606w = tab['a_f606w']
f814w = tab['a_f814w']
RminusI = f606w-f814w
w = np.where((RminusI > 0) & (RminusI < 3) & good)[0]
lpm = np.array(tab['lpm'][w])
bpm = np.array(tab['bpm'][w])
x = np.array(RminusI[w])
y = np.array(f606w[w])

nbins = 50
count2d, yedge, xedge = np.histogram2d(y, x, bins=nbins)
lpm_sum = np.histogram2d(y, x, bins=nbins, weights=lpm-lpm.mean())[0]
bpm_sum = np.histogram2d(y, x, bins=nbins, weights=bpm-bpm.mean())[0]
lpm_sumsq = np.histogram2d(y, x, bins=nbins, weights=(lpm-lpm.mean())**2)[0]
bpm_sumsq = np.histogram2d(y, x, bins=nbins, weights=(bpm-bpm.mean())**2)[0]

ccount = count2d.clip(1) 
lpm_mean = lpm_sum/ccount
bpm_mean = bpm_sum/ccount
lpm_rms = np.sqrt(lpm_sumsq/ccount-lpm_mean**2)
bpm_rms = np.sqrt(bpm_sumsq/ccount-bpm_mean**2)
lpm_msigma = lpm_rms/np.sqrt(ccount)
bpm_msigma = bpm_rms/np.sqrt(ccount)

ww = np.where(count2d > 100)
yy, xx = np.mgrid[:nbins, :nbins]
xx = (0.5*(xedge[1:]+xedge[:-1]))[xx]
yy = (0.5*(yedge[1:]+yedge[:-1]))[yy]
fig, ax = plt.subplots(figsize=(12, 10))

Q = ax.quiver(xx[ww], yy[ww], lpm_mean[ww], bpm_mean[ww], color='red', width=0.0015)
qlength = 5
ax.quiverkey(Q, 0.8, 0.97, qlength, f'{qlength} mas/yr', coordinates='axes', labelpos='W')
ax.invert_yaxis()
_ = ax.set(xlabel='A_F606W - A_F814W', ylabel='A_F606W',
           xlim=(xedge[0], xedge[-1]), ylim=(yedge[-1], yedge[0]))
../../../_images/7a784abb038e694928b050d2d99d114a1f15ee3d39eb7003b6f99218a7baaf78.png

RMS in longitude PM as a function of color/magnitude#

Mean longitude PM as image#

fig, (ax1, ax2, ax3) = plt.subplots(nrows=1, ncols=3, figsize=(20, 6), tight_layout=True, sharey=True)

# plot ax1
p1 = ax1.scatter(xs, ys, c=zs, s=2, edgecolors='none', cmap='plasma')
ax1.set(xlabel='A_F606W - A_F814W', ylabel='A_F606W',
        title=f'Color-magnitude\n{len(x):,} stars in SWEEPS')
ax1.set(xlim=(xedge[0], xedge[-1]), ylim=(yedge[0], yedge[-1]))
ax1.invert_yaxis()
fig.colorbar(p1, ax=ax1)

# plot ax2
mask = (lpm_msigma <= 1.0) & (count2d > 10)
im2 = (lpm_mean+lpm.mean())*mask
im2[~mask] = np.nan
vmax = np.nanmax(np.abs(im2))

p2 = ax2.imshow(im2, cmap='RdYlGn', aspect="auto", origin="lower",
                extent=(xedge[0], xedge[-1], yedge[0], yedge[-1]))
ax2.set(xlabel='A_F606W - A_F814W',
        title='Mean Longitude PM\n$\\sigma(\\mathrm{PM}) \\leq 1$ and $N > 10$')
ax2.set(xlim=(xedge[0], xedge[-1]), ylim=(yedge[0], yedge[-1]))
ax2.invert_yaxis()
cb2 = fig.colorbar(p2, ax=ax2)
cb2.ax.set_ylabel('mas/yr', rotation=270, labelpad=15)

# plat ax3
im3 = lpm_rms*(count2d > 10)

p3 = ax3.imshow(im3, cmap='magma', aspect="auto", origin="lower",
                extent=(xedge[0], xedge[-1], yedge[0], yedge[-1]),
                norm=LogNorm(vmin=im3[im3 > 0].min(), vmax=im3.max()))
ax3.set(xlabel='A_F606W - A_F814W',
        title='RMS in Longitude PM\nN > 10')
ax3.set(xlim=(xedge[0], xedge[-1]), ylim=(yedge[0], yedge[-1]))
ax3.invert_yaxis()
cb3 = fig.colorbar(p3, ax=ax3)
_ = cb3.ax.set_ylabel('mas/yr', rotation=270)
../../../_images/952120aa76abb16fe17e9df34a695d9c9a4db20d21a8d299a86bf14c3d3c1360.png

Mean latitude PM as image#

fig, (ax1, ax2, ax3) = plt.subplots(nrows=1, ncols=3, figsize=(20, 6), tight_layout=True, sharey=True)

# plot ax1
p1 = ax1.scatter(xs, ys, c=zs, s=2, edgecolors='none', cmap='plasma')
ax1.set(xlabel='A_F606W - A_F814W', ylabel='A_F606W',
        title=f'Color-magnitude\n{len(x):,} stars in SWEEPS')
ax1.set(xlim=(xedge[0], xedge[-1]), ylim=(yedge[0], yedge[-1]))
ax1.invert_yaxis()
fig.colorbar(p1, ax=ax1)

# plot ax2
mask = (bpm_msigma <= 1.0) & (count2d > 10)
im2 = (bpm_mean+bpm.mean())*mask
im2[~mask] = np.nan
vmax = np.nanmax(np.abs(im2))

p2 = ax2.imshow(im2, cmap='RdYlGn', aspect="auto", origin="lower",
                extent=(xedge[0], xedge[-1], yedge[0], yedge[-1]))
ax2.set(xlabel='A_F606W - A_F814W',
        title='Mean Latitude PM\n$\\sigma(\\mathrm{PM}) \\leq 1$ and $N > 10$')
ax2.set(xlim=(xedge[0], xedge[-1]), ylim=(yedge[0], yedge[-1]))
ax2.invert_yaxis()
cb2 = fig.colorbar(p2, ax=ax2)
cb2.ax.set_ylabel('mas/yr', rotation=270, labelpad=15)

# plot ax3
im3 = bpm_rms*(count2d > 10)

p3 = ax3.imshow(im3, cmap='magma', aspect="auto", origin="lower",
                extent=(xedge[0], xedge[-1], yedge[0], yedge[-1]),
                norm=LogNorm(vmin=im3[im3 > 0].min(), vmax=im3.max()))
ax3.set(xlabel='A_F606W - A_F814W',
        title='RMS in Latitude PM\nN > 10')
ax3.set(xlim=(xedge[0], xedge[-1]), ylim=(yedge[0], yedge[-1]))
ax3.invert_yaxis()
cb3 = fig.colorbar(p3, ax=ax3)
_ = cb3.ax.set_ylabel('mas/yr', rotation=270)
../../../_images/e03e8db08217959fa53b39de0a9c6c0307be8ada9bc90ef6f6cf04b9553e2d5d.png

Proper Motions in Bulge and Disk #

Fit a smooth function to the main ridgeline of color-magnitude diagram#

Fit the R-I vs R values, but force the function to increase montonically with R. We use a log transform of the y coordinate to help.

# locate ridge line
iridgex = np.argmax(myPDF, axis=1)
pdfx = myPDF[np.arange(len(iridgex), dtype=int), iridgex]
# pdfx = myPDF.max(axis=1)
wx = np.where(pdfx > 0.1)[0]
iridgex = iridgex[wx]
# use weighted sum of 2*hw+1 points around peak
hw = 10
pridgex = 0.0
pdenom = 0.0
for k in range(-hw, hw+1):
    wt = myPDF[wx, iridgex+k]
    pridgex = pridgex + k*wt
    pdenom = pdenom + wt
pridgex = iridgex + pridgex/pdenom
ridgex = np.interp(pridgex, np.arange(len(axes[0])), axes[0])

# Fit the data using a polynomial model
x0 = axes[1][wx].min()
x1 = axes[1][wx].max()
p_init = models.Polynomial1D(9)
fit_p = fitting.LinearLSQFitter()
xx = (axes[1][wx]-x0)/(x1-x0)
yoff = 0.65
yy = np.log(ridgex - yoff)
p = fit_p(p_init, xx, yy)

# define useful functions for the ridge line


def ridge_color(f606w, function=p, yoff=yoff, x0=x0, x1=x1):
    """Return R-I position of ridge line as a function of f606w magnitude
    
    function, yoff, x0, x1 are from polynomial fit above
    """
    return yoff + np.exp(p((f606w-x0)/(x1-x0)))


# calculate grid of function values for approximate inversion
rxgrid = axes[1][wx]
rygrid = ridge_color(rxgrid)
color_domain = [rygrid[0], rygrid[-1]]
mag_domain = [axes[1][wx[0]], axes[1][wx[-1]]]
print(f"color_domain {color_domain}")
print(f"mag_domain   {mag_domain}")


def ridge_mag(RminusI, xgrid=rxgrid, ygrid=rygrid):
    """Return f606w position of ridge line as a function of R-I color
    
    Uses simple linear interpolation to get approximate value
    """
    f606w = np.interp(RminusI, ygrid, xgrid)
    f606w[(RminusI < ygrid[0]) | (RminusI > ygrid[-1])] = np.nan
    return f606w
color_domain [0.6883374859498668, 2.156051675824816]
mag_domain   [19.356585323810577, 26.480549454689026]

Plot the results to check that they look reasonable.

ridgexf = yoff + np.exp(p(xx))

fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(12, 6), tight_layout=True)

ax1.plot(axes[1][wx], ridgex, 'bo')
ax1.plot(axes[1][wx], ridgexf, color='red')
ax1.set(xlabel='R', ylabel='R-I')

# check the derivative plot to see if it stays positive
deriv = (np.exp(p(xx)) * 
         models.Polynomial1D.horner(xx, (p.parameters * np.arange(len(p.parameters)))[1:]))

ax2.semilogy(axes[1][wx], np.exp(p(xx)) *
             models.Polynomial1D.horner(xx, (p.parameters * np.arange(len(p.parameters)))[1:]), color='red')
_ = ax2.set(xlabel='R', ylabel='Fit derivative', title=f'Min deriv {deriv.min():.6f}')
../../../_images/b74bf790e6f50f6e41894bce4cba89ed8dafd45a7dbf0d3e0cf1ba681cbfafb1.png

Plot the ridgeline on the CMD#

fig, ax = plt.subplots(figsize=(12, 10))

sc = ax.scatter(xs, ys, c=zs, s=2, edgecolors='none', cmap='plasma')

# overplot ridge line
ax.plot(ridge_color(axes[1][wx]), axes[1][wx], color='red')
ax.plot(axes[0], ridge_mag(axes[0]), color='green')

ax.set(xlabel='A_F606W - A_F814W', ylabel='A_F606W',
       title=f'Color-magnitude\n{len(x):,} stars in SWEEPS',
       xlim=(xedge[0], xedge[-1]), ylim=(yedge[0], yedge[-1]))
ax.invert_yaxis()
_ = fig.colorbar(sc, ax=ax)
../../../_images/f15aa7dd4f7ddff1a91f8867b3b6aebb15a1de62fa96308987617a8a1cad25b7.png

Binned distribution of PM(Long) vs magnitude offset from ridge line#

yloc = ridge_mag(x)
x1 = x[np.isfinite(yloc)]
wy = np.where((axes[0] >= x1.min()) & (axes[0] <= x1.max()))[0]
ridgey = ridge_mag(axes[0][wy])

# Weighted histogram
dmagmin = -2.0
dmagmax = 1.0
xmax = axes[0][wy[-1]]
# xmin = axes[0][wy[0]]
xmin = 1.0
wsel = np.where((y-yloc >= dmagmin) & (y-yloc <= dmagmax) & (x >= xmin) & (x <= xmax))[0]

x2 = y[wsel]-yloc[wsel]
y2 = lpm[wsel]-lpm.mean()
hrange = (dmagmin, dmagmax)
hbins = 50
count1d, xedge1d = np.histogram(x2, range=hrange, bins=hbins)
lpm_sum1d = np.histogram(x2, range=hrange, bins=hbins, weights=y2)[0]
lpm_sumsq1d = np.histogram(x2, range=hrange, bins=hbins, weights=y2**2)[0]

ccount1d = count1d.clip(1)
lpm_mean1d = lpm_sum1d/ccount1d
lpm_rms1d = np.sqrt(lpm_sumsq1d/ccount1d-lpm_mean1d**2)
lpm_msigma1d = lpm_rms1d/np.sqrt(ccount1d)
lpm_mean1d += lpm.mean()

x1d = 0.5*(xedge1d[1:]+xedge1d[:-1])

xboundary = np.hstack((axes[0][wy], axes[0][wy[::-1]]))
yboundary = np.hstack((ridgey+dmagmax, ridgey[::-1]+dmagmin))
wb = np.where((xboundary >= xmin) & (xboundary <= xmax))
xboundary = xboundary[wb]
yboundary = yboundary[wb]
print(xboundary[0], yboundary[0], xboundary[-1], yboundary[-1])
print(xboundary.shape, yboundary.shape)
xboundary = np.append(xboundary, xboundary[0])
yboundary = np.append(yboundary, yboundary[0])

# don't plot huge error points
wp = np.where(lpm_msigma1d < 1)
1.0020693112164736 23.784969024110392 1.0020693112164736 20.784969024110392
(394,) (394,)
fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(14, 7), tight_layout=True)

ax1.scatter(xs, ys, c=zs, s=2, edgecolors='none', cmap='plasma')
ax1.plot(xboundary, yboundary, color='red')
ax1.set(xlabel='R - I (A_F606W - A_F814W)', ylabel='R (A_F606W)',
        title=f'Color-magnitude\n{len(x):,} stars in SWEEPS',
        xlim=(xedge[0], xedge[-1]), ylim=(yedge[0], yedge[-1]))
ax1.invert_yaxis()

ax2.errorbar(x1d[wp], lpm_mean1d[wp], xerr=(xedge1d[1]-xedge1d[0])/2.0, yerr=lpm_msigma1d[wp], linestyle='')
ax2.set(xlabel='Distance from ridge line [R mag]', ylabel='Mean Longitude PM [mas/yr]',
        title=f'{len(x2):,} stars in SWEEPS\n1-sigma error bars on mean PM')
ax2.invert_xaxis()
../../../_images/034c28cea8f0a2280e598922da86183076a93ad44c1675e7f21c437d937dea0d.png

Reproduce Figure 1 from Calamida et al. 2014#

image

w = np.where((RminusI > 0) & (RminusI < 3) & good)[0]

# Calculate the point density
x = np.array(RminusI[w])
y = np.array(f606w[w])
myPDF, axes = fastKDE.pdf(x, y, numPoints=2**10+1)
finterp = RectBivariateSpline(axes[1], axes[0], myPDF)
z = finterp(y, x, grid=False)
idx = z.argsort()
xs, ys, zs = x[idx], y[idx], z[idx]

# select a random subset of points in the most crowded regions to speed up plotting
wran = np.where(np.random.random(len(zs))*zs < 0.1)[0]
print(f"Plotting {len(wran)} of {len(zs)} points")
xs = xs[wran]
ys = ys[wran]
zs = zs[wran]

# locate ridge line in magnitude as a function of color
xloc = ridge_color(y)
ridgex = ridge_color(axes[1][wx])

# locate ridge line in color as function of magnitude
yloc = ridge_mag(x)
x1 = x[np.isfinite(yloc)]
wy = np.where((axes[0] >= x1.min()) & (axes[0] <= x1.max()))[0]
ridgey = ridge_mag(axes[0][wy])

# low-noise objects
print(f"Selected {len(w):,} low-noise objects")

# red objects
ylim = yloc - 1.5 - (yloc - 25.0).clip(0)/(1+10.0**(-0.4*(yloc-26.0)))
wred = np.where((y < 25) & (y > 19.5) & (y < ylim) & (x-xloc > 0.35))[0]
# wred = np.where((y<25) & (y > 19.5) & ((y-yloc) < -1.5)
#                   & (x-xloc > 0.3))[0]
#                   & (x > 1.1) & (x < 2.5) & (x-xloc > 0.2))[0]
print(f"Selected {len(wred):,} red objects")
# main sequence objects
wmain = np.where((y > 21) & (y < 22.4) & (np.abs(x-xloc) < 0.1))[0]
print(f"Initially selected {len(wmain):,} MS objects")
# sort by distance from ridge and select the closest
wmain = wmain[np.argsort(np.abs(x[wmain]-xloc[wmain]))]
wmain = wmain[:len(wred)]
print(f"Selected {len(wmain):,} MS objects closest to ridge")
Plotting 156044 of 333525 points
Selected 333,525 low-noise objects
Selected 2,797 red objects
Initially selected 59,605 MS objects
Selected 2,797 MS objects closest to ridge
fig = plt.figure(figsize=(12, 8), tight_layout=True)
gs = gridspec.GridSpec(2, 2, width_ratios=[2, 1])

ax1 = fig.add_subplot(gs[:, 0])
ax2 = fig.add_subplot(gs[0, 1])
ax3 = fig.add_subplot(gs[1, 1])

# plot ax1
ax1.scatter(xs, ys, c=zs, s=2, cmap='plasma', edgecolors='none')
ax1.scatter(x[wred], y[wred], c='red', s=2, edgecolors='none')
ax1.scatter(x[wmain], y[wmain], c='blue', s=2, edgecolors='none')
ax1.set(xlabel='F606W-F814W [mag]', ylabel='F606W [mag]', title=f'{len(x):,} stars in SWEEPS',
        xlim=(0, 3), ylim=(18, 27.5))
ax1.invert_yaxis()

# plot ax2
lrange = (-20, 5)
brange = (-12.5, 12.5)

# MS and red points in random order
wsel = w[np.append(wmain, wred)]
colors = np.array(['blue']*len(wsel))
colors[len(wmain):] = 'red'
irs = np.argsort(np.random.random(len(wsel)))
wsel = wsel[irs]
colors = colors[irs]

masks = [w, wsel]
sizes = [0.1, 2]
colors2 = ['darkgray', colors]

for mask, color, size in zip(masks, colors2, sizes):
    ax2.scatter('lpm', 'bpm', data=tab[mask], c=color, s=size)

ax2.set(ylabel='Latitude PM [mas/yr]', xlim=lrange, ylim=brange)

# plot ax3
bins = 0.5
bincount = int((lrange[1]-lrange[0])/bins + 0.5) + 1

masks = [w[wmain], w[wred]]
labels = ['MS', 'Red']
colors = ['b', 'r']

for mask, label, color in zip(masks, labels, colors):
    ax3.hist('lpm', data=tab[mask], range=lrange, bins=bincount, label=label, color=color, histtype='step')

ax3.set(xlabel='Longitude PM [mas/yr]', ylabel=f'Number [in {bins:.2} mas bins]')
_ = ax3.legend(loc='upper left')
../../../_images/d307e45e943819dbfb1e0cad61bfd7e3c7d192240fc7fe96c07a669750422dc3.png

Mean and median proper motions of bulge stars compared with SgrA*#

(-6.379 \(\pm\) 0.026, -0.202 \(\pm\) 0.019) mas/yr Reid and Brunthaler (2004)

lpmmain = np.mean(tab['lpm'][w[wmain]])
bpmmain = np.mean(tab['bpm'][w[wmain]])

norm = 1.0/np.sqrt(len(tab['lpm'][w[wmain]]))
print("Bulge stars mean PM longitude {:.2f} +- {:.3f} latitude {:.2f} +- {:.3f}".format(
    np.mean(tab['lpm'][w[wmain]]), norm*np.std(tab['lpm'][w[wmain]]),
    np.mean(tab['bpm'][w[wmain]]), norm*np.std(tab['bpm'][w[wmain]])
    ))

norm = 1.2533/np.sqrt(len(tab['lpm'][w[wmain]]))
print("Bulge stars median PM longitude {:.2f} +- {:.3f} latitude {:.2f} +- {:.3f}".format(
    np.median(tab['lpm'][w[wmain]]), norm*np.std(tab['lpm'][w[wmain]]),
    np.median(tab['bpm'][w[wmain]]), norm*np.std(tab['bpm'][w[wmain]])
    ))
Bulge stars mean PM longitude -6.31 +- 0.059 latitude -0.13 +- 0.053
Bulge stars median PM longitude -6.45 +- 0.074 latitude -0.13 +- 0.067

White Dwarfs #

w = np.where((RminusI > 0) & (RminusI < 3) & good)[0]

wwd = np.where((RminusI < 0.7)
               & (RminusI > 0) 
               & (f606w > 22.5) 
               & good 
               & (tab['lpm'] < 5) 
               & (tab['lpm'] > -20))[0]
xwd = np.array(RminusI[wwd])
ywd = np.array(f606w[wwd])

# Calculate the point density
x = np.array(RminusI[w])
y = np.array(f606w[w])

myPDF, axes = fastKDE.pdf(x, y, numPoints=2**10+1)
finterp = RectBivariateSpline(axes[1], axes[0], myPDF)
z = finterp(y, x, grid=False)
idx = z.argsort()
xs, ys, zs = x[idx], y[idx], z[idx]

# select a random subset of points in the most crowded regions to speed up plotting
wran = np.where(np.random.random(len(zs))*zs < 0.1)[0]
print(f"Plotting {len(wran)} of {len(zs)} points")
xs = xs[wran]
ys = ys[wran]
zs = zs[wran]

# locate ridge line in magnitude as a function of color
xloc = ridge_color(y)
ridgex = ridge_color(axes[1][wx])

# locate ridge line in color as function of magnitude
yloc = ridge_mag(x)
x1 = x[np.isfinite(yloc)]
wy = np.where((axes[0] >= x1.min()) & (axes[0] <= x1.max()))[0]
ridgey = ridge_mag(axes[0][wy])

# low-noise objects
print(f"Selected {len(w):,} low-noise objects")

# red objects
ylim = yloc - 1.5 - (yloc - 25.0).clip(0)/(1+10.0**(-0.4*(yloc-26.0)))
wred = np.where((y < 25) & (y > 19.5) & (y < ylim) & (x-xloc > 0.35))[0]
# wred = np.where((y < 25) & (y > 19.5) & ((y-yloc) < -1.5)
#                   & (x-xloc > 0.3))[0]
#                   & (x > 1.1) & (x < 2.5) & (x-xloc > 0.2))[0]
print("Selected {:,} red objects".format(len(wred)))
# main sequence objects
wmain = np.where((y > 21) & (y < 22.4) & (np.abs(x-xloc) < 0.1))[0]
print("Initially selected {:,} MS objects".format(len(wmain)))
# sort by distance from ridge and select the closest
wmain = wmain[np.argsort(np.abs(x[wmain]-xloc[wmain]))]
wmain = wmain[:len(wred)]
print(f"Selected {len(wmain):,} MS objects closest to ridge")
Plotting 156416 of 333525 points
Selected 333,525 low-noise objects
Selected 2,797 red objects
Initially selected 59,605 MS objects
Selected 2,797 MS objects closest to ridge
fig = plt.figure(figsize=(12, 8), tight_layout=True)
gs = gridspec.GridSpec(2, 2, width_ratios=[2, 1])

ax1 = fig.add_subplot(gs[:, 0])
ax2 = fig.add_subplot(gs[0, 1])
ax3 = fig.add_subplot(gs[1, 1])

# plot ax1
ax1.scatter(xs, ys, c=zs, s=2, cmap='plasma', edgecolors='none')
ax1.scatter(x[wred], y[wred], c='red', s=2, edgecolors='none')
ax1.scatter(x[wmain], y[wmain], c='blue', s=2, edgecolors='none')
ax1.scatter(xwd, ywd, c='green', s=10, edgecolors='none')
ax1.set(xlabel='F606W-F814W [mag]', ylabel='F606W [mag]',
        title=f'{len(x):,} stars in SWEEPS', xlim=(0, 3), ylim=(18, 27.5))
ax1.invert_yaxis()

# plot ax2
lrange = (-20, 5)
brange = (-12.5, 12.5)

# MS and red points in random order
wsel = w[np.append(wmain, wred)]
colors = np.array(['blue']*len(wsel))
colors[len(wmain):] = 'red'
irs = np.argsort(np.random.random(len(wsel)))
wsel = wsel[irs]
colors = colors[irs]

masks = [w, wsel, wwd]
sizes = [0.1, 2, 2]
colors2 = ['darkgray', colors, 'green']

for mask, color, size in zip(masks, colors2, sizes):
    ax2.scatter('lpm', 'bpm', data=tab[mask], c=color, s=size)

ax2.set(ylabel='Latitude PM [mas/yr]', xlim=lrange, ylim=brange)

# plot ax3
bins = 0.5
bincount = int((lrange[1]-lrange[0])/bins + 0.5) + 1

masks = [wwd, w[wmain], w[wred]]
labels = ['WD', 'MS', 'Red']
colors = ['g', 'b', 'r']

for mask, label, color in zip(masks, labels, colors):
    ax3.hist('lpm', data=tab[mask], range=lrange, bins=bincount, label=label, color=color, histtype='step')

ax3.set(xlabel='Longitude PM [mas/yr]', ylabel=f'Number [in {bins:.2} mas bins]')
_ = ax3.legend(loc='upper left')
../../../_images/5a5d9d8bfa48d8aa6a936346eafdbba1c0fe396b7822f31e4bf73f9fc7c175fd.png

White dwarf mean PM and uncertainity#

norm = 1.0/np.sqrt(len(tab['lpm'][wwd]))
print("WD PM mean +- stdev longitude {:.2f} +- {:.3f} latitude {:.2f} +- {:.3f}".format(
    np.mean(tab['lpm'][wwd]), norm*np.std(tab['lpm'][wwd]),
    np.mean(tab['bpm'][wwd]), norm*np.std(tab['bpm'][wwd])
    ))
WD PM mean +- stdev longitude -5.60 +- 0.152 latitude -0.03 +- 0.245

WDs generally closer to bulge (MS) PM distribution#

bins = 0.5
bincount = int((lrange[1]-lrange[0])/bins + 0.5) + 1

masks = [wwd, w[wmain], w[wred]]
labels = ['WD', 'MS', 'Red']
colors = ['g', 'b', 'r']

fig, ax = plt.subplots(figsize=(8, 6))

for mask, label, color in zip(masks, labels, colors):
    ax.hist('lpm', data=tab[mask], range=lrange, bins=bincount, label=label, color=color, histtype='step', density=True, cumulative=1)
    
ax.set(xlabel='Longitude PM [mas/yr]', ylabel='Cumulative fraction')
_ = ax.legend(loc='upper left')
../../../_images/f6feec6763b0fa9c9e5b4e20f52c171abd72051c08c488e93eab3006b9164b15.png

Look for quasar candidates (low PM blue stars) #

Note this includes all objects, not just “good” objects with low mag noise, because quasars might be variable too.

wqso1 = np.where((RminusI < 0.5)
                 & (np.sqrt(tab['bpm']**2+tab['lpm']**2) < 1.0)
                 & (tab['NumFilters'] > 2))[0]
wqso1 = wqso1[np.argsort(f606w[wqso1])]
tab[wqso1]
Table length=14
ObjIDRADecRAerrDecerrNumFiltersNumVisitsa_f606wa_f606w_na_f606w_mada_f814wa_f814w_na_f814w_madbpmlpmbpmerrlpmerrpmdevyrdTyrStartyrEnd
int64float64float64float64float64int32int32float64int32float64float64int32float64float64float64float64float64float64float64float64float64float64
4000710313439269.74005171773325-29.1960324733696620.62659640476711190.8430363739939153139418.632399559020996740.0441493988037109418.75860023498535770.22760009765625-0.27295919570065810.09336852951614050.120074967796713130.193544431979997666.6645611028851392010.410458098704312.1389362929372312003.4367273590012015.5756636519384
4000710257520269.76634477578085-29.2233101667974570.70106072268639690.9635875312674559149618.71660041809082750.0379009246826171918.785300254821777740.45225048065185547-0.579616612423514-0.0041956748645314320.12862608053011320.226135156132128127.0797343733470172010.450586592934812.1389362929372312003.4367273590012015.5756636519384
4000710307965269.7417955221381-29.1985938594320160.92059357282046350.8518786138892231149418.73979949951172730.0638999938964843818.865349769592285760.200050354003906250.3782536664095988-0.36868955902233850.21250729952998930.173450479126670947.67323066072039952010.48587754622912.1389362929372312003.4367273590012015.5756636519384
4000710352240269.7337926320315-29.181394875452980.69463568140069660.906134226177322447918.78339958190918740.02304935455322265618.512550354003906740.27725028991699220.63371794604432230.297155837378090540.150632729427004340.200701984422958786.7381081223247512009.773737000133711.3711852081716672003.4367273590012014.8079125671727
4000710347699269.7783405856686-29.179119311166470.411557788323881730.484193362005322648118.839149475097656760.033950805664062518.898500442504883730.23670005798339844-0.116781003128475530.64992525647767450.0848778177748430.110360525762329693.79050728910901662009.760042142227111.3711852081716672003.4367273590012014.8079125671727
4000710305789269.7500895465239-29.1997069628762741.23779853402990740.9596173093379398139518.883800506591797760.01639938354492187518.647899627685547750.104499816894531250.76136088973964420.060728015679124810.144585196792062830.3078910876643928.6696086746168462010.451318818121812.1389362929372312003.4367273590012015.5756636519384
4000710287023269.73049137354224-29.2087865146858370.58867814142106730.8187862653505924139319.005799293518066700.1794986724853515619.03809928894043770.4453010559082031-0.027042326411294833-0.106561818017951920.113687594145356840.185473848934728326.0600193408506422010.409980814181812.1389362929372312003.4367273590012015.5756636519384
4000711198572269.7837898799627-29.1710583461873870.242890611463202840.378799458135692845620.384400367736816520.03075027465820312520.409199714660645540.018199920654296875-0.39276687371980523-0.26149912803296160.071572579772810260.069548523746184072.63978326440014982008.052253504512210.9976219052332842003.4367273590012014.4343492642342
4000711114267269.77699533206413-29.2130203399329034.241370555135963.939764673878310843523.104300498962402320.03829956054687523.02050018310547330.07789993286132812-0.8306962177406612-0.068584987281524040.92216223248728991.349666416366427418.9259942776495722005.554219435558512.1364001366899472003.4367273590012015.573127495691
4000710335454269.7613531455845-29.1872063610708647.4742973298759631.483136231049532837723.993050575256348760.0631999969482421924.481599807739258790.20109939575195312-0.54231856855157350.70604156118027321.46469300447186380.818374901008934844.5428643744239852009.979332637238311.3711852081716672003.4367273590012014.8079125671727
4000710273567269.7541240409338-29.215787960244744.7482141647964491.694094522863292437824.544499397277832760.0755500793457031224.190000534057617780.12094974517822266-0.06045787883823814-0.296998397160966930.93281897176258610.578092189093136828.4006901952327272009.689665799419811.3413992910292122003.4367273590012014.7781266500303
4000710273213269.762844233717-29.2159930735964582.84914127596411462.158951117635643535825.248699188232422570.07260131835937525.19580078125570.114398956298828120.58745325924043080.39619888781943280.57457023306250710.489268627804960217.0350228161346032008.657572905504111.3711852081716672003.4367273590012014.8079125671727
4000710327380269.7667196275462-29.1898039056530221.49756615770321074.658478945843615535325.343299865722656520.0939502716064453125.171499252319336350.27519989013671875-0.59257648522096910.35109846224308780.400360430563875550.96311017225164223.3949711231211242008.199840995264811.3413992910292122003.4367273590012014.7781266500303
4000710296308269.7744892710409-29.204392434039083.85543709725692542.264390319511768337825.49880027770996750.0967006683349609425.095499992370605780.112750053405761720.189753023786942140.171716606536581270.71736161335096080.644308755073237427.1842846412212932009.57501534738411.3717329051641372003.43617966200852014.8079125671727
fig, ax = plt.subplots(figsize=(10, 10))
ax.scatter(xs, ys, c=zs, s=2, edgecolors='none', cmap='plasma')
ax.plot(RminusI[wqso1], f606w[wqso1], 'rs', markersize=10, fillstyle='none')
ax.set(xlim=(xedge[0], xedge[-1]), ylim=(yedge[0], yedge[-1]))
ax.set(xlabel='R - I (A_F606W - A_F814W)', ylabel='R (A_F606W)',
       title=f'{len(x):,} stars in SWEEPS\n{len(wqso1)} low PM blue stars with NumFilters$\\geq3$')
ax.invert_yaxis()
../../../_images/f4a790c42d02f89fab1263db0a15ce8e43cfd4dda23580e9b885819ffa33b5fe.png
objid = tab['ObjID']
print(f"Plotting {len(wqso1)} objects")
for o in objid[wqso1]:
    positions(o, jobs=jobs)
Plotting 14 objects
../../../_images/bb68c91361e1173460a1831ba3dc7a9b376ba195bb13c572dcfbd3e62a9e61cf.png ../../../_images/7014d6ad21aaa92e5dae300b3e53df33bee9609603226775a2a6ea47b9c48a85.png ../../../_images/5218addeade781470fd382b2c67ede123653e45d5f3a13d4b78a8042aca2b3bd.png ../../../_images/a1caf2222e39fb5c8ee46d958eb0539e7fcaa0dccfba84cbae52de8bfb7a5b62.png ../../../_images/b9a94408c5f4ec5e5a393f80b7aedb3571a050903134c78200fa9fdca0613e6e.png ../../../_images/8f965f5a42424ca0cff6972ff44fbef4556b4939e1322667a2462de439a299e8.png ../../../_images/b7291a2a2ad1479acd840d2c5585ce5e4fff98868e9a0e58421e76ffa0fe2f48.png ../../../_images/7ad7aa2b68f58cc919812799547c2aded300cd5aa75939212ffc664da0631da0.png ../../../_images/28fc5eb0353edc3ebb3e0a72d4fd272d43f7e2cd7e767c644d16d7960dec8de4.png ../../../_images/c792d3cc33bccdb0e4f24349faedaf9d4ce456cd8615abffcfddcbd77bbace75.png ../../../_images/9667b7f23378e25657f01b4abe7a9ea23cea5cca402fd4e1469bdb690884d26d.png ../../../_images/557edad85bc22180fb6f8138321e96763b17d281f3eb6417b2e810b875ebe27f.png ../../../_images/73ab46405ba713b0189c0d9be5bff9afbfdcefe703ac604419cf8e9137928833.png ../../../_images/e7167f44227325e8095261ea495eb6c1770366fff9cb8258f28a9811fa56ca00.png

Try again with different criteria on proper motion fit quality#

wqso2 = np.where((RminusI < 0.5)
                 & (np.sqrt(tab['bpm']**2+tab['lpm']**2) < 2.0)
                 & (np.sqrt(tab['bpmerr']**2+tab['lpmerr']**2) < 2.0)
                 & (tab['pmdev'] < 4.0))[0]
wqso2 = wqso2[np.argsort(f606w[wqso2])]
tab[wqso2]
Table length=10
ObjIDRADecRAerrDecerrNumFiltersNumVisitsa_f606wa_f606w_na_f606w_mada_f814wa_f814w_na_f814w_madbpmlpmbpmerrlpmerrpmdevyrdTyrStartyrEnd
int64float64float64float64float64int32int32float64int32float64float64int32float64float64float64float64float64float64float64float64float64float64
4000710347699269.7783405856686-29.179119311166470.411557788323881730.484193362005322648118.839149475097656760.033950805664062518.898500442504883730.23670005798339844-0.116781003128475530.64992525647767450.0848778177748430.110360525762329693.79050728910901662009.760042142227111.3711852081716672003.4367273590012014.8079125671727
4000711253077269.6915378419676-29.240201450193220.47048817337398590.527016297536424224618.8927001953125450.01090049743652343818.636449813842773420.1782999038696289-1.7408991354864214-0.59470771026326410.36131623059721120.74823452127072992.96134036189064352013.50340094293923.006781202524872011.80167617768172014.8084573802066
4000711347522269.70065127433986-29.2320349526647440.458137394606503550.97255062095775424318.917999267578125430.02580070495605468818.648849487304688380.28500080108642580.5245222694829835-0.90253215714022780.53318792314672771.13299016246126933.73078094247710322013.46489691614353.006781202524872011.80167617768172014.8084573802066
4000709025473269.82873131529516-29.195122574848120.54113639375424790.692045030767130524618.92685031890869440.012649536132812518.75755023956299420.38874912261962890.8548976473145768-1.15783376955440320.28769067083577350.433552708535085933.4563402628410352013.31542570356511.3719145413717642003.43617966200852014.8080942033803
4000710279756269.7207706977463-29.2123085559488870.413744843922597470.4883003181612267127018.977749824523926540.02824974060058593818.614700317382812550.35890007019042970.033714894109117335-1.6007022288058330.090524207329567410.090737501394596063.6197013826943342009.67976228942312.1389362929372312003.4367273590012015.5756636519384
4000711345953269.71277619272627-29.231306080124010.47321918364871311.004685668423822324619.01865005493164460.01224994659423828118.701799392700195390.15010070800781250.349685603051353271.91399348040913870.72542488495716681.08618790828335833.7553155526457392013.50340094293923.006781202524872011.80167617768172014.8084573802066
4000709014231269.8069522092499-29.200219944064810.38345216597794820.54906701179173724719.06329917907715460.03770065307617187518.564849853515625460.0460500717163085941.0342947783087193-1.42792407655898760.155265468888894280.368898475427276552.00174138195950762013.300790214705811.3719145413717642003.43617966200852014.8080942033803
4000710326758269.75587447129374-29.1899951880466380.399232380202015570.37137690294128917139519.077000617980957760.02159976959228515619.024099349975586770.26650047302246094-1.1141957269870415-0.86749835590098590.099480220588749970.064240536257315773.0978789147166582010.451318818121512.1389362929372312003.4367273590012015.5756636519384
4000711198572269.7837898799627-29.1710583461873870.242890611463202840.378799458135692845620.384400367736816520.03075027465820312520.409199714660645540.018199920654296875-0.39276687371980523-0.26149912803296160.071572579772810260.069548523746184072.63978326440014982008.052253504512210.9976219052332842003.4367273590012014.4343492642342
4000724763944269.7747901164537-29.265234033836870.433776731714064060.447421664711086624622.25380039215088460.0594997406005859421.910449981689453460.0336999893188476561.1821223347200740.140849289910045030.59608815874312850.42645224887957963.36392085386135162013.5031453570163.006781709597822011.8014940660382014.8082757756358
fig, ax = plt.subplots(figsize=(10, 10))
ax.scatter(xs, ys, c=zs, s=2, edgecolors='none', cmap='plasma')
ax.plot(RminusI[wqso2], f606w[wqso2], 'rs', markersize=10, fillstyle='none')
ax.set(xlabel='R - I (A_F606W - A_F814W)', ylabel='R (A_F606W)',
       xlim=(xedge[0], xedge[-1]), ylim=(yedge[0], yedge[-1]),
       title=f'{len(x):,} stars in SWEEPS\n{len(wqso2)} low PM blue stars\nwith NumFilters$\\geq3$')
ax.invert_yaxis()
../../../_images/b69ea88d5b1a61377397c087c5384ecb8fd5ee5b93143fa3578755d98c3593af.png
objid = tab['ObjID']
print(f"Plotting {len(wqso2)} objects")
for o in objid[wqso2]:
    positions(o, jobs=jobs)
Plotting 10 objects
../../../_images/b9a94408c5f4ec5e5a393f80b7aedb3571a050903134c78200fa9fdca0613e6e.png ../../../_images/eb98858449881bf53f06f2accebccb753aee220b7c9206eef027073bdc4af9c6.png ../../../_images/78e8f80751b8525b2b90c2f65a20a96ac0ead9a322fca6b327d2889fa44bd087.png ../../../_images/cd5af5fadea2430d3b27918207238058b8d886203bd2bf1db42ceea3e41f4e39.png ../../../_images/b22d87d79bbcc370de20da1c1f9a5d25d093b42e4c1841d6b932612cc8a77218.png ../../../_images/d55580cf37ea75bd43b51b36f9e40546a2059d5bd7f35f0d1c185a0129c71e02.png ../../../_images/130189c3c1f540a528107843ba16af57fb371bfec8b1991880c012bd3be98b5a.png ../../../_images/18a50a91b2a26229a48fe2e70a97c8b94a1dbfefd878845920d1d47f7df52464.png ../../../_images/7ad7aa2b68f58cc919812799547c2aded300cd5aa75939212ffc664da0631da0.png ../../../_images/d113e2ea15f59016f3c68a69e554f0913e8806cb671d166f2b561e0d64d85bcc.png

High Proper Motion Objects #

Get a list of objects with high, accurately measured proper motions. Proper motions are measured relative to the main sequence sample (Galactic center approximately).

f606w = tab['a_f606w']
f814w = tab['a_f814w']
RminusI = f606w-f814w
lpm0 = np.array(tab['lpm'])
bpm0 = np.array(tab['bpm'])
lpmerr0 = np.array(tab['lpmerr'])
bpmerr0 = np.array(tab['bpmerr'])
pmtot0 = np.sqrt((bpm0-bpmmain)**2+(lpm0-lpmmain)**2)
pmerr0 = np.sqrt(bpmerr0**2+lpmerr0**2)

# sort samples by decreasing PM
wpml = np.where((pmtot0 > 12) & (pmtot0 < 15) & (pmerr0 < 1.0) & good)[0]
wpml = wpml[np.argsort(-pmtot0[wpml])]
xpml = np.array(RminusI[wpml])
ypml = np.array(f606w[wpml])

wpmh = np.where((pmtot0 > 15) & (pmerr0 < 1.0) & good)[0]
wpmh = wpmh[np.argsort(-pmtot0[wpmh])]
xpmh = np.array(RminusI[wpmh])
ypmh = np.array(f606w[wpmh])

# Calculate the point density
w = np.where((RminusI > -1) & (RminusI < 4) & good)[0]
x = np.array(RminusI[w])
y = np.array(f606w[w])
t0 = time.time()
myPDF, axes = fastKDE.pdf(x, y, numPoints=2**10+1)
print(f"kde took {(time.time()-t0):.1f} sec")

# interpolate to get z values at points
finterp = RectBivariateSpline(axes[1], axes[0], myPDF)
z = finterp(y, x, grid=False)

# Sort the points by density, so that the densest points are plotted last
idx = z.argsort()
xs, ys, zs = x[idx], y[idx], z[idx]

# select a random subset of points in the most crowded regions to speed up plotting
wran = np.where(np.random.random(len(zs))*zs < 0.05)[0]
print(f"Plotting {len(wran)} of {len(zs)} points")
xs = xs[wran]
ys = ys[wran]
zs = zs[wran]
kde took 1.4 sec
Plotting 98616 of 333743 points
fig, ax = plt.subplots(figsize=(12, 10))

ax.scatter(xs, ys, c=zs, s=2, edgecolors='none', cmap='plasma')
ax.scatter(xpml, ypml, s=40, c="green", marker="^", label='10 mas/yr < PM < 15 mas/yr')
ax.scatter(xpmh, ypmh, s=80, c="red", marker="^", label='15 mas/yr < PM')

ax.set(xlabel='A_F606W - A_F814W', ylabel='A_F606W',
       title=f'High proper motions relative to mean of main sequence sample (bulge)\n{len(x):,} stars in SWEEPS')

ax.invert_yaxis()
ax.legend()
<matplotlib.legend.Legend at 0x7f3fa5605ed0>
../../../_images/12dbbadb365448753168722136db57cb619e9e1319a9591d29201fa6de027637.png
print(f"Plotting {len(wpmh)} objects")
for o in tab["ObjID"][wpmh]:
    positions(o, jobs=jobs)
Plotting 34 objects
../../../_images/502ef6eb828b996c1622aaf72be4e150ce530f5c7e8b49a68cd2794bc2ce6c2d.png ../../../_images/2c0ebe53209d654c53ce0dfd198a9969d35d006264f245e5f2fb80b94d2d1cfa.png ../../../_images/b0db9e27bf7a14f33afacfe60db08c2c7a6b7bf31fc71c2a1be10bab8c6c4dad.png ../../../_images/105fb762becb67b4d4e535dfa57480efdb927d4fcd44188f94d2629678ca927c.png ../../../_images/a5ba944438f8f325880e67552027730f89a5fadc7535be52a885cd138f927dd3.png ../../../_images/33bef1426bd5a7ddb054749e7aa3f59dd21b75968a0471e6db3cbd86f2dc3958.png ../../../_images/c88476739716fc0bbf4d8f508ac27a8d3fae7076d9c65039fdb6a36f6b8c2140.png ../../../_images/42bbe3edfac6ab167c6564c7fd1056dd7f9452028b4dfe2242ccf9f7ea9e157c.png ../../../_images/c4394641a10de9bf4914ac8ade955016b976cf59d524af856877ce0408062c33.png ../../../_images/8d683af1a676fe22b35b37b2f653cbfaf21953dabfcd9601d794f8240c872769.png ../../../_images/14e0fc6b80cc8c295517a7aaf6f214353f11ac5bab123fdaec570c255c82466d.png ../../../_images/79d92d51dddc98398c5f2a8720fa820d27b5951658b0c8c5a14fac2ab1e25c42.png ../../../_images/5430dbbf5855ea65276f06e182972dca35cb0a9920f1ac187f1955725b03c9b6.png ../../../_images/07360b327edeade81ff777808872064ff35a6efd2e0170ba6db8f9b461a86c9e.png ../../../_images/c74965dc114dc24bf96e20a5b5fb8a76b82c8fdba35014b9c2c8d15c8bed0293.png ../../../_images/450b4ab1f3379b7b7c1657d97b74fc015cb4be567757b29345e0067bd5db2259.png ../../../_images/91fb95b61fe9179f6ba7302692e6bdb6522dbbab98a1bc70ade0df70292ea3da.png ../../../_images/39bb2ab7ef042c9215ba9fdb9ee2c45351c39a065ac0ec387d0d6e3b80baf0a4.png ../../../_images/f72d2b884f2d4756394a451495f8c8eae99b267608be3e803a95e8dfa31d521d.png ../../../_images/62ede599d8f9f470b1f7b46c7fbe634312a19102698b2d00fb8a651c0c8339a1.png ../../../_images/4f64dac62a5d2008f4175250e98300f394b4c281f0e635b6819e77565c835843.png ../../../_images/740e338b8b661a17953c6842c883e6732c4891878ba0d89025e7d4b6fad42607.png ../../../_images/8d6068f56e226a6bbcd91b01d7bd29f5c02063e6348109100a7f7f95bae58815.png ../../../_images/1d3ada4ad90c88f3745bf9feb7cec710ed24059ff5eba187f13fef44fcbb98f9.png ../../../_images/6b0612061627c37eece1742a085764a9d9e7a32e07ff3cddf67be700e209b5b1.png ../../../_images/574c76ace9969830aa1d855334b3a47efa3c8d3fccd22a26f1559084d16019ba.png ../../../_images/fb76681b6b98cd0e5c5e5ba283072b5fba335041e25d3b9fc9ba8134d047dcc6.png ../../../_images/3bf947460864b0de7298e43f82a8733065f71af1b98dbec35ae1491d84e4356f.png ../../../_images/1b98b31ed288a535d2ab904c0c008e7104212474f2639bee1d68c280bbc9b4a9.png ../../../_images/49f7613bb745ab0874f7ceec4be5bd2309fd39731c6f6dc88724032b54735920.png ../../../_images/298927a47d254bc4ed672343fa344eed739550aad2e97a5598165edf88bde696.png ../../../_images/d733c9564b0611c8d4071338228eacfb33853ba2acc25d89d07be507ab60bbbc.png ../../../_images/2ea0e08e7fa4aa35430225d5af64f977466bf6fa6a1d7ab016e75f14ed240f9b.png ../../../_images/36556035f81e0a5552c0752e2b6c69ba038173df47e97c7c372f3de3bb12d22f.png

Very red high proper motion objects#

wpmred =  np.where((pmtot0 > 12) & (pmerr0 < 1.0) & (RminusI > 2.6) & good)[0]
print(f"Plotting {len(wpmred)} objects")
for o in tab["ObjID"][wpmred]:
    positions(o, jobs=jobs)
Plotting 4 objects
../../../_images/2c0ebe53209d654c53ce0dfd198a9969d35d006264f245e5f2fb80b94d2d1cfa.png ../../../_images/450b4ab1f3379b7b7c1657d97b74fc015cb4be567757b29345e0067bd5db2259.png ../../../_images/a122c0d8d8dead738fc292a0785b872c830a68b8c935ceb158eb6c8a0b981c04.png ../../../_images/39a9c33debbbf49ea79128a7e7da7e46d38bff4b9d5a6090b4401b761d4ec07e.png

Very blue high proper motion objects#

wpmblue =  np.where((pmtot0 > 8) & (pmerr0 < 1.0) & (RminusI < 0.5) & good)[0]
print(f"Plotting {len(wpmblue)} objects")
for o in tab["ObjID"][wpmblue]:
    positions(o, jobs=jobs)
Plotting 12 objects
../../../_images/a202abddcc8506a78ad7f4cefce50ca730ef9962ff6f32b4ebf193212ab2dc21.png ../../../_images/dec8953772088195a5aa9254515db05b7a94c36a73d2b6560e861ea40c0fe261.png ../../../_images/39bb2ab7ef042c9215ba9fdb9ee2c45351c39a065ac0ec387d0d6e3b80baf0a4.png ../../../_images/88800adfa0dcfaa79cc4dd3d2d3b17f83b1c02ca363520632707923d90c00aa0.png ../../../_images/fcc6b9dd946eab73eb0a5c3a87aae40b255c5e4d8c81c9e95bed3876ea3a74f8.png ../../../_images/76ab00c9ce2a6cd40f5e3404102e494372949cf6fc9a7558e15ad6fcaf8243ce.png ../../../_images/ec3c29afd56af1f14b53ee51e56b1a680f11d4f6d095ebc3d3c2105a8143217f.png ../../../_images/f6dab4e3b43b8a6f98a9ad67d685e3c1b5620c5a8eca371dceb6d1a9a1130b19.png ../../../_images/eab86b1d5115eefad9850dfa3896381a713793d4524538165289fd4057362d55.png ../../../_images/982795da82388f3846a28b9b79f74c16adaf4656e743c946cf9efe5f0de33c97.png ../../../_images/9ff2ea1d9c54f1509003b36a60be636f98372331392584f19af97fb27eb5b186.png ../../../_images/bb9c08a02888d66158f2aea2ecb5d3c31b61f8ba64b047361c7e665a35940f7e.png

Get HLA cutout images for selected objects #

Get HLA color cutout images for the high-PM objects. The query_hla function gets a table of all the color images that are available at a given position using the f814w+f606w filters. The get_image function reads a single cutout image (as a JPEG color image) and returns a PIL image object.

See the documentation on HLA VO services and the fitscut image cutout service for more information on the web services being used.

def query_hla(ra, dec, size=0.0, imagetype="color", inst="ACS", format="image/jpeg",
              spectral_elt=("f814w", "f606w"), autoscale=95.0, asinh=1, naxis=33):
    # convert a list of filters to a comma-separated string
    if not isinstance(spectral_elt, str):
        spectral_elt = ",".join(spectral_elt)
    siapurl = (f"https://hla.stsci.edu/cgi-bin/hlaSIAP.cgi?"
               f"pos={ra},{dec}&size={size}&imagetype={imagetype}&inst={inst}"
               f"&format={format}&spectral_elt={spectral_elt}"
               f"&autoscale={autoscale}&asinh={asinh}"
               f"&naxis={naxis}")
    votable = Table.read(siapurl, format="votable")
    return votable


def get_image(url):
    """Get image from a URL"""
    r = requests.get(url)
    im = Image.open(BytesIO(r.content))
    return im
# display earliest and latest images side-by-side
# wsel = wpmred
# wsel = wpmblue
# top 10 highest PM objects
wsel = wpmh[:10]
nim = len(wsel)
icols = 1        # objects per row
ncols = 2*icols  # two images for each object
nrows = (nim+icols-1)//icols

imsize = 33
xcross = np.array([-1, 1, 0, 0, 0])*2 + imsize/2
ycross = np.array([0, 0, 0, -1, 1])*2 + imsize/2

# selected data from tab
sd = tab[['RA', 'Dec', 'ObjID']][wsel]

# create the figure
fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=(12, (12/ncols)*nrows))

# iterate each observation, and each set of axes for the first and last image
for (ax1, ax2), obj in zip(axes, sd):
    
    # get the image urls and observation datetime
    hlatab = query_hla(obj["RA"], obj["Dec"], naxis=imsize)[['URL', 'StartTime']]
    # sort the data by the observation datetime, and get the first and last observation url
    (url1, time1), (url2, time2) = hlatab[np.argsort(hlatab['StartTime'])][[0, -1]]
    
    # get the images
    im1 = get_image(url1)
    im2 = get_image(url2)
    
    # plot the images
    ax1.imshow(im1, origin="upper")
    ax2.imshow(im2, origin="upper")
    
    # plot the center
    ax1.plot(xcross, ycross, 'g')
    ax2.plot(xcross, ycross, 'g')
    
    # labels and titles
    ax1.set(ylabel=f'ObjID {obj["ObjID"]}', title=time1)
    ax2.set_title(time2)
../../../_images/3ac0a764f928403ce380a841b7ede693e36ec28bea7e9acea4bff91e241879f6.png

Look at the entire collection of images for the highest PM object#

i = wpmh[0]

# selected data
sd = tab['ObjID', 'RA', 'Dec', 'a_f606w', 'a_f814w', 'bpm', 'lpm', 'yr', 'dT'][i]
display(sd)

imsize = 33
# get the URL and StartTime data
hlatab = query_hla(sd['RA'], sd['Dec'], naxis=imsize)[['URL', 'StartTime']]
# sort the data
hlatab = hlatab[np.argsort(hlatab['StartTime'])]

nim = len(hlatab)
ncols = 8
nrows = (nim+ncols-1)//ncols

xcross = np.array([-1, 1, 0, 0, 0])*2 + imsize/2
ycross = np.array([0, 0, 0, -1, 1])*2 + imsize/2
Row index=208405
ObjIDRADeca_f606wa_f814wbpmlpmyrdT
int64float64float64float64float64float64float64float64float64
4000711121911269.7367256594695-29.20969991911761820.6727504730224618.963199615478516-18.43788346257518-36.801459330875692004.1942381434012.749260607770275
# get the images: takes about 90 seconds for 77 images
images = [get_image(url) for url in hlatab['URL']]
# create the figure
fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=(20, (20/ncols)*nrows), tight_layout=True)
# flatten the axes for easy iteration and zipping
axes = axes.flat

plt.rcParams.update({"font.size": 11})
for ax, time1, img in zip(axes, hlatab['StartTime'], images):
    # plot image
    ax.imshow(img, origin="upper")
    # plot the center
    ax.plot(xcross, ycross, 'g')
    # set the title
    ax.set_title(time1)
    
# remove the last 3 unused axes
for ax in axes[nim:]:
    ax.remove()

_ = fig.suptitle(f"ObjectID: {sd['ObjID']}\nRA: {sd['RA']:0.2f} Dec: {sd['Dec']:0.2f}\nObservations: {nim}", y=1, fontsize=14)
../../../_images/a27d8289a52a2528a5d510e5eb51676632658c287e17fe92d7b9126a03b52d5b.png