def calc_offset_upos(pattern, offset_array, offset_det, site, area, U): """Compute how the detectors are offset from the array midpoint in unskewd pixel units. All angles are in radians.""" ref_hor = np.array([pattern[0],np.mean(pattern[1:3])]) + offset_array det_hor = ref_hor[:,None] + offset_det.T # Transform to celestial coordinates ref_cel = coordinates.transform("hor","cel", ref_hor[::-1], time=ref_time, site=site)[::-1] det_cel = coordinates.transform("hor","cel", det_hor[::-1], time=ref_time, site=site)[::-1] # RA is ambiguous because we don't know the time properly ra0 = np.mean(area.box(),0)[1] det_cel[1] += ra0 - ref_cel[1] ref_cel[1] = ra0 # And convert into pixels ref_pix = area.sky2pix(ref_cel) det_pix = area.sky2pix(det_cel) # Unskew these to get pixels in the coordinate system # the noise model lives in ref_upix = U.apply_pix(ref_pix) det_upix = U.apply_pix(det_pix) # And express that as positions in this new coordinate system ref_upos = enmap.pix2sky(U.ushape, U.uwcs, ref_upix) det_upos = enmap.pix2sky(U.ushape, U.uwcs, det_upix) # And finally record the offset of each detector from the reference point offset_upos = det_upos - ref_upos[:,None] return offset_upos
def get_pix_ranges(shape, wcs, horbox, daz, nt=4, azdown=1, ndet=1.0): (t1,t2),(az1,az2),el = horbox[:,0], horbox[:,1], np.mean(horbox[:,2]) nphi = np.abs(utils.nint(360/wcs.wcs.cdelt[0])) # Find the pixel coordinates of first az sweep naz = utils.nint(np.abs(az2-az1)/daz)/azdown ahor = np.zeros([3,naz]) ahor[0] = utils.ctime2mjd(t1) ahor[1] = np.linspace(az1,az2,naz) ahor[2] = el acel = coordinates.transform("hor","cel",ahor[1:],time=ahor[0],site=site) y, x1 = upscale(fixx(utils.nint(enmap.sky2pix(shape, wcs, acel[::-1])),nphi),azdown) # Reduce to unique y values _, uinds, hits = np.unique(y, return_index=True, return_counts=True) y, x1 = y[uinds], x1[uinds] # Find the pixel coordinates of time drift thor = np.zeros([3,nt]) thor[0] = utils.ctime2mjd(np.linspace(t1,t2,nt)) thor[1] = az1 thor[2] = el tcel = coordinates.transform("hor","cel",thor[1:],time=thor[0],site=site) _, tx = utils.nint(fixx(enmap.sky2pix(shape, wcs, tcel[::-1]),nphi)) x2 = x1 + tx[-1]-tx[0] x1, x2 = np.minimum(x1,x2), np.maximum(x1,x2) pix_ranges = np.concatenate([y[:,None],x1[:,None],x2[:,None]],1) # Weight per pixel in pix ranges. If ndet=1 this corresponds to # telescope time per output pixel weights = (t2-t1)/(naz*azdown)/(x2-x1)*ndet * hits return pix_ranges, weights
def calc_offset_upos(pattern, offset_array, offset_det, site, area, U): """Compute how the detectors are offset from the array midpoint in unskewd pixel units. All angles are in radians.""" ref_hor = np.array([pattern[0], np.mean(pattern[1:3])]) + offset_array det_hor = ref_hor[:, None] + offset_det.T # Transform to celestial coordinates ref_cel = coordinates.transform("hor", "cel", ref_hor[::-1], time=ref_time, site=site)[::-1] det_cel = coordinates.transform("hor", "cel", det_hor[::-1], time=ref_time, site=site)[::-1] # RA is ambiguous because we don't know the time properly ra0 = np.mean(area.box(), 0)[1] det_cel[1] += ra0 - ref_cel[1] ref_cel[1] = ra0 # And convert into pixels ref_pix = area.sky2pix(ref_cel) det_pix = area.sky2pix(det_cel) # Unskew these to get pixels in the coordinate system # the noise model lives in ref_upix = U.apply_pix(ref_pix) det_upix = U.apply_pix(det_pix) # And express that as positions in this new coordinate system ref_upos = enmap.pix2sky(U.ushape, U.uwcs, ref_upix) det_upos = enmap.pix2sky(U.ushape, U.uwcs, det_upix) # And finally record the offset of each detector from the reference point offset_upos = det_upos - ref_upos[:, None] return offset_upos
def calc_driftangle(hor, t, site): hor = np.atleast_2d(hor).T t = np.atleast_1d(t) equ = coordinates.transform("hor", "equ", hor, time=utils.ctime2mjd(t), site=site) hor_drift = utils.rewind(coordinates.transform("equ","hor", equ, time=utils.ctime2mjd(t+1), site=site),hor,2*np.pi) vec_drift = hor_drift-hor # Compute angle between this vector and the az axis angle = np.arctan2(vec_drift[1],vec_drift[0]*np.cos(hor[1]))%np.pi return angle
def find_scan_vel(scan, ipos, aspeed, dt=0.1): hpos = coordinates.transform("equ", "hor", ipos, time=scan.mjd0, site=scan.site) hpos[0] += aspeed * dt opos = coordinates.transform("hor", "equ", hpos, time=scan.mjd0, site=scan.site) opos[0] = utils.rewind(opos[0], ipos[0]) return (opos - ipos) / dt
def __call__(self, sys1, sys2, pos): mjd = utils.ctime2mjd(self.data["t"]) bore = np.array([self.data["az"], self.data["el"]]) * utils.degree hor = coordinates.transform( sys1, sys2, np.asarray(pos) * utils.degree, mjd, bore=bore) / utils.degree return hor
def calc_az_sweep(pattern, offset, site, pad=2.0, subsample=1.0): """Helper function. Given a pattern and mean focalplane offset, computes the shape of an azimuth sweep on the sky.""" el1 = pattern[0] + offset[0] az1 = pattern[1] + offset[1] - pad az2 = pattern[2] + offset[1] + pad daz = rhs.pixshape()[0] / np.cos(el1) / subsample naz = int(np.ceil((az2 - az1) / daz)) naz = fft.fft_len(naz, "above", [2, 3, 5, 7]) # Simulate a single sweep at arbitrary time sweep_az = np.arange(naz) * daz + az1 sweep_el = np.full(naz, el1) sweep_cel = coordinates.transform("hor", "cel", np.array([sweep_az, sweep_el]), time=ref_time, site=site) # Make ra safe sweep_cel = utils.unwind(sweep_cel) return bunch.Bunch(sweep_cel=sweep_cel, sweep_hor=np.array([sweep_az, sweep_el]), el=el1, az1=az1, az2=az2, naz=naz, daz=daz)
def __call__(self, ipos, time): opos = coordinates.transform(self.isys, self.osys, ipos, time=time, site=self.site) x, y = self.wcs.wcs_world2pix(opos[0]/utils.degree,opos[1]/utils.degree,0) nx = int(np.abs(360.0/self.wcs.wcs.cdelt[0])) x = utils.unwind(x, nx, ref=nx/2) opos[0] = utils.unwind(opos[0]) return np.array([opos[0],opos[1],x,y])
def obj_pos(scan, obj): ra, dec = coordinates.ephem_pos(obj, scan.mjd0) az, el = coordinates.transform("cel", "hor", [[ra], [dec]], time=[scan.mjd0], site=scan.site)[:, 0] return az, el, ra, dec
def get_samples(self): # Start with the noise if hasattr(self, "_tod") and self._tod is not None: return self._tod.copy() np.random.seed(self.seed) tod = np.zeros([self.ndet,self.nsamp]).astype(np.float32) if self.noise_scale != 0: tod = np.random.standard_normal([self.ndet,self.nsamp]).astype(np.float32)*self.noise_scale covs = array_ops.eigpow(self.noise.icovs, -0.5, axes=[-2,-1]) N12 = nmat.NmatBinned(covs, self.noise.bins, self.noise.dets) N12.apply(tod) else: tod[...] = 0 tod = tod.astype(np.float64) # And add the point sources for di in range(self.ndet): for i, (pos,amp,beam) in enumerate(zip(self.srcs.pos,self.srcs.amps,self.srcs.beam)): point = (self.boresight+self.offsets[di,None,:])[:,1:] point = coordinates.transform(self.sys, self.simsys, point.T, time=self.boresight[:,0]+self.mjd0, site=self.site).T r2 = np.sum((point-pos[None,:])**2,1)/beam**2 I = np.where(r2 < self.nsigma**2)[0] tod[di,I] += np.exp(-0.5*r2[I])*np.sum(amp*self.comps[di]) if hasattr(self, "_tod"): self._tod = tod.copy() return tod
def tele_pos(scan): t, az, el = np.mean(scan.boresight[::10], 0) t = uscan.mjd0 + t / (24. * 60 * 60) ra, dec = coordinates.transform("hor", "cel", [[az], [el]], time=[t], site=scan.site)[:, 0] return az, el, ra, dec
def calc_driftangle(hor, t, site): hor = np.atleast_2d(hor).T t = np.atleast_1d(t) equ = coordinates.transform("hor", "equ", hor, time=utils.ctime2mjd(t), site=site) hor_drift = utils.rewind( coordinates.transform("equ", "hor", equ, time=utils.ctime2mjd(t + 1), site=site), hor, 2 * np.pi) vec_drift = hor_drift - hor # Compute angle between this vector and the az axis angle = np.arctan2(vec_drift[1], vec_drift[0] * np.cos(hor[1])) % np.pi return angle
def calc_sky_bbox_scan(scan, osys, nsamp=100): """Compute the bounding box of the scan in the osys coordinate system. Returns [{from,to},{dec,ra}].""" ipoints = utils.box2contour(scan.box, nsamp) opoints = np.array([coordinates.transform(scan.sys,osys,b[1:,None],time=scan.mjd0+b[0,None]/3600/24,site=scan.site)[::-1,0] for b in ipoints]) # Take care of angle wrapping along the ra direction opoints[...,1] = utils.rewind(opoints[...,1], ref="auto") obox = utils.bounding_box(opoints) # Grow slighly to account for non-infinite nsamp obox = utils.widen_box(obox, 5*utils.arcmin, relative=False) return obox
def remap(pos, dir, beta, pol=True, modulation=True, recenter=False): """Given a set of coordinates "pos[{ra,dec]}", computes the aberration deflected positions for a speed beta in units of c in the direction dir. If pol=True (the default), then the output will have three columns, with the third column being the aberration-induced rotation of the polarization angle.""" pos = coordinates.transform("equ", ["equ", dir], pos, pol=pol) if recenter: before = np.mean(pos[1, ::10]) # Use -beta here to get the original position from the deflection, # instead of getting the deflection from the original one as # aber_angle normally computes. pos[1] = np.pi / 2 - aber_angle(np.pi / 2 - pos[1], -beta) if recenter: after = np.mean(pos[1, ::10]) pos[1] -= after - before res = coordinates.transform(["equ", dir], "equ", pos, pol=pol) if modulation: amp = mod_amplitude(np.pi / 2 - pos[1], beta) res = np.concatenate([res, [amp]]) return res
def hor2cel(hor): shape = hor.shape[1:] hor = hor.reshape(hor.shape[0],-1) tmp = coordinates.transform("hor", "cel", hor[1:], time=hor[0]+t_mid, site=site, pol=True) res = np.zeros((4,)+tmp.shape[1:]) res[0] = utils.rewind(tmp[0], tmp[0,0]) res[1] = tmp[1] res[2] = np.cos(2*tmp[2]) res[3] = np.sin(2*tmp[2]) res = res.reshape(res.shape[:1]+shape) return res
def __call__(self, ipos, time): opos = coordinates.transform(self.isys, self.osys, ipos, time=time, site=self.site) x, y = self.wcs.wcs_world2pix(opos[0] / utils.degree, opos[1] / utils.degree, 0) nx = int(np.abs(360.0 / self.wcs.wcs.cdelt[0])) x = utils.unwind(x, nx, ref=nx / 2) opos[0] = utils.unwind(opos[0]) return np.array([opos[0], opos[1], x, y])
def hor2cel(hor, toff): """Transform from [{tsec,az,el},nsamp] to [{ra,dec,c,s},nsamp], where tsec is the offset from toff. Toff is given in mjd.""" shape = hor.shape[1:] hor = hor.reshape(hor.shape[0],-1).astype(float) tmp = coordinates.transform("hor", "cel", hor[1:], time=hor[0]/24/60/60+toff, site=site, pol=True) res = np.zeros((4,)+tmp.shape[1:]) res[0] = utils.rewind(tmp[0], tmp[0,0]) res[1] = tmp[1] res[2] = np.cos(2*tmp[2]) res[3] = np.sin(2*tmp[2]) res = res.reshape(res.shape[:1]+shape) return res
def __call__(self, ipos): """Transform ipos[{t,az,el},nsamp] into opix[{y,x,c,s},nsamp].""" shape = ipos.shape[1:] ipos = ipos.reshape(ipos.shape[0],-1) time = self.scan.mjd0 + ipos[0]/utils.day2sec opos = coordinates.transform("hor", "cel", ipos[1:], time=time, site=self.scan.site, pol=True) opix = np.zeros((4,)+ipos.shape[1:]) opix[:2] = self.wcs.wcs_world2pix(*tuple(opos[:2]/utils.degree)+(0,))[::-1] nx = int(np.abs(360/self.wcs.wcs.cdelt[0])) opix[1] = utils.unwind(opix[1], period=nx, ref=nx/2) opix[2] = np.cos(2*opos[2]) opix[3] = np.sin(2*opos[2]) return opix.reshape((opix.shape[0],)+shape)
def build_pos_transform(scan, sys): # Set up pointing interpolation box = np.array(scan.box) #margin = (box[1]-box[0])*1e-3 # margin to avoid rounding erros #margin[1:] += 10*utils.arcmin #box[0] -= margin/2; box[1] += margin/2 # Find the rough position of our scan ref_phi = coordinates.transform(scan.sys, sys, scan.box[:1, 1:].T, time=scan.mjd0 + scan.box[:1, 0], site=scan.site)[0, 0] return pos2pix(scan, None, sys, ref_phi=ref_phi)
def summarize(self, chain): """Given a chain of lpars, compute a summary in the same format as returned by SrcFitterML.""" dposs = np.array([c.dpos for c in chain]) poss_cel = np.array([c.poss for c in chain]) poss_hor = poss_cel * 0 for i in range(len(self.sdata)): poss_hor[:, i] = coordinates.transform("cel", "hor", poss_cel[:, i].T, time=utils.ctime2mjd( self.sdata[i].ctime), site=self.sdata[i].site).T ampss = np.array([c.amps for c in chain]) vampss = np.array([c.vamps for c in chain]) chisq0 = chain[0].chisq0 chisq = np.mean([c.chisq for c in chain]) # Compute means dpos = np.mean(dposs, 0) pos_cel = np.mean(poss_cel, 0) pos_hor = np.mean(poss_hor, 0) amps = np.mean(ampss, 0) # And uncertainties dpos_cov = np.cov(dposs.T) ddpos = np.diag(dpos_cov)**0.5 pcorr = dpos_cov[0, 1] / ddpos[0] / ddpos[1] # mean([da0_i + da_ij]**2). Uncorrelated, so this is # mean(da0**2) + mean(da**2) arel = ampss + np.random.standard_normal(ampss.shape) * vampss**0.5 amps = np.mean(arel, 0) vamps = np.var(arel, 0) #vamps = np.var(ampss,0)+np.mean(vampss,0) damps = vamps**0.5 models = self.lik.eval(dpos).models nsigma = (chisq0 - chisq)**0.5 # We want how much to offset detector by, not how much to offset # source by res = bunch.Bunch(dpos=dpos, poss_cel=pos_cel, poss_hor=pos_hor, ddpos=ddpos, amps=amps, damps=damps, pcorr=pcorr, nsrc=len(self.sdata), models=models, nsigma=nsigma, chisq0=chisq0, chisq=chisq, npix=self.lik.npix) return res
def get_sids_in_tod(id, src_pos, bounds, ind, isids=None, src_sys="cel"): if isids is None: isids = list(range(src_pos.shape[-1])) if bounds is not None: poly = bounds[:,:,ind]*utils.degree poly[0] = utils.rewind(poly[0],poly[0,0]) # bounds are defined in celestial coordinates. Must convert srcpos for comparison mjd = utils.ctime2mjd(float(id.split(".")[0])) srccel = coordinates.transform(src_sys, "cel", src_pos, time=mjd) srccel[0] = utils.rewind(srccel[0], poly[0,0]) poly = pad_polygon(poly.T, poly_pad).T accepted = np.where(utils.point_in_polygon(srccel.T, poly.T))[0] sids = [isids[i] for i in accepted] else: sids = isids return sids
def __call__(self, ipos): """Transform ipos[{t,az,el},nsamp] into opix[{y,x,c,s},nsamp].""" shape = ipos.shape[1:] ipos = ipos.reshape(ipos.shape[0], -1) time = self.scan.mjd0 + ipos[0] / utils.day2sec # We support sidelobe mapping by passing the detector pointing "ipos" as the "boresight" # pointing, which is only used in boresight-relative coordinates or sidelobe mapping. # This actually results in a better coordinate system than if we had passed in the actual # boresight, since we don't really want boresight-centered coordinates, we want detector # centered coordinates. opos = coordinates.transform(self.scan.sys, self.sys, ipos[1:], time=time, site=self.scan.site, pol=True, bore=ipos[1:]) # Parallax correction sundist = config.get("pmat_parallax_au") if sundist: # Transform to a sun-centered coordinate system, assuming all objects # are at a distance of sundist from the sun opos[1::-1] = parallax.earth2sun(opos[1::-1], self.scan.mjd0, sundist) opix = np.zeros((4, ) + ipos.shape[1:]) if self.template is not None: opix[:2] = self.template.sky2pix(opos[1::-1], safe=2) # When mapping the full sky, angle wraps can't be hidden # ouside the image. We must therefore unwind along each # interpolation axis to avoid discontinuous interpolation nx = int(np.round(np.abs(360 / self.template.wcs.wcs.cdelt[0]))) opix[1] = utils.unwind(opix[1].reshape(shape), period=nx, axes=range(len(shape))).reshape(-1) # Prefer positive numbers opix[1] -= np.floor(opix[1].reshape(-1)[0] / nx) * nx # but not if they put everything outside our patch if np.min(opix[1]) > self.template.shape[-1]: opix[1] -= nx else: # If we have no template, output angles instead of pixels. # Make sure the angles don't have any jumps in them opix[:2] = opos[1::-1] # output order is dec,ra opix[1] = utils.rewind(opix[1], self.ref_phi) opix[2] = np.cos(2 * opos[2]) opix[3] = np.sin(2 * opos[2]) return opix.reshape((opix.shape[0], ) + shape)
def hor2cel(hor): shape = hor.shape[1:] hor = hor.reshape(hor.shape[0], -1) tmp = coordinates.transform("hor", "cel", hor[1:], time=hor[0] + t_mid, site=site, pol=True) res = np.zeros((4, ) + tmp.shape[1:]) res[0] = utils.rewind(tmp[0], tmp[0, 0]) res[1] = tmp[1] res[2] = np.cos(2 * tmp[2]) res[3] = np.sin(2 * tmp[2]) res = res.reshape(res.shape[:1] + shape) return res
def calc_az_sweep(pattern, offset, site, pad=2.0, subsample=1.0): """Helper function. Given a pattern and mean focalplane offset, computes the shape of an azimuth sweep on the sky.""" el1 = pattern[0] + offset[0] az1 = pattern[1] + offset[1] - pad az2 = pattern[2] + offset[1] + pad daz = rhs.pixshape()[0]/np.cos(el1)/subsample naz = int(np.ceil((az2-az1)/daz)) naz = fft.fft_len(naz, "above", [2,3,5,7]) # Simulate a single sweep at arbitrary time sweep_az = np.arange(naz)*daz + az1 sweep_el = np.full(naz,el1) sweep_cel = coordinates.transform("hor","cel", np.array([sweep_az,sweep_el]),time=ref_time,site=site) # Make ra safe sweep_cel = utils.unwind(sweep_cel) return bunch.Bunch(sweep_cel=sweep_cel, sweep_hor=np.array([sweep_az,sweep_el]), el=el1, az1=az1, az2=az2, naz=naz, daz=daz)
def build_rangedata(tod, rcut, d, ivar): nmax = np.max(rcut.ranges[:, 1] - rcut.ranges[:, 0]) nrange = rcut.nrange rdata = bunch.Bunch() rdata.detmap = np.zeros(nrange, int) rdata.tod = np.zeros([nrange, nmax], dtype) rdata.pos = np.zeros([nrange, nmax, 2]) rdata.ivar = np.zeros([nrange, nmax], dtype) # Build our detector mapping for di in range(rcut.ndet): rdata.detmap[rcut.detmap[di]:rcut.detmap[di + 1]] = di rdata.n = rcut.ranges[:, 1] - rcut.ranges[:, 0] # Extract our tod samples and coordinates for i, r in enumerate(rcut.ranges): di = rdata.detmap[i] rn = r[1] - r[0] rdata.tod[i, :rn] = tod[di, r[0]:r[1]] bore = d.boresight[:, r[0]:r[1]] mjd = utils.ctime2mjd(bore[0]) pos_hor = bore[1:] + d.point_offset[di, :, None] pos_rel = coordinates.transform(tod_sys, sys, pos_hor, time=mjd, site=d.site) rdata.pos[i, :rn] = pos_rel.T # Expand noise ivar too, including the effect of our normal data cut rdata.ivar[ i, :rn] = ivar[di] * (1 - d.cut[di:di + 1, r[0]:r[1]].to_mask()[0]) # Precompute our fourier space units rdata.freqs = fft.rfftfreq(nmax, 1 / d.srate) # And precompute out butterworth filter rdata.butter = filters.mce_filter(rdata.freqs, d.mce_fsamp, d.mce_params) # Store the fiducial time constants for reference rdata.tau = d.tau # These are also nice to have rdata.dsens = ivar**-0.5 / d.srate**0.5 rdata.asens = np.sum(ivar)**-0.5 / d.srate**0.5 rdata.srate = d.srate rdata.dets = d.dets rdata.beam = d.beam rdata.id = d.entry.id return rdata
def hor2cel(hor, toff): """Transform from [{tsec,az,el},nsamp] to [{ra,dec,c,s},nsamp], where tsec is the offset from toff. Toff is given in mjd.""" shape = hor.shape[1:] hor = hor.reshape(hor.shape[0], -1).astype(float) tmp = coordinates.transform("hor", "cel", hor[1:], time=hor[0] / 24 / 60 / 60 + toff, site=site, pol=True) res = np.zeros((4, ) + tmp.shape[1:]) res[0] = utils.rewind(tmp[0], tmp[0, 0]) res[1] = tmp[1] res[2] = np.cos(2 * tmp[2]) res[3] = np.sin(2 * tmp[2]) res = res.reshape(res.shape[:1] + shape) return res
def calc_full_result(self, dpos, marginalize=True): res = self.lik.eval(dpos) res.poss_cel = res.poss res.poss_hor = np.array([ coordinates.transform("cel", "hor", res.poss_cel[i], utils.ctime2mjd(self.sdata[i].ctime), site=self.sdata[i].site) for i in range(self.nsrc) ]) # Get the position uncertainty hess = self.calc_hessian(dpos, step=0.1 * utils.arcmin) hess = 0.5 * (hess + hess.T) try: pcov = np.linalg.inv(0.5 * hess) except np.linalg.LinAlgError: pcov = np.diag([np.inf, np.inf]) dchisq = res.chisq0 - res.chisq # Apply marginalization correction if marginalize: R = self.lik.rmax A = np.pi * R**2 Abeam = 2 * np.pi * self.fwhm**2 / (8 * np.log(2)) npoint = int(np.round(A / Abeam * self.nsrc)) # Correct position and uncertainty dpos, pcov = marg_pos(dpos, pcov, res.chisq0 - res.chisq, npoint, R) # Correct total chisquare prob = stats.norm.cdf(-dchisq**0.5) if prob > 1e-10: prob = 1 - (1 - prob)**npoint dchisq = stats.norm.ppf(prob)**2 ddpos = np.diag(pcov)**0.5 pcorr = pcov[0, 1] / ddpos[0] / ddpos[1] res.dpos = dpos res.ddpos = ddpos res.damps = res.vamps**0.5 res.pcorr = pcorr res.pcov = pcov res.nsrc = self.nsrc res.dchisq = dchisq res.nsigma = dchisq**0.5 return res
def __call__(self, ipos): """Transform ipos[{t,az,el},nsamp] into opix[{y,x,c,s},nsamp].""" shape = ipos.shape[1:] ipos = ipos.reshape(ipos.shape[0], -1) time = self.scan.mjd0 + ipos[0] / utils.day2sec opos = coordinates.transform("hor", "cel", ipos[1:], time=time, site=self.scan.site, pol=True) opix = np.zeros((4, ) + ipos.shape[1:]) opix[:2] = self.wcs.wcs_world2pix(*tuple(opos[:2] / utils.degree) + (0, ))[::-1] nx = int(np.abs(360 / self.wcs.wcs.cdelt[0])) opix[1] = utils.unwind(opix[1], period=nx, ref=nx / 2) opix[2] = np.cos(2 * opos[2]) opix[3] = np.sin(2 * opos[2]) return opix.reshape((opix.shape[0], ) + shape)
def __init__(self, sdat): self.mjd = utils.ctime2mjd(sdat.ctime) self.site = sdat.site self.ref_cel = sdat.srcpos # Find the boresight pointing that defines our focalplane # coordinates. This is not exact for two reasons: # 1. The array center is offset from the boresight, by about 1 degree. # 2. The detectors are offset from the array center by half that. # We could store the former to improve our accuracy a bit, but # to get #2 we would need a time-domain fit, which is what we're # trying to avoid here. The typical error from using the array center # instead would be about 1' offset * 1 degree error = 1.05 arcsec error. # We *can* easily get the boresight elevation since we have constant # elevation scans, so that removes half the error. That should be # good enough. self.bore= [ coordinates.transform("cel","hor",sdat.srcpos,time=self.mjd,site=self.site)[0], sdat.el ] self.ref_foc = cel2foc(self.ref_cel, self.site, self.mjd, self.bore)
def build_rangedata(tod, rcut, d, ivar): nmax = np.max(rcut.ranges[:,1]-rcut.ranges[:,0]) nrange = rcut.nrange rdata = bunch.Bunch() rdata.detmap = np.zeros(nrange,int) rdata.tod = np.zeros([nrange,nmax],dtype) rdata.pos = np.zeros([nrange,nmax,2]) rdata.ivar= np.zeros([nrange,nmax],dtype) # Build our detector mapping for di in range(rcut.ndet): rdata.detmap[rcut.detmap[di]:rcut.detmap[di+1]] = di rdata.n = rcut.ranges[:,1]-rcut.ranges[:,0] # Extract our tod samples and coordinates for i, r in enumerate(rcut.ranges): di = rdata.detmap[i] rn = r[1]-r[0] rdata.tod[i,:rn] = tod[di,r[0]:r[1]] bore = d.boresight[:,r[0]:r[1]] mjd = utils.ctime2mjd(bore[0]) pos_hor = bore[1:] + d.point_offset[di,:,None] pos_rel = coordinates.transform(tod_sys, sys, pos_hor, time=mjd, site=d.site) rdata.pos[i,:rn] = pos_rel.T # Expand noise ivar too, including the effect of our normal data cut rdata.ivar[i,:rn] = ivar[di] * (1-d.cut[di:di+1,r[0]:r[1]].to_mask()[0]) # Precompute our fourier space units rdata.freqs = fft.rfftfreq(nmax, 1/d.srate) # And precompute out butterworth filter rdata.butter = filters.mce_filter(rdata.freqs, d.mce_fsamp, d.mce_params) # Store the fiducial time constants for reference rdata.tau = d.tau # These are also nice to have rdata.dsens = ivar**-0.5 / d.srate**0.5 rdata.asens = np.sum(ivar)**-0.5 / d.srate**0.5 rdata.srate = d.srate rdata.dets = d.dets rdata.beam = d.beam rdata.id = d.entry.id return rdata
def summarize(self, chain): """Given a chain of lpars, compute a summary in the same format as returned by SrcFitterML.""" dposs = np.array([c.dpos for c in chain]) poss_cel = np.array([c.poss for c in chain]) poss_hor = poss_cel*0 for i in range(len(self.sdata)): poss_hor[:,i] = coordinates.transform("cel","hor",poss_cel[:,i].T, time=utils.ctime2mjd(self.sdata[i].ctime), site=self.sdata[i].site).T ampss = np.array([c.amps for c in chain]) vampss = np.array([c.vamps for c in chain]) chisq0 = chain[0].chisq0 chisq = np.mean([c.chisq for c in chain]) # Compute means dpos = np.mean(dposs,0) pos_cel = np.mean(poss_cel,0) pos_hor = np.mean(poss_hor,0) amps = np.mean(ampss,0) # And uncertainties dpos_cov= np.cov(dposs.T) ddpos = np.diag(dpos_cov)**0.5 pcorr = dpos_cov[0,1]/ddpos[0]/ddpos[1] # mean([da0_i + da_ij]**2). Uncorrelated, so this is # mean(da0**2) + mean(da**2) arel = ampss + np.random.standard_normal(ampss.shape)*vampss**0.5 amps = np.mean(arel,0) vamps = np.var(arel,0) #vamps = np.var(ampss,0)+np.mean(vampss,0) damps = vamps**0.5 models = self.lik.eval(dpos).models nsigma = (chisq0-chisq)**0.5 # We want how much to offset detector by, not how much to offset # source by res = bunch.Bunch( dpos = dpos, poss_cel=pos_cel, poss_hor=pos_hor, ddpos= ddpos, amps=amps, damps=damps, pcorr=pcorr, nsrc = len(self.sdata), models=models, nsigma = nsigma, chisq0 = chisq0, chisq = chisq, npix=self.lik.npix) return res
def calc_full_result(self, dpos, marginalize=True): res = self.lik.eval(dpos) res.poss_cel = res.poss res.poss_hor = np.array([ coordinates.transform("cel","hor",res.poss_cel[i],utils.ctime2mjd(self.sdata[i].ctime),site=self.sdata[i].site) for i in range(self.nsrc) ]) # Get the position uncertainty hess = self.calc_hessian(dpos, step=0.1*utils.arcmin) hess = 0.5*(hess+hess.T) try: pcov = np.linalg.inv(0.5*hess) except np.linalg.LinAlgError: pcov = np.diag([np.inf,np.inf]) dchisq = res.chisq0-res.chisq # Apply marginalization correction if marginalize: R = self.lik.rmax A = np.pi*R**2 Abeam = 2*np.pi*self.fwhm**2/(8*np.log(2)) npoint= int(np.round(A/Abeam * self.nsrc)) # Correct position and uncertainty dpos, pcov = marg_pos(dpos, pcov, res.chisq0-res.chisq, npoint, R) # Correct total chisquare prob = stats.norm.cdf(-dchisq**0.5) if prob > 1e-10: prob = 1-(1-prob)**npoint dchisq = stats.norm.ppf(prob)**2 ddpos = np.diag(pcov)**0.5 pcorr = pcov[0,1]/ddpos[0]/ddpos[1] res.dpos = dpos res.ddpos = ddpos res.damps = res.vamps**0.5 res.pcorr = pcorr res.pcov = pcov res.nsrc = self.nsrc res.dchisq= dchisq res.nsigma= dchisq**0.5 return res
def __init__(self, sdat): self.mjd = utils.ctime2mjd(sdat.ctime) self.site = sdat.site self.ref_cel = sdat.srcpos # Find the boresight pointing that defines our focalplane # coordinates. This is not exact for two reasons: # 1. The array center is offset from the boresight, by about 1 degree. # 2. The detectors are offset from the array center by half that. # We could store the former to improve our accuracy a bit, but # to get #2 we would need a time-domain fit, which is what we're # trying to avoid here. The typical error from using the array center # instead would be about 1' offset * 1 degree error = 1.05 arcsec error. # We *can* easily get the boresight elevation since we have constant # elevation scans, so that removes half the error. That should be # good enough. self.bore = [ coordinates.transform("cel", "hor", sdat.srcpos, time=self.mjd, site=self.site)[0], sdat.el ] self.ref_foc = cel2foc(self.ref_cel, self.site, self.mjd, self.bore)
# Allocate our output map omap = enmap.zeros(shape, wcs, dtype) nblock = (omap.shape[-2]+bsize-1)//bsize for bi in range(nblock): r1 = bi*bsize r2 = (bi+1)*bsize print "Processing row %5d/%d" % (r1, omap.shape[-2]) # Output map coordinates osub = omap[...,r1:r2,:] pmap = osub.posmap() # Coordinate transformation if args.rot: s1,s2 = args.rot.split(",") opos = coordinates.transform(s2, s1, pmap[::-1], pol=ncomp==3) pmap[...] = opos[1::-1] if len(opos) == 3: psi = -opos[2].copy() del opos # Switch to healpix convention theta = np.pi/2-pmap[0] phi = pmap[1] # Evaluate map at these locations if args.order == 0: pix = healpy.ang2pix(nside, theta, phi) osub[:] = imap[:,pix] elif args.order == 1: for i in range(ncomp): osub[i] = healpy.get_interp_val(imap[i], theta, phi) # Rotate polarization if necessary if args.rot and ncomp==3:
def err(t): ora, odec = coordinates.transform("hor","cel",[az,el],time=t,site=site) return utils.rewind(ra-ora, 0)
def tele_pos(scan): t, az, el = np.mean(scan.boresight[::10],0) t = uscan.mjd0 + t / (24.*60*60) ra, dec = coordinates.transform("hor","cel",[[az],[el]], time=[t], site=scan.site)[:,0] return az, el, ra, dec
def err(t): ora, odec = coordinates.transform("hor", "cel", [az, el], time=t, site=site) return utils.rewind(ra - ora, 0)
def find_scan_vel(scan, ipos, aspeed, dt=0.1): hpos = coordinates.transform("equ","hor", ipos, time=scan.mjd0, site=scan.site) hpos[0] += aspeed*dt opos = coordinates.transform("hor","equ", hpos, time=scan.mjd0, site=scan.site) opos[0] = utils.rewind(opos[0],ipos[0]) return (opos-ipos)/dt
except zipfile.BadZipfile: print "%s %9.3f %9.3f %9.3f %9.3f %s" % (id, np.nan, np.nan, np.nan, np.nan, "badzip") continue if bore.shape[0] < 3 or bore.shape[1] < 1: print "%s %9.3f %9.3f %9.3f %9.3f %s" % (id, np.nan, np.nan, np.nan, np.nan, "nobore") continue # Compute mean pointing in hor and equ t = np.median(bore[0, ::10]) hor = np.median(bore[1:, ::10], 1) try: equ = coordinates.transform("hor", "equ", hor[None] * utils.degree, time=utils.ctime2mjd(t[None]), site=site)[0] / utils.degree except AttributeError: equ = np.array([np.nan, np.nan]) hour = t / 3600 % 24 bsub = bore[:, 50::100].copy() bsub = bsub[:, np.any(~np.isnan(bsub), 0)] bsub[0] = utils.ctime2mjd(bsub[0]) bsub[1:3] = coordinates.transform("hor", "equ", bsub[1:3] * utils.degree, time=bsub[0], site=site) # Compute matching object
def build_tod_stats(entry, Naz=8, Nt=2): """Collect summary information for the tod in the given entry, returning it as a bunch. If some information can't be found, then those fields will be set to a placeholder value (usually NaN), but the fields will still all be present.""" # At the very least we need the pointing, so no try catch around this d = actdata.read(entry, ["boresight", "site"]) d += actdata.read_point_offsets(entry, no_correction=True) d = actdata.calibrate(d, exclude=["autocut"]) # Get the array center and radius acenter = np.mean(d.point_offset, 0) arad = np.mean((d.point_offset - acenter)**2, 0)**0.5 t, baz, bel = 0.5 * (np.min(d.boresight, 1) + np.max(d.boresight, 1)) #t, baz, bel = np.mean(d.boresight,1) az = baz + acenter[0] el = bel + acenter[1] dur, waz, wel = np.max(d.boresight, 1) - np.min(d.boresight, 1) if waz > 180 * utils.degree: print("bad waz %8.3f for %s" % (waz / utils.degree, entry.id)) mjd = utils.ctime2mjd(t) hour = t / 3600. % 24 day = hour >= day_range[0] and hour < day_range[1] night = not day jon = (t - jon_ref) / (3600 * 24) ra, dec = coordinates.transform(tsys, "cel", [az, el], mjd, site=d.site) # Get the array center bounds on the sky, assuming constant elevation ts = utils.ctime2mjd(t + dur / 2 * np.linspace(-1, 1, Nt)) azs = az + waz / 2 * np.linspace(-1, 1, Naz) E1 = coordinates.transform(tsys, "cel", [azs, [el] * Naz], time=[ts[0]] * Naz, site=d.site)[:, 1:] E2 = coordinates.transform(tsys, "cel", [[azs[-1]] * Nt, [el] * Nt], time=ts, site=d.site)[:, 1:] E3 = coordinates.transform(tsys, "cel", [azs[::-1], [el] * Naz], time=[ts[-1]] * Naz, site=d.site)[:, 1:] E4 = coordinates.transform(tsys, "cel", [[azs[0]] * Nt, [el] * Nt], time=ts[::-1], site=d.site)[:, 1:] bounds = np.concatenate([E1, E2, E3, E4], 1) bounds[0] = utils.rewind(bounds[0]) ## Grow bounds by array radius #bmid = np.mean(bounds,1) #for i in range(2): # bounds[i,bounds[i]<bmid[i]] -= arad[i] # bounds[i,bounds[i]>bmid[i]] += arad[i] tot_id = entry.id + (":" + entry.tag if entry.tag else "") res = bunch.Bunch(id=tot_id, nsamp=d.nsamp, t=t, mjd=mjd, jon=jon, hour=hour, day=day, night=night, dur=dur, az=az / utils.degree, el=el / utils.degree, baz=baz / utils.degree, bel=bel / utils.degree, waz=waz / utils.degree, wel=wel / utils.degree, ra=ra / utils.degree, dec=dec / utils.degree, bounds=bounds / utils.degree) if "gseason" in entry: res[entry.gseason] = True # Planets for obj in [ "Sun", "Moon", "Mercury", "Venus", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune" ]: res[obj] = coordinates.ephem_pos(obj, utils.ctime2mjd(t)) / utils.degree # Get our weather information, if available try: d += actdata.read(entry, ["apex"]) d = actdata.calibrate_apex(d) res["pwv"] = d.apex.pwv res["wx"] = d.apex.wind[0] res["wy"] = d.apex.wind[1] res["wind_speed"] = d.apex.wind_speed res["T"] = d.apex.temperature except errors.DataMissing: res["pwv"] = np.NaN res["wx"] = np.NaN res["wy"] = np.NaN res["wind_speed"] = np.NaN res["T"] = np.NaN # Try to get our cut info, so that we can select on # number of detectors and cut fraction try: npre = d.nsamp * d.ndet d += actdata.read(entry, ["cut"]) res["ndet"] = d.ndet res["cut"] = 1 - d.nsamp * d.ndet / float(npre) except errors.DataMissing: res["ndet"] = 0 res["cut"] = 1.0 # Try to get hwp info res["hwp"] = False res["hwp_name"] = "none" try: epochs = actdata.try_read(files.read_hwp_epochs, "hwp_epochs", entry.hwp_epochs) t, _, ar = entry.id.split(".") t = float(t) if ar in epochs: for epoch in epochs[ar]: if t >= epoch[0] and t < epoch[1]: res["hwp"] = True res["hwp_name"] = epoch[2] except errors.DataMissing: pass return res
def foc2cel(fpos, site, mjd, bore): baz, bel = bore hpos = coordinates.recenter(fpos, [0,0,baz,bel]) cpos = coordinates.transform("hor","cel",hpos,time=mjd,site=site) return cpos
def cel2foc(cpos, site, mjd, bore): baz, bel = bore hpos = coordinates.transform("cel","hor",cpos,time=mjd,site=site) fpos = coordinates.recenter(hpos, [baz,bel,0,0]) return fpos
hour = info[ind].fields.hour tags = sorted(list(set(info[ind].tags) - set([id]))) # Get input pointing bore = d.boresight[:, ::sstep] offs = d.point_offset.T[:, ::dstep] ipoint = np.zeros(bore.shape + offs.shape[1:]) ipoint[0] = utils.ctime2mjd(bore[0, :, None]) ipoint[1:] = bore[1:, :, None] + offs[:, None, :] ipoint = ipoint.reshape(3, -1) iref = np.mean(ipoint[1:], 1) # Transform to equ opoint = coordinates.transform("hor", "equ", ipoint[1:], time=ipoint[0], site=d.site) oref = np.mean(opoint, 1) print "%s %4.1f %8.3f %7.3f %8.3f %7.3f" % ( id, hour, iref[0] / utils.degree, iref[1] / utils.degree, oref[0] / utils.degree, oref[1] / utils.degree), # Compute position of each object, and distance to it orect = coordinates.ang2rect(opoint, zenith=False) for obj in objs: objpos = coordinates.ephem_pos(obj, ipoint[0, 0]) objrect = coordinates.ang2rect(objpos, zenith=False) odist = np.min(np.arccos(np.sum(orect * objrect[:, None], 0))) print "| %8.3f %7.3f %7.3f" % (objpos[0] / utils.degree, objpos[1] /
az1 = np.min(d.boresight[1]) az2 = np.max(d.boresight[1]) el = np.mean(d.boresight[2]) if not valid_az_range(az1, az2): L.debug("Skipped %s (%s)" % (id, "Azimuth crosses poles")) continue # Then get the ra block we live in. This is set by the lowest RA- # detector at the lowest az of the scan at the earliest time in # the scan. So transform all the detectors. ipoint = np.zeros([2, d.ndet]) ipoint[0] = az1 + d.point_offset[:,0] ipoint[1] = el + d.point_offset[:,1] mjd = utils.ctime2mjd(d.boresight[0,0]) opoint = coordinates.transform("hor","cel",ipoint,time=mjd,site=d.site) ra1 = np.min(opoint[0]) wid = tagger.build(az1,az2,el,ra1) print "%s %s" % (id, wid) sys.stdout.flush() elif command == "build": # Given a list of id tag, loop over tags, and project tods on # a work space per tag. parser = config.ArgumentParser(os.environ["HOME"] + "/.enkirc") parser.add_argument("command") parser.add_argument("todtags") parser.add_argument("odir") args = parser.parse_args() filedb.init()
def __call__(self, pos): mjd = utils.ctime2mjd(self.data["t"]) hor = coordinates.transform("cel", "hor", pos * utils.degree, mjd) / utils.degree return hor
# Allocate our output map omap = enmap.zeros(shape, wcs, dtype) nblock = (omap.shape[-2] + bsize - 1) // bsize for bi in range(nblock): r1 = bi * bsize r2 = (bi + 1) * bsize print "Processing row %5d/%d" % (r1, omap.shape[-2]) # Output map coordinates osub = omap[..., r1:r2, :] pmap = osub.posmap() # Coordinate transformation if args.rot: s1, s2 = args.rot.split(",") opos = coordinates.transform(s2, s1, pmap[::-1], pol=ncomp == 3) pmap[...] = opos[1::-1] if len(opos) == 3: psi = -opos[2].copy() del opos # Switch to healpix convention theta = np.pi / 2 - pmap[0] phi = pmap[1] # Evaluate map at these locations if args.order == 0: pix = healpy.ang2pix(nside, theta, phi) osub[:] = imap[:, pix] elif args.order == 1: for i in range(ncomp): osub[i] = healpy.get_interp_val(imap[i], theta, phi) # Rotate polarization if necessary if args.rot and ncomp == 3:
def obj_pos(scan, obj): ra, dec = coordinates.ephem_pos(obj, scan.mjd0) az, el = coordinates.transform("cel","hor",[[ra],[dec]],time=[scan.mjd0], site=scan.site)[:,0] return az, el, ra, dec
def cel2foc(cpos, site, mjd, bore): baz, bel = bore hpos = coordinates.transform("cel", "hor", cpos, time=mjd, site=site) fpos = coordinates.recenter(hpos, [baz, bel, 0, 0]) return fpos
el = np.mean(d.boresight[2]) if not valid_az_range(az1, az2): L.debug("Skipped %s (%s)" % (id, "Azimuth crosses poles")) continue # Then get the ra block we live in. This is set by the lowest RA- # detector at the lowest az of the scan at the earliest time in # the scan. So transform all the detectors. ipoint = np.zeros([2, d.ndet]) ipoint[0] = az1 + d.point_offset[:, 0] ipoint[1] = el + d.point_offset[:, 1] mjd = utils.ctime2mjd(d.boresight[0, 0]) opoint = coordinates.transform("hor", "cel", ipoint, time=mjd, site=d.site) ra1 = np.min(opoint[0]) wid = tagger.build(az1, az2, el, ra1) print "%s %s" % (id, wid) sys.stdout.flush() elif command == "build": # Given a list of id tag, loop over tags, and project tods on # a work space per tag. parser = config.ArgumentParser(os.environ["HOME"] + "/.enkirc") parser.add_argument("command") parser.add_argument("todtags") parser.add_argument("odir") args = parser.parse_args()
# Iterate over tods for ind in range(comm.rank, len(ids), comm.size): id = ids[ind] oid = id.replace(":","_") # Check if we hit any of the sources. We first make sure # there's no angle wraps in the bounds, and then move the sources # to the same side of the sky. bounds are pretty approximate, so # might not actually hit all these sources if bounds is not None: poly = bounds[:,:,ind]*utils.degree poly[0] = utils.rewind(poly[0],poly[0,0]) # bounds are defined in celestial coordinates. Must convert srcpos for comparison mjd = utils.ctime2mjd(float(id.split(".")[0])) srccel = coordinates.transform(src_sys, "cel", srcpos, time=mjd) srccel[0] = utils.rewind(srccel[0], poly[0,0]) poly = pad_polygon(poly.T, poly_pad).T sids = np.where(utils.point_in_polygon(srccel.T, poly.T))[0] sids = sorted(list(set(sids)&allowed)) else: sids = sorted(list(allowed)) if len(sids) == 0: print("%s has 0 srcs: skipping" % id) continue try: nsrc = len(sids) print("%s has %d srcs: %s" % (id,nsrc,", ".join(["%d (%.1f)" % (i,a) for i,a in zip(sids,amps[sids])]))) except TypeError as e: print("Weird: %s" % e) print(sids)
continue hour = info[ind].fields.hour tags = sorted(list(set(info[ind].tags)-set([id]))) # Get input pointing bore = d.boresight[:,::sstep] offs = d.point_offset.T[:,::dstep] ipoint = np.zeros(bore.shape + offs.shape[1:]) ipoint[0] = utils.ctime2mjd(bore[0,:,None]) ipoint[1:]= bore[1:,:,None]+offs[:,None,:] ipoint = ipoint.reshape(3,-1) iref = np.mean(ipoint[1:],1) # Transform to equ opoint = coordinates.transform("hor","equ", ipoint[1:], time=ipoint[0], site=d.site) oref = np.mean(opoint,1) print "%s %4.1f %8.3f %7.3f %8.3f %7.3f" % (id, hour, iref[0]/utils.degree, iref[1]/utils.degree, oref[0]/utils.degree, oref[1]/utils.degree), # Compute position of each object, and distance to it orect = coordinates.ang2rect(opoint, zenith=False) for obj in objs: objpos = coordinates.ephem_pos(obj, ipoint[0,0]) objrect = coordinates.ang2rect(objpos, zenith=False) odist = np.min(np.arccos(np.sum(orect*objrect[:,None],0))) print "| %8.3f %7.3f %7.3f" % (objpos[0]/utils.degree, objpos[1]/utils.degree, odist/utils.degree), print "| "+" ".join(tags) sys.stdout.flush()