def read_guide_stars_catalog(self, filename, max_sep_arcsec=2.): log = get_logger() log.info("reading guide stars in {}".format(filename)) # here we could do some conversion of column names catalog = Table.read(filename) if (not "xcentroid" in catalog.dtype.names) or ( not "ra_gaia" in catalog.dtype.names): log.error( "I can only deal with Aaron's catalogs with columns xcentroid,ycentroid,ra_gaia,dec_gaia, sorry" ) raise RuntimeError( "I can only deal with Aaron's catalogs with columns xcentroid,ycentroid,ra_gaia,dec_gaia, sorry" ) max_sep_arcsec = 2. log.info( "selection stars for which we have a good match (< {} arcsec)". format(max_sep_arcsec)) dra = (catalog["ra"] - catalog["ra_gaia"]) * cosd( catalog["dec_gaia"]) * 3600. # arcsec ddec = (catalog["dec"] - catalog["dec_gaia"]) * 3600. # arcsec dr = np.hypot(dra, ddec) selection = (dr < 2) # arcsec if np.sum(selection) == 0: log.error("no star is matched with sufficient precision!") raise RuntimeError("no star is matched with sufficient precision!") return catalog[:][selection]
def test_tancorr_fit(self): tcorr = TanCorr() tel_ha1 = 12. tel_dec1 = 80. npts = 24 x1 = 0.1 * np.random.uniform(size=npts) y1 = 0.1 * np.random.uniform(size=npts) ha, dec = xy2hadec(x1, y1, tel_ha1, tel_dec1) true_dha_arcsec = 3. true_ddec_arcsec = 1. tel_ha2 = tel_ha1 + true_dha_arcsec / 3600. tel_dec2 = tel_dec1 + true_ddec_arcsec / 3600. x2, y2 = hadec2xy(ha, dec, tel_ha2, tel_dec2) tcorr.fit(x1, y1, x2, y2) meas_dha_arcsec = tcorr.dha / cosd(tel_dec1) * 3600. meas_ddec_arcsec = tcorr.ddec * 3600. assert (np.abs(true_dha_arcsec - meas_dha_arcsec) < 0.01) assert (np.abs(true_ddec_arcsec - meas_ddec_arcsec) < 0.01)
def test_it(self): print("Testing fiberassign_radec2xy_cs5") nrand = 500 square_side_in_degrees = 1.5 xtan = np.pi / 180. * square_side_in_degrees * ( np.random.uniform(size=nrand) - 0.5) ytan = np.pi / 180. * square_side_in_degrees * ( np.random.uniform(size=nrand) - 0.5) tile_ra = 12. tile_dec = 40. tile_ha = 1 tile_mjd = 59344.4 tile_fieldrot = -130 / 3600. # deg lst = tile_ra + tile_ha ha, dec = xy2hadec(xtan, ytan, tile_ha, tile_dec) ra = lst - ha x, y = fiberassign_radec2xy_cs5(ra, dec, tile_ra, tile_dec, tile_mjd, tile_ha, tile_fieldrot) print("Testing fiberassign_cs5_xy2radec") ra2, dec2 = fiberassign_cs5_xy2radec(x, y, tile_ra, tile_dec, tile_mjd, tile_ha, tile_fieldrot) drad = cosd(dec) * (ra2 - ra) ddec = (dec2 - dec) distdeg = np.sqrt(drad**2 + ddec**2) rms = (np.sqrt(np.mean(distdeg**2))) print("rms={} arcsec".format(rms * 3600.)) assert (rms * 3600. < 0.1) print("Testing fiberassign_radec2xy_flat") x, y = fiberassign_radec2xy_flat(ra, dec, tile_ra, tile_dec, tile_mjd, tile_ha, tile_fieldrot) print("Testing fiberassign_flat_xy2radec") ra2, dec2 = fiberassign_flat_xy2radec(x, y, tile_ra, tile_dec, tile_mjd, tile_ha, tile_fieldrot) drad = cosd(dec) * (ra2 - ra) ddec = (dec2 - dec) distdeg = np.sqrt(drad**2 + ddec**2) rms = (np.sqrt(np.mean(distdeg**2))) print("rms={} arcsec".format(rms * 3600.)) assert (rms * 3600. < 0.1)
def qs2xy(q, s): '''angular q,s on curved focal surface -> focal tangent plane x,y Args: q: angle in degrees s: focal surface radial distance in mm Returns (x, y) cartesian location on focal tangent plane in mm Notes: (x,y) are in the "CS5" DESI coordinate system tangent plane to the curved focal surface. q is the radial angle measured counter-clockwise from the x-axis; s is the radial distance along the curved focal surface; it is *not* sqrt(x**2 + y**2). (q,s) are the preferred coordinates for the DESI focal plane hardware engineering team. ''' r = s2r(s) x = r * cosd(q) y = r * sind(q) return x, y
def fit_tancorr(self, catalog, mjd=None, hexrot_deg=None, lst=None): log = get_logger() x_gfa = catalog["xcentroid"] y_gfa = catalog["ycentroid"] ra_gaia = catalog["ra_gaia"] dec_gaia = catalog["dec_gaia"] # mjd,hexprot_deg,lst could have been set before if mjd is not None: self.mjd = mjd log.info("Use argument MJD={}".format(self.mjd)) elif "mjd_obs" in catalog.keys(): self.mjd = np.mean(catalog["mjd_obs"]) log.info("Use 'mjd_obs' in catalog, MJD={}".format(self.mjd)) elif self.mjd is None: log.error("mjd is None") raise RuntimeError("mjd is None") else: log.info("Use MJD={}".format(self.mjd)) if hexrot_deg is not None: self.hexrot_deg = hexrot_deg elif self.hexrot_deg is None: log.error("hexrot_deg is None") raise RuntimeError("hexrot_deg is None") if lst is not None: self.lst = lst elif self.lst is None: from desimeter.time import mjd2lst log.warning("Compute LST from MJD={}".format(self.mjd)) self.lst = mjd2lst(self.mjd) log.info("Use LST={}".format(self.lst)) # first transfo: gfa2fp x_fp, y_fp = self.all_gfa2fp(x_gfa, y_gfa, petal_loc=catalog["petal_loc"]) # keep only petal data for which we have the metrology selection = (x_fp != 0) x_gfa = x_gfa[selection] y_gfa = y_gfa[selection] x_fp = x_fp[selection] y_fp = y_fp[selection] ra_gaia = ra_gaia[selection] dec_gaia = dec_gaia[selection] # transform focal plane to tangent plane x_tan_meas, y_tan_meas = fp2tan(x_fp, y_fp, self.adc1, self.adc2) correction = TanCorr() for _ in range( 3 ): # loop because change of pointing induces a rotation of the field # we transform GAIA coordinates to the tangent plane x_tan_gaia, y_tan_gaia = radec2tan( ra_gaia, dec_gaia, self.ra, self.dec, mjd=self.mjd, lst_deg=self.lst, hexrot_deg=self.hexrot_deg, precession=self.precession, aberration=self.aberration, polar_misalignment=self.polar_misalignment) # now that we have both sets of coordinates, we fit a transformation from one to the other correction.fit(x_tan_meas, y_tan_meas, x_tan_gaia, y_tan_gaia) # opposite sign for the telescope offset because I have converted GAIA RA Dec to tangent plane ... self.dec -= correction.ddec self.ra += correction.dha / cosd(self.dec) # HA = LST-RA # save params to this log.info("RMS coord. residual = {:3.2f} arcsec".format( correction.rms_arcsec)) log.info("Rotation angle (field rot ZP) ={:4.3f} deg".format( correction.rot_deg)) log.info("Pointing correction dHA={:3.2f} arcsec, dDec={:3.2f} arcsec". format(correction.dha * 3600., correction.ddec * 3600.)) log.info("Scales sxx={:5.4f} syy={:5.4f} sxy={:5.4f}".format( correction.sxx, correction.syy, correction.sxy)) # now I just copy the correction parameters in this class self.sxx = correction.sxx self.syy = correction.syy self.sxy = correction.sxy self.fieldrot_zp_deg = correction.rot_deg self.nstars = correction.nstars self.rms_arcsec = correction.rms_arcsec # I now derive the field rotation self.fieldrot_deg = self.compute_fieldrot()
def read_guide_stars_catalog(self, filename, max_sep_arcsec=2.): log = get_logger() log.info("reading guide stars in {}".format(filename)) # here we could do some conversion of column names catalog = Table.read(filename) if "mjd_obs" in catalog.dtype.names: self.mjd = np.mean(catalog["mjd_obs"]) log.info("use mjd={} from catalog['mjd_obs']".format(self.mjd)) if (not "xcentroid" in catalog.dtype.names) or ( not "ra_gaia" in catalog.dtype.names): log.error( "I can only deal with Aaron's catalogs with columns xcentroid,ycentroid,ra_gaia,dec_gaia, sorry" ) raise RuntimeError( "I can only deal with Aaron's catalogs with columns xcentroid,ycentroid,ra_gaia,dec_gaia, sorry" ) log.info( "selection stars for which we have a good match (< {} arcsec)". format(max_sep_arcsec)) if all([_ in catalog.columns for _ in ['pmra', 'pmdec', 'ref_epoch']]): if self.mjd is None: log.error( "Cannot compute proper motion correction because mjd=None") raise RuntimeError( "Cannot compute proper motion correction because mjd=None") # if proper motions and reference epochs are there pmra = catalog['pmra'] pmdec = catalog['pmdec'] # if unknown, zero out the pms pmra[~np.isfinite(pmra)] = 0 pmdec[~np.isfinite(pmdec)] = 0 cur_year = Time(self.mjd, format='mjd').to_value(format='jyear') # observation time in decimal years (like 2020.3) ref_epoch = catalog['ref_epoch'] dra = (cur_year - ref_epoch) * pmra / 3600e3 / cosd( catalog['dec_gaia']) ddec = (cur_year - ref_epoch) * pmdec / 3600e3 # add pm and rename columns catalog['ra_gaia'] += dra catalog['dec_gaia'] += ddec catalog.rename_column('ra_gaia', 'ra_gaia_with_pm') catalog.rename_column('dec_gaia', 'dec_gaia_with_pm') ra_column = 'ra_gaia_with_pm' dec_column = 'dec_gaia_with_pm' else: ra_column = 'ra_gaia' dec_column = 'dec_gaia' log.warning("No proper motion info in catalog") match_dra = (catalog["ra"] - catalog[ra_column]) * cosd( catalog[dec_column]) * 3600. # arcsec match_ddec = (catalog["dec"] - catalog[dec_column]) * 3600. # arcsec dr = np.hypot(match_dra, match_ddec) selection = (dr < max_sep_arcsec) # arcsec if np.sum(selection) == 0: log.error("no star is matched with sufficient precision!") raise RuntimeError("no star is matched with sufficient precision!") return catalog[:][selection]