def fit_rotoff(self, x1, y1, x2, y2): """ Fit rotation + offset (but not scale) for (x1,y1) -> (x2,y2) Args: x1,y1,x2,y2 : 1D np.arrays of coordinates in tangent plane """ assert((x1.shape == y1.shape)&(x2.shape == y2.shape)&(x1.shape == x2.shape)) n = len(x1) v = np.concatenate([x2, y2]) A = np.zeros((2*n, 4)) A[0:n, 0] = x1 A[n:, 0] = y1 A[0:n, 1] = -y1 A[n:, 1] = x1 A[0:n, 2] = 1 A[n:, 2] = 0 A[0:n, 3] = 0 A[n:, 3] = 1 ATv = A.T.dot(v) ATA = A.T.dot(A) p = np.linalg.solve(ATA, ATv) self.rot_deg = arctan2d(p[1], p[0]) self.dx = p[2] self.dy = p[3] self.sxx = 1. self.syy = 1. self.sxy = 0.
def hadec2altaz(ha,dec) : """ Convert HA,Dec to Altitude , Azimuth at Kitt Peak elevation Args: ha: float or 1D np.array hour angle in degrees dec: float or 1D np.array declination in degrees Returns: alt, az alt: float or 1D np.array altitude in degrees az: float or 1D np.array azimuth in degrees """ sha,cha = sincosd(ha) sdec,cdec = sincosd(dec) slat,clat = sincosd(LATITUDE) x = - cha * cdec * slat + sdec * clat y = - sha * cdec z = cha * cdec * clat + sdec * slat r = np.hypot(x, y) az = arctan2d(y,x) alt = arctan2d(z,r) return alt,az
def altaz2hadec(alt,az) : """ Convert Altitude, Azimuth to HA, Dec at Kitt Peak elevation Args: alt: float or 1D np.array altitude in degrees az: float or 1D np.array azimuth in degrees Returns: ha, dec ha: float or 1D np.array Hour Angle in degrees dec: float or 1D np.array Hour Angle in degrees """ salt,calt = sincosd(alt) saz,caz = sincosd(az) slat,clat = sincosd(LATITUDE) ha = arctan2d( -saz*calt, -caz*slat*calt+salt*clat) dec = arcsind(slat*salt+clat*calt*caz) return ha,dec
def xy2qs(x, y): '''Focal tangent plane x,y -> angular q,s on curved focal surface Args: x, y: cartesian location on focal tangent plane in mm Returns (q, s) where q=angle in degrees; s=focal surface radial dist [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 = np.hypot(x, y) s = r2s(r) q = arctan2d(y, x) return q, s
def fit(self, x1, y1, x2, y2, solid=False) : """ Adjust tranformation from x1,y1 to x2,y2 Args: x1,y1,x2,y2 : 1D np.arrays of coordinates in tangent plane Optional: if solid, scales are forced = 1 Returns: None """ if solid : return self.fit_rotoff(x1, y1, x2, y2) assert((x1.shape == y1.shape)&(x2.shape == y2.shape)&(x1.shape == x2.shape)) # now fit simultaneously extra offset, rotation, scale self.nmatch=x1.size H=np.zeros((3,self.nmatch)) H[0] = 1. H[1] = x1 H[2] = y1 A = H.dot(H.T) Ai = np.linalg.inv(A) ax = Ai.dot(np.sum(x2*H,axis=1)) x2p = ax[0] + ax[1]*x1 + ax[2]*y1 # x2p = predicted x2 from x1 (=x1 after telescope pointing offset) ay = Ai.dot(np.sum(y2*H,axis=1)) y2p = ay[0] + ay[1]*x1 + ay[2]*y1 # y2p = predicted y2 from y1 # tangent plane coordinates are in radians self.rms = np.sqrt( np.mean( (x2-x2p)**2 + (y2-y2p)**2 ) ) # pointing offset self.dx = ax[0] self.dy = ay[0] # dilatation and rotation # |ax1 ax2| |sxx sxy| |ca -sa| # |ay1 ay2|=|syx syy|*|sa ca| # ax1=sxx*ca+sxy*sa ; ax2=-sxx*sa+sxy*ca # ay1=syx*ca+syy*sa ; ay2=-syx*sa+syy*ca # ax1+ay2 = (sxx+syy)*ca # ay1-ax2 = (sxx+syy)*sa sxx_p_syy = np.hypot(ax[1]+ay[2], ay[1]-ax[2]) sa=(ay[1]-ax[2])/sxx_p_syy ca=(ax[1]+ay[2])/sxx_p_syy if sa != 0 : self.rot_deg = arctan2d(sa,ca) sxy = sa*ax[1]+ca*ay[1] - sxx_p_syy*ca*sa sxx =(ax[1]-sxy*sa)/ca syy = (ay[1]-sxy*ca)/sa else : sxx = ax[1] syy = ay[2] sxy = ax[2] self.sxx = sxx self.syy = syy self.sxy = sxy
def fit(self, x1, y1, x2, y2): """ Adjust tranformation from focal plane x1,y1 to x2,y2 Args: x1,y1,x2,y2 : 1D np.arrays of coordinates in tangent plane Returns: None """ assert ((x1.shape == y1.shape) & (x2.shape == y2.shape) & (x1.shape == x2.shape)) # first ajust an offset using spherical coordinates # assume fiducial pointing of telescope to convert # tangent plane coords to angles self.dha = 0. self.ddec = 0. x1t = x1 + 0. y1t = y1 + 0. ha, dec = xy2hadec(x1, y1, 0, 0) for _ in range(4): x1t, y1t = hadec2xy(ha, dec, self.dha, self.ddec) dx = np.mean(x2 - x1t) dy = np.mean(y2 - y1t) self.dha -= np.rad2deg(dx) self.ddec -= np.rad2deg(dy) x1t, y1t = hadec2xy(ha, dec, self.dha, self.ddec) # now fit simultaneously extra offset, rotation, scale self.nstars = x1t.size H = np.zeros((3, self.nstars)) H[0] = 1. H[1] = x1t H[2] = y1t A = H.dot(H.T) Ai = np.linalg.inv(A) ax = Ai.dot(np.sum(x2 * H, axis=1)) x2p = ax[0] + ax[1] * x1t + ax[ 2] * y1t # x2p = predicted x2 from x1t (=x1 after telescope pointing offset) ay = Ai.dot(np.sum(y2 * H, axis=1)) y2p = ay[0] + ay[1] * x1t + ay[2] * y1t # y2p = predicted y2 from y1t # tangent plane coordinates are in radians rms = np.sqrt(np.mean((x2 - x2p)**2 + (y2 - y2p)**2)) self.rms_arcsec = np.rad2deg(rms) * 3600. # interpret this back into telescope pointing offset, field rotation, dilatation # pointing offset # increasing gaia stars x means telescope is more to the left so tel_ha should be decreased # increasing gaia stars y means telescope is more to the bottom so tel_dec should be decreased # tangent plane coordinates are in rad ddha = -np.rad2deg(ax[0]) dddec = -np.rad2deg(ay[0]) self.dha += ddha self.ddec += dddec # dilatation and rotation # |ax1 ax2| |sxx sxy| |ca -sa| # |ay1 ay2|=|syx syy|*|sa ca| # ax1=sxx*ca+sxy*sa ; ax2=-sxx*sa+sxy*ca # ay1=syx*ca+syy*sa ; ay2=-syx*sa+syy*ca # ax1+ay2 = (sxx+syy)*ca # ay1-ax2 = (sxx+syy)*sa sxx_p_syy = np.hypot(ax[1] + ay[2], ay[1] - ax[2]) sa = (ay[1] - ax[2]) / sxx_p_syy ca = (ax[1] + ay[2]) / sxx_p_syy self.rot_deg = arctan2d(sa, ca) sxy = sa * ax[1] + ca * ay[1] - sxx_p_syy * ca * sa sxx = (ax[1] - sxy * sa) / ca syy = (ay[1] - sxy * ca) / sa self.sxx = sxx self.syy = syy self.sxy = sxy
def getLONLAT(xyz): """Convert xyz into its spherical angles""" xyz = getNormalized(xyz) # usually unnecessary return arctan2d(xyz[1],xyz[0]) , arcsind(xyz[2]) # degrees
def uv2xy(u, v): s = np.hypot(u, v) q = arctan2d(v, u) x, y = qs2xy(q, s) return x, y