def query_target(target_coord, df, dist=1*u.arcsec, verbose=True): """Cross-match target coordinates to HARPS database. Parameters ---------- targ_coord : astropy.coordinates.SkyCoord targ_coord df : pandas.DataFrame dataframe/ table of HARPS data downloaded previously dist : astropy.units angular distance within which to find nearest HARPS object verbose : bool print texts Returns ------- res : pandas.DataFrame resulting masked dataframe """ if verbose: print('\nQuerying objects within {} of ra,dec=({},{})\n'.format(dist, target_coord.ra.value,target_coord.dec.value)) coords = SkyCoord(ra=df['RA_deg'], dec=df['DEC_deg'], unit=u.deg) #compute angular separation between target and all HARPS objects and #check which falls within `dist` idxs = target_coord.separation(coords)<dist #check if there is any within search space if idxs.sum() > 0: #result may be multiple objects res = df[idxs] if verbose: msg='There are {} matches: {}'.format(len(res), res['Target'].values) print(msg) # logging.info(msg) print('{}\n\n'.format(df.loc[idxs,df.columns[7:14]].T)) return res else: #find the nearest HARPS object in the database to target idx, sep2d, dist3d = match_coordinates_3d(target_coord, coords, nthneighbor=1) nearest_obj = df.iloc[[idx]]['Target'].values[0] ra,dec = df.iloc[[idx]][['RA_deg','DEC_deg']].values[0] msg='Nearest HARPS obj to target is\n{}: ra,dec=({:.4f},{:.4f})\n'.format(nearest_obj,ra,dec) print(msg) # logging.info(msg) print('Try angular distance larger than d={:.4f}\"\n'.format(sep2d.arcsec[0])) return None
def catalog_match_track( coords, catalog, affine_param, adj_sep_sgn=True, ): """Map Astropy SkyCoord to orbit catalog. Does a ``match_coordinates_3d`` using orbit as catalog to get the time index and 3d distance. Returns ------- afn : Quantity the affine parameter x, y : Quantity distance in tangent plane """ idx, _, sep3d, = match_coordinates_3d(coords, catalog) afn = affine_param[idx] # now project onto plane orthogonal to curve # TODO more rigorous tangent vector method cart = catalog.cartesian tvec = cart[idx + 1] - cart[idx - 1] # tangent vector that = tvec / np.linalg.norm(tvec) # define vectors along curve from central point delta2 = cart[idx + 1] - cart[idx] delta1 = cart[idx - 1] - cart[idx] fac = np.divide(np.dot(that, delta1), np.dot(that, delta2)) xvec = delta1 - fac * delta2 xhat = xvec / np.linalg.norm(xvec) yvec = np.cross(that, xvec) yhat = yvec / np.linalg.norm(yvec) # basic projection # https://en.wikipedia.org/wiki/Vector_projection x = np.dot(sep3d, xhat) y = np.dot(sep3d, yhat) # and error in projection d_afn = np.dot(sep3d, that) return afn, x, y, d_afn
def _clean_source_list(self, sources): """ Function to clean surces from the catalog removing sources near the borders, with 10 pixel tolerance, and to remove blended sources (within 8") Parameters ---------- sources : pandas.DataFrame Catalog with sources to be removed Returns ------- sources : pandas.DataFrame Clean catalog """ print("Cleaning sources table...") # find sources inside the image with 10 pix of inward tolerance inside = ((sources.row > 10) & (sources.row < 1014) & (sources.col > 10) & (sources.col < 1090)) sources = sources[inside].reset_index(drop=True) # find well separated sources s_coords = SkyCoord(sources.ra, sources.dec, unit=("deg")) midx, mdist = match_coordinates_3d(s_coords, s_coords, nthneighbor=2)[:2] # remove sources closer than 8" = 2 pix closest = mdist.arcsec < 8.0 blocs = np.vstack([midx[closest], np.where(closest)[0]]) bmags = np.vstack([ sources.phot_g_mean_mag[midx[closest]], sources.phot_g_mean_mag[np.where(closest)[0]], ]) faintest = [ blocs[idx][s] for s, idx in enumerate(np.argmax(bmags, axis=0)) ] unresolved = np.in1d(np.arange(len(sources)), faintest) del s_coords, midx, mdist, closest, blocs, bmags sources = sources[~unresolved].reset_index(drop=True) return sources
def clean_source_list(sources): print("Cleaning sources table:") # remove bright/faint objects # sources = sources[ # (sources.phot_g_mean_flux > 1e3) & (sources.phot_g_mean_flux < 1e6) # ].reset_index(drop=True) # print(sources.shape) # find well separated sources s_coords = SkyCoord(sources.ra, sources.dec, unit=("deg")) midx, mdist = match_coordinates_3d(s_coords, s_coords, nthneighbor=2)[:2] # remove sources closer than 4" = 1 pix closest = mdist.arcsec < 8.0 blocs = np.vstack([midx[closest], np.where(closest)[0]]) bmags = np.vstack( [ sources.phot_g_mean_mag[midx[closest]], sources.phot_g_mean_mag[np.where(closest)[0]], ] ) faintest = [blocs[idx][s] for s, idx in enumerate(np.argmax(bmags, axis=0))] unresolved = np.in1d(np.arange(len(sources)), faintest) del s_coords, midx, mdist, closest, blocs, bmags sources = sources[~unresolved].reset_index(drop=True) print(sources.shape) # find sources inside the image with 10 pix of inward tolerance inside = ( (sources.row > 10) & (sources.row < 1014) & (sources.col > 10) & (sources.col < 1090) ) sources = sources[inside].reset_index(drop=True) print(sources.shape) print("done!") return sources
def track_fn(coords, tol=None, init_sampler: T.Union[float, int] = 1e4): """Map coordinates to catalog projection. .. todo:: change defualt `tol` to something else Parameters ---------- coords: SkyCoord tol : float or None, optional If None (default), does catalog match but no further minimization. The catalog is the evaluation of the "method" function with affine parameter linearly sampled with `init_sampler` points between "afn_bounds" init_sampler : int or float, optional the number of points in ``np.linspace`` for an inital sampling of the affine parameter. """ _aff = np.linspace( # affine parameter *t_bnds, num=int(init_sampler))[1:-2] catalog = _track_fn(_aff) if tol is None: return catalog_match_track( coords, catalog=catalog, affine_param=_aff, adj_sep_sgn=True, ) else: # TODO actual minimization # initial guess idx, sep2d, _, = match_coordinates_3d(coords, catalog) raise ValueError("Not yet implemented") return idx
def indices_xmatch_coords( catalog, other, maxdist=1 * u.arcsec, obstime=None, nthneighbor: int = 1, ): """Basic 2-catalog x-match. Returns match indices. see https://docs.astropy.org/en/stable/coordinates/matchsep.html Parameters ---------- catalog, other : SkyCoord or BaseCoordinateFrame `catalog` is the "catalogcoord", `other` are the "matchcoord". Note that this is in the opposite order as Astropy. maxdist : `~astropy.units.Angle` or `~astropy.coordinates.Distance`, optional The maximum separation to be considered a match. If Angle, does an on-sky x-match using :func:`~astropy.coordinates.match_coordinates_sky`. If Distance, does a 3d x-match using :func:`~astropy.coordinates.match_coordinates_3d`. obstime : Time, optional If provided, the "epoch" at which to x-match the coords. If None (default), will use the obstime of the first catalog to have an obstime. An absence of obstime in a catalog means the coordinates are *right now*, if this is not the case, ensure the catalog has this information. Returns ------- catalog_idx : integer array indices into `catalog` for the x-match. info : dict Useful information. - sep2d : on-sky separation (Angle) - dist3d : 3D distance (Quantity) Other Parameters ---------------- nthneighbor : int The nthneighbor to use in ``match_coordinates_``. TODO, rename to "_nth" but "quantity_input" can't handle underscores. See Also -------- :func:`~astropy.coordinates.match_coordinates_sky` :func:`~astropy.coordinates.match_coordinates_3d` """ if u.get_physical_type(maxdist.unit) == "angle": idx, sep2d, dist3d = coord.match_coordinates_sky( matchcoord=other, catalogcoord=catalog, nthneighbor=nthneighbor, ) sep = sep2d # separation constraints on this elif u.get_physical_type(maxdist.unit) == "length": idx, sep2d, dist3d = coord.match_coordinates_3d( matchcoord=other, catalogcoord=catalog, nthneighbor=nthneighbor, ) sep = dist3d # separation constraints on this # separation constraints midx = np.where(sep < maxdist)[0] if len(midx) == 0: # no matches return False, False, {} elif len(midx) == 1: # correct for case of only 1 match midx = midx[0] sel = ... else: sel = midx catalog_idx = idx[sel] other_idx = midx info = {"sep2d": sep2d[sel], "dist3d": dist3d[sel]} return catalog_idx, other_idx, info
# take a moment to calculate what the matched catalog contains c_master = coord.SkyCoord(x=hosts_data_master['halo_x'], y=hosts_data_master['halo_y'], z=hosts_data_master['halo_z'], unit='Mpc', frame='icrs', representation='cartesian') c_matching = coord.SkyCoord(x=hosts_data['halo_x'], y=hosts_data['halo_y'], z=hosts_data['halo_z'], unit='Mpc', frame='icrs', representation='cartesian') matchid, _, sep3d = coord.match_coordinates_3d(c_matching, c_master) mask = (sep3d.value <= hosts_data['halo_rvir'] * .001 * .1) print 'Matches found: ', np.sum(mask) # let the calculation be done to add additional marks to the main catalog (and satellite number) and then # make a cut with only the matched galaxies. We won't do satellite numbers because it will likely # not work terribly well due to statistics. # add calculated marks vratio_temp = hosts_data['halo_vmax'] / (np.sqrt( gnewton * hosts_data['halo_mass'] / hosts_data['halo_rvir'])) cnfw_temp = hosts_data['halo_rvir'] / hosts_data['halo_rs'] # now we want to go host halo by host halo, match pid to subhalos, and # count the number that meet our requirements and then add this on
def __init__(self, tpfs, radius_limit=6, flux_limit=1e4): """ Class to work with TPF collections Parameters: ----------- tpfs : lk.TargetPixelFileCollection Collection of target pixel files from Kepler or TESS. These TPFs must be from the same quarter, campaign or sector (they must share the same time basis) radius_limit : int The radius in pixels out to which we will consider flux to be part of the PSF flux_limit : float The limit in flux where we will calculate the PSF model. The PSF model will still be applied to fainter targets than this limit. Attributes: ----------- time: np.ndarray Time array of measurements flux: np.ndarray Flux values from TPFs with shape (ntimes x npixels) flux_err: np.ndarray Flux error values from TPFs with shape (ntimes x npixels) unw: np.ndarray Array which specifies which TPF a pixel came from. Has dimenions of ntimes x npixels. (Name is "unw[rap]") ra : np.ndarray The RA of every pixel dec : np.ndarray The declination of every pixel GaiaData : pyia.GaiaData The gaia data for all sources nearby to pixels sources : pd.DataFrame Dataframe containing all the sources nearby to pixels. This is separate from GaiaData attribute for convenience dx : np.ndarray The x distance from every source at every pixel. Has dimensions nsources x npixels dy : np.ndarray The y distance from every source at every pixel. Has dimensions nsources x npixels gv : np.ndarray The gaia flux of each target, has dimensions nsources x npixels close_mask : np.ndarray Boolean mask, True where the distance to a source is under `radius_limit`. Has shape nsources x npixels. mask : np.ndarray Boolean mask, True where there is expected to be significant flux from the PSF of a source. Has shape nsources x npixels. xcent : np.ndarray The x centroid position of the sources, as a function of time. Has dimensions (ntime) ycent : np.ndarray The y centroid position of the sources, as a function of time. Has dimensions (ntime) """ self.tpfs = tpfs self.time = tpfs[0].time bad_cadences = np.hypot(tpfs[0].pos_corr1, tpfs[0].pos_corr2) > 10 self.flux = np.hstack( [np.hstack(tpf.flux.transpose([2, 0, 1])) for tpf in tpfs]) self.flux_err = np.hstack( [np.hstack(tpf.flux_err.transpose([2, 0, 1])) for tpf in tpfs]) self.flux_err[bad_cadences] *= 1e2 self.nt = len(self.time) self.npixels = self.flux.shape[1] self.ntpfs = len(self.tpfs) # We need this to know which pixels belong to which TPF later. self.unw = np.hstack([ np.zeros( (tpf.shape[0], tpf.shape[1] * tpf.shape[2]), dtype=int) + idx for idx, tpf in enumerate(tpfs) ]) # Find the locations of all the pixels locs = [ np.mgrid[tpf.column:tpf.column + tpf.shape[2], tpf.row:tpf.row + tpf.shape[1]].reshape( 2, np.product(tpf.shape[1:])) for tpf in tpfs ] locs = np.hstack(locs) self.locs = locs self.ra, self.dec = tpfs[0].wcs.wcs_pix2world( np.vstack([(locs[0] - tpfs[0].column), (locs[1] - tpfs[0].row)]).T, 1).T # Create a set of sources from Gaia c = SkyCoord(self.ra, self.dec, unit='deg') self.GaiaData = get_sources(self, magnitude_limit=18) sources = self.GaiaData.data.to_pandas() coords = SkyCoord(sources.ra, sources.dec, unit=('deg')) slocs = np.asarray([ tpfs[0].wcs.wcs_world2pix( np.vstack([sources.ra[idx], sources.dec[idx]]).T, 0.5)[0] for idx in range(len(sources)) ]) sources['y'] = slocs[:, 1] + tpfs[0].row sources['x'] = slocs[:, 0] + tpfs[0].column self.dx, self.dy, self.gv = np.asarray([ np.vstack([ locs[0] - sources['x'][idx], locs[1] - sources['y'][idx], np.zeros(len(locs[0])) + sources.phot_g_mean_flux[idx] ]) for idx in range(len(sources)) ]).transpose([1, 0, 2]) l, d = match_coordinates_3d(coords, coords, nthneighbor=2)[:2] dmag = np.abs( np.asarray(sources.phot_g_mean_mag) - np.asarray(sources.phot_g_mean_mag[l])) pixel_dist = np.abs(d.to('arcsec')).value / 4 # Find the faint contaminated stars bad = pixel_dist < 1.5 blocs = np.vstack([l[bad], np.where(bad)[0]]).T mlocs = np.vstack([ sources.phot_g_mean_mag[l[bad]], sources.phot_g_mean_mag[np.where(bad)[0]] ]).T faintest = [ blocs[idx][i] for idx, i in enumerate(np.argmax(mlocs, axis=1)) ] bad = np.in1d(np.arange(len(sources)), faintest) r = np.hypot(self.dx, self.dy) self.close_mask = (r < radius_limit) # close_to_pixels = (np.hypot(self.dx, self.dy) < 1).sum(axis=1) # Identifing targets that are ON silicon surrounded = np.zeros(len(sources), bool) for t in np.arange(self.ntpfs): xok = (np.asarray(sources['x']) > self.locs[0, self.unw[0] == t] [:, None]) & (np.asarray(sources['x']) < (self.locs[0, self.unw[0] == t][:, None] + 1)) yok = (np.asarray(sources['y']) > self.locs[1, self.unw[0] == t] [:, None]) & (np.asarray(sources['y']) < (self.locs[1, self.unw[0] == t][:, None] + 1)) surrounded |= (xok & yok).any(axis=0) bad |= ~surrounded self.bad_sources = sources[bad].reset_index(drop=True) source_mask = np.asarray([(c.separation(d1).min().arcsec) < 12 for d1 in coords]) source_mask &= ~bad sources = sources[source_mask].reset_index(drop=True) self.dx, self.dy, self.gv = self.dx[source_mask], self.dy[ source_mask], self.gv[source_mask] self.GaiaData = self.GaiaData[source_mask] self.sources = sources self.nsources = len(self.sources) r = np.hypot(self.dx, self.dy) self.close_mask = (r < radius_limit) # self._find_PSF_edge(radius_limit=radius_limit, flux_limit=flux_limit) m = sparse.csr_matrix(self.mask.astype(float)) dx1, dy1 = self.dx[self.mask], self.dy[self.mask] self.xcents, self.ycents = np.ones(len(self.flux)), np.ones( len(self.flux)) for tdx in range(len(self.flux)): self.xcents[tdx] = np.average(dx1, weights=np.nan_to_num( m.multiply(self.flux[tdx]).data)) self.ycents[tdx] = np.average(dy1, weights=np.nan_to_num( m.multiply(self.flux[tdx]).data)) d = SkyCoord(self.sources.ra, self.sources.dec, unit='deg') close = match_coordinates_3d( d, d, nthneighbor=2)[1].to('arcsecond').value < 8 sources = SkyCoord(self.sources.ra, self.sources.dec, unit=('deg')) tpfloc = SkyCoord( [SkyCoord(tpf.ra, tpf.dec, unit='deg') for tpf in tpfs]) idx = np.asarray( [match_coordinates_3d(loc, sources)[0] for loc in tpfloc]) jdx = np.asarray( [match_coordinates_3d(source, tpfloc)[0] for source in sources]) self.fresh = ~np.in1d(np.arange(0, self.nsources), idx) self.mean_model = self._build_model() self._fit_model()