def point_in_polygon_safe(points, polygons): points = np.asarray(points) polygons = np.array(polygons) # Put the polygon on the same side of the sky as the points polygons[0] = utils.rewind(polygons[0], points[0], 360) # But don't allow sky wraps inside polygons polygons[0] = utils.rewind(polygons[0], polygons[0, 0], 360) return utils.point_in_polygon(points.T, polygons.T)
def poly_dist(points, polygons): points = np.asarray(points) * utils.degree polygons = np.array(polygons) * utils.degree # Put the polygon on the same side of the sky as the points polygons[0] = utils.rewind(polygons[0], points[0]) # But don't allow sky wraps inside polygons polygons[0] = utils.rewind(polygons[0], polygons[0, 0]) inside = utils.point_in_polygon(points.T, polygons.T) dists = utils.poly_edge_dist(points.T, polygons.T) dists = np.where(inside, 0, dists) return dists
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 make_maps(tod, data, pos, ncomp, radius, resolution): tod = tod.copy() pos = np.array(pos) # Handle angle wrapping pos = utils.rewind(pos, data.ref) nsrc= len(pos) dbox= np.array([[-1,-1],[1,1]])*radius shape, wcs = enmap.geometry(pos=dbox, res=resolution) # Set up pixels n = int(np.round(2*radius/resolution)) boxes = np.array([[p-radius,p+radius] for p in pos]) # Set up output maps rhs = enmap.zeros((nsrc,ncomp) +shape, wcs, dtype=dtype) div = enmap.zeros((ncomp,nsrc,ncomp)+shape, wcs, dtype=dtype) # Build rhs ptsrc_data.nmat_basis(tod, data) pmat_thumbs(-1, tod, rhs, boxes, data) # Build div for c in range(ncomp): idiv = div[0].copy(); idiv[:,c] = 1 wtod = data.tod.astype(dtype,copy=True); wtod[...] = 0 pmat_thumbs( 1, wtod, idiv, boxes, data) ptsrc_data.nmat_basis(wtod, data, white=True) pmat_thumbs(-1, wtod, div[c], boxes, data) div = np.rollaxis(div,1) mask = div[:,0] != 0 bin = rhs.copy() bin[mask] = rhs[mask]/div[:,0][mask] # Fixme: only works for ncomp == 1 return bin, rhs, div
def make_maps(tod, data, pos, ncomp, radius, resolution): tod = tod.copy() pos = np.array(pos) # Handle angle wrapping pos = utils.rewind(pos, data.ref) nsrc = len(pos) dbox = np.array([[-1, -1], [1, 1]]) * radius shape, wcs = enmap.geometry(pos=dbox, res=resolution) # Set up pixels n = int(np.round(2 * radius / resolution)) boxes = np.array([[p - radius, p + radius] for p in pos]) # Set up output maps rhs = enmap.zeros((nsrc, ncomp) + shape, wcs, dtype=dtype) div = enmap.zeros((ncomp, nsrc, ncomp) + shape, wcs, dtype=dtype) # Build rhs ptsrc_data.nmat_basis(tod, data) pmat_thumbs(-1, tod, rhs, boxes, data) # Build div for c in range(ncomp): idiv = div[0].copy() idiv[:, c] = 1 wtod = data.tod.astype(dtype, copy=True) wtod[...] = 0 pmat_thumbs(1, wtod, idiv, boxes, data) ptsrc_data.nmat_basis(wtod, data, white=True) pmat_thumbs(-1, wtod, div[c], boxes, data) div = np.rollaxis(div, 1) mask = div[:, 0] != 0 bin = rhs.copy() bin[mask] = rhs[mask] / div[:, 0][mask] # Fixme: only works for ncomp == 1 return bin, rhs, div
def interpol_pos(from_sys, to_sys, name_or_pos, mjd, site=None, dt=10): """Given the name of an ephemeris object or a [ra,dec]-type position in radians in from_sys, compute its position in the specified coordinate system for each mjd. The mjds are assumed to be sampled densely enough that interpolation will work. For ephemeris objects, positions are computed in steps of 10 seconds by default (controlled by the dt argument).""" box = utils.widen_box([np.min(mjd), np.max(mjd)], 1e-2) sub_nsamp = max(3, int((box[1] - box[0]) * 24. * 3600 / dt)) sub_mjd = np.linspace(box[0], box[1], sub_nsamp, endpoint=True) if isinstance(name_or_pos, basestring): sub_from = ephem_pos(name_or_pos, sub_mjd) else: pos = np.asarray(name_or_pos) assert pos.ndim == 1 sub_from = np.zeros([2, sub_nsamp]) sub_from[:] = np.asarray(name_or_pos)[:, None] sub_pos = transform_raw(from_sys, to_sys, sub_from, time=sub_mjd, site=site) sub_pos[1] = utils.rewind(sub_pos[1], ref="auto") inds = (mjd - box[0]) * (sub_nsamp - 1) / (box[1] - box[0]) full_pos = utils.interpol(sub_pos, inds[None], order=3) return full_pos
def earth2sun(pos_earthrel, time, sundist, diff=False): """Compute the apparent displacement pos_sunrel - pos_earthrel for a source with earth-relative pointing given by pos_earthrel[{ra,dec},:] at the given time, and with the given distance from the sun (in AU).""" # Compute the position of the object relative to the earth at each time vec_obj_earthrel = utils.ang2rect(pos_earthrel, zenith=False) vec_earth_sunrel = -ephemeris.ephem_vec("Sun", time) # Get the object earth distance based on the sun distance, using # the law of sines: sin(obj_earth_sun)/obj_sun = sin(sun_obj_earth)/obj_earth. b = np.sum(vec_earth_sunrel**2, 0)**0.5 c = sundist cosC = np.sum(-(vec_obj_earthrel.T * vec_earth_sunrel.T).T, 0) / b sinC = (1 - cosC**2)**0.5 sinB = sinC * b / c sinA = np.sin(np.pi - np.arcsin(sinB) - np.arcsin(sinC)) a = b * sinA / sinB earthdist = a vec_obj_earthrel *= earthdist # Get the relative position vec_obj_sunrel = (vec_obj_earthrel.T + vec_earth_sunrel.T).T # Translate this into an angular position pos_sunrel = utils.rect2ang(vec_obj_sunrel, zenith=False) if not diff: return pos_sunrel # And turn that into a displacement offset = pos_sunrel - pos_earthrel offset[1] = utils.rewind(offset[1]) return offset
def transform_meta(transfun, coords, fields=["ang", "mag"], offset=5e-7): """Computes metadata for the coordinate transformation functor transfun applied to the coordinate array coords[2,...], such as the induced rotation, magnification. Currently assumes that input and output coordinates are in non-zenith polar coordinates. Might generalize this later. """ if "mag_brute" in fields: ntrans = 3 elif "ang" in fields: ntrans = 2 else: ntrans = 1 coords = np.asarray(coords) offsets = np.array([[0, 0], [1, 0], [0, 1]]) * offset # Transform all the coordinates. We assume we aren't super-close to the poles # either before or after the transformation. ocoords = np.zeros((ntrans, 2) + coords.shape[1:]) ocoords = None for i in range(ntrans): # Transpose to get broadcasting right a = transfun((coords.T + offsets[i].T).T) if ocoords is None: ocoords = np.zeros((ntrans, ) + a.shape, a.dtype) ocoords[i] = a class Result: pass res = Result() res.icoord = coords res.ocoord = ocoords[0] # Compute the individual properties we're interested in diff = utils.rewind(ocoords[1:] - ocoords[0, None]) if "ang" in fields: # We only need the theta offset of this one. We started with # an offset in the [1,0] direction, and want to know how # far we have rotated away from this direction. This # Uses the IAU tangent plane angle convention: # http://healpix.jpl.nasa.gov/html/intronode12.htm # and assumes that both input and putput coordinates have the # same handedness. This is not always the case, for example # with horizontal to celestial coordinate transformations. # In these cases, the caller must correct there resulting angle # manually. phiscale = np.cos(ocoords[0, 1]) res.ang = np.arctan2(diff[0, 1], diff[0, 0] * phiscale) if "mag" in fields: res.mag = np.cos(res.icoord[1]) / np.cos(res.ocoord[1]) if "mag_brute" in fields: # Compute the ratio of the areas of the triangles # made up by the three point-sets in the input and # output coordinates. This ratio is always 1 when # using physical areas, so we instead compute the # apparent areas here. def tri_area(diff): return 0.5 * np.abs(diff[0, 0] * diff[1, 1] - diff[0, 1] * diff[1, 0]) res.mag = (tri_area(diff).T / tri_area(offsets[1:] - offsets[0]).T).T return res
def get_templates(self, pos, irads): x = utils.rewind(self.pos - pos[:, None, None], 0, 2 * np.pi) W = np.array([[irads[0], irads[2]], [irads[2], irads[1]]]) xWx = np.sum(np.einsum("ab,byx->ayx", W, x) * x, 0) profile = np.exp(-0.5 * xWx) bases = np.eye(self.nfreq * self.ncomp).reshape( self.nfreq * self.ncomp, self.nfreq, self.ncomp) return profile[None, None, None] * bases[:, :, :, None, None]
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 sim_srcs(shape, wcs, srcs, beam, omap=None, dtype=None, nsigma=5, rmax=None, method="loop", mmul=1, return_padded=False): """Simulate a point source map in the geometry given by shape, wcs for the given srcs[nsrc,{dec,ra,T...}], using the beam[{r,val},npoint], which must be equispaced. If omap is specified, the sources will be added to it in place. All angles are in radians. The beam is only evaluated up to the point where it reaches exp(-0.5*nsigma**2) unless rmax is specified, in which case this gives the maximum radius. mmul gives a factor to multiply the resulting source model by. This is mostly useful in conction with omap. method can be "loop" or "vectorized", but "loop" is both faster and uses less memory, so there's no point in using the latter. The source simulation is sped up by using a source lookup grid. """ if omap is None: omap = enmap.zeros(shape, wcs, dtype) ishape = omap.shape omap = omap.preflat ncomp = omap.shape[0] # In keeping with the rest of the functions here, srcs is [nsrc,{dec,ra,T,Q,U}]. # The beam parameters are ignored - the beam argument is used instead amps = srcs[:,2:2+ncomp] poss = srcs[:,:2].copy() # Rewind positions to let us use flat-sky approximation for distance calculations #wcs = enmap.enlib.wcs.fix_wcs(wcs) ref = np.mean(enmap.box(shape, wcs, corner=False)[:,1]) poss[:,1] = utils.rewind(poss[:,1], ref) beam = expand_beam(beam, nsigma, rmax) rmax = nsigma2rmax(beam, nsigma) # Pad our map by rmax, so we get the contribution from sources # just ourside our area. We will later split our map into cells of size cres. Let's # adjust the padding so we have a whole number of cells cres = utils.nint(rmax/omap.pixshape()) epix = cres-(omap.shape[-2:]+2*cres)%cres padding = [cres,cres+epix] wmap, wslice = enmap.pad(omap, padding, return_slice=True) # Overall we will have this many grid cells cshape = wmap.shape[-2:]/cres # Find out which sources matter for which cells srcpix = wmap.sky2pix(poss.T).T pixbox= np.array([[0,0],wmap.shape[-2:]],int) nhit, cell_srcs = build_src_cells(pixbox, srcpix, cres) posmap = wmap.posmap() model = eval_srcs_loop(posmap, poss, amps, beam, cres, nhit, cell_srcs) # Update our work map, through our view if mmul != 1: model *= mmul wmap += model if not return_padded: # Copy out omap[:] = wmap[wslice] # Restore shape omap = omap.reshape(ishape) return omap else: return wmap.reshape(ishape[:-2]+wmap.shape[-2:]), wslice
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_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 calc_pbox(shape, wcs, box, n=10): nphi = utils.nint(np.abs(360 / wcs.wcs.cdelt[0])) dec = np.linspace(box[0, 0], box[1, 0], n) ra = np.linspace(box[0, 1], box[1, 1], n) y = enmap.sky2pix(shape, wcs, [dec, dec * 0 + box[0, 1]])[0] x = enmap.sky2pix(shape, wcs, [ra * 0 + box[0, 0], ra])[1] x = utils.unwind(x, nphi) pbox = np.array([[np.min(y), np.min(x)], [np.max(y), np.max(x)]]) xm1 = np.mean(pbox[:, 1]) xm2 = utils.rewind(xm1, shape[-1] / 2, nphi) pbox[:, 1] += xm2 - xm1 pbox = utils.nint(pbox) return pbox
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 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 sun2earth(pos_sunrel, time, sundist, diff=False): """Compute the apparent displacement pos_earthrel - pos_sunrel for a source with sun-relative pointing given by pos_sunrel[{ra,dec},:] at the given time, and with the given distance from the sun (in AU).""" # Compute the position of the object relative to the earth at each time vec_obj_sunrel = utils.ang2rect(pos_sunrel, zenith=False) * sundist vec_sun_earthrel = ephemeris.ephem_vec("Sun", time) vec_obj_earthrel = (vec_obj_sunrel.T + vec_sun_earthrel.T).T # Translate this into an angular position pos_earthrel = utils.rect2ang(vec_obj_earthrel, zenith=False) if not diff: return pos_earthrel # And turn that into a displacement offset = pos_earthrel - pos_sunrel offset[1] = utils.rewind(offset[1]) return offset
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 apply(self, dir, tod, srcs, tmul=None, pmul=None): if tmul is None: tmul = self.tmul if pmul is None: pmul = self.pmul while srcs.ndim < 4: srcs = srcs[:,None] # Handle angle wrapping without modifying the original srcs array wsrcs = srcs.copy() wsrcs[...,:2] = utils.rewind(srcs[...,:2], self.ref) # + self.dpos core = get_core(tod.dtype) # boresight: (t, az, el) core.pmat_ptsrc3(dir, tmul, pmul, tod.T, wsrcs.T, self.scan.boresight.T, self.scan.offsets.T, self.scan.comps.T, self.rbox.T, self.nbox.T, self.yvals.T, self.beam[1], self.beam[0,-1], self.rmax, self.cells.T, self.ncell.T, self.cbox.T) # Copy out any amplitudes that may have changed srcs[...,2:5] = wsrcs[...,2:5] # 2:5 -> T,Q,U in nparams
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 apply(self, dir, tod, srcs, tmul=None, pmul=None): if tmul is None: tmul = self.tmul if pmul is None: pmul = self.pmul if srcs.ndim == 2: srcs = srcs[:, None] # Handle angle wrapping without modifying the original srcs array wsrcs = srcs.copy() wsrcs[:, :, :2] = utils.rewind(srcs[:, :, :2], self.ref) + self.dpos t1 = time.time() core = get_core(tod.dtype) core.pmat_ptsrc2(dir, tmul, pmul, tod.T, wsrcs.T, self.scan.boresight.T, self.scan.offsets.T, self.scan.comps.T, self.rbox.T, self.nbox.T, self.yvals.T, self.scan.beam[1], self.scan.beam[0, -1], self.rmax, self.cells.T, self.ncell.T, self.cbox.T) # Copy out any amplitudes that may have changed srcs[:, :, 2:5] = wsrcs[:, :, 2:5]
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 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_gridinfo(shape, wcs, steps=[2, 2], nstep=[200, 200], zenith=False): """Return an array of line segments representing a coordinate grid for the given shape and wcs. the steps argument describes the number of points to use along each meridian.""" steps = np.zeros([2]) + steps nstep = np.zeros([2], dtype=int) + nstep gridinfo = Gridinfo() if enlib.wcs.is_plain(wcs): box = np.sort(enmap.box(shape, wcs), 0) start = np.floor(box[0] / steps) * steps nline = np.floor(box[1] / steps) - np.floor(box[0] / steps) + 1 else: box = np.array([[-90., 0.], [90., 360.]]) start = np.array([-90., 0.]) nline = np.array([180., 360.]) / steps + 1 gridinfo.lon = [] gridinfo.lat = [] gridinfo.shape = shape[-2:] gridinfo.wcs = wcs # Draw lines of longitude for phi in start[1] + np.arange(nline[1]) * steps[1]: # Loop over theta pixs = np.array( wcs.wcs_world2pix( phi, np.linspace(box[0, 0], box[1, 0], nstep[0], endpoint=True), 0)).T if not enlib.wcs.is_plain(wcs): phi = utils.rewind(phi, 0, 360) gridinfo.lon.append((phi, calc_line_segs(pixs))) # Draw lines of latitude for theta in start[0] + np.arange(nline[0]) * steps[0]: # Loop over phi pixs = np.array( wcs.wcs_world2pix( np.linspace(box[0, 1], box[1, 1] + 0.9, nstep[1], endpoint=True), theta, 0)).T if zenith: theta = 90 - theta gridinfo.lat.append((theta, calc_line_segs(pixs))) return gridinfo
def build_src_cells(cbox, srcpos, cres, unwind=False): # srcpos is [nsrc,...,{dec,ra}]. Reshape to 3d to keep things simple. # will reshape back when returning cbox = np.asarray(cbox) srcpos = np.asarray(srcpos) ishape = srcpos.shape srcpos = srcpos.reshape(ishape[0],-1,ishape[-1]) cshape = tuple(np.ceil(((cbox[1]-cbox[0])/cres)).astype(int)) if unwind: # Make the sources' ra compatible with our area ref = np.mean(cbox[:,1],0) srcpos[:,...,1] = utils.rewind(srcpos[:,...,1], ref) # How big must our cell hit array be? nmax = max(1,np.max(build_src_cells_helper(cbox, cshape, cres, srcpos))) ncell, cells = build_src_cells_helper(cbox, cshape, cres, srcpos, nmax) # Reshape back to original shape ncell = ncell.reshape(ishape[1:-1]+ncell.shape[1:]) cells = cells.reshape(ishape[1:-1]+cells.shape[1:]) return ncell, cells
def avoidance_cut(bore, det_offs, site, name_or_pos, margin): """Cut samples that get too close to the specified object (e.g. "Sun" or "Moon") or celestial position ([ra,dec] in racians). Margin specifies how much to avoid the object by.""" cmargin = np.cos(margin) mjd = utils.ctime2mjd(bore[0]) obj_pos = coordinates.interpol_pos("cel", "hor", name_or_pos, mjd, site) obj_pos[0] = utils.rewind(obj_pos[0], bore[1]) cosel = np.cos(obj_pos[1]) # Only cut if above horizon above_horizon = obj_pos[1] > 0 null_cut = sampcut.empty(det_offs.shape[0], bore.shape[1]) if np.all(~above_horizon): return null_cut # Find center of array, and radius arr_center = np.mean(det_offs, 0) arr_rad = np.max(np.sum((det_offs - arr_center)**2, 1)**0.5) def calc_mask(det_pos, rad, mask=slice(None)): offs = (det_pos - obj_pos[:, mask]) offs[0] *= cosel[mask] dists2 = np.sum(offs**2, 0) return dists2 < rad**2 # Find samples that could possibly be near object for any detector cand_mask = calc_mask(arr_center[:, None] + bore[1:], margin + arr_rad) cand_mask &= above_horizon cand_inds = np.where(cand_mask)[0] if len(cand_inds) == 0: return null_cut # Loop through all detectors and find out if each candidate actually intersects cuts = [] for di, off in enumerate(det_offs): det_pos = bore[1:, cand_inds] + off[:, None] det_mask = calc_mask(det_pos, margin, cand_inds) # Expand mask to full set det_mask_full = np.zeros(bore.shape[1], bool) det_mask_full[cand_inds] = det_mask # And use this to build actual cuts cuts.append(sampcut.from_mask(det_mask_full)) res = sampcut.stack(cuts) return res
def avoidance_cut(bore, det_offs, site, name_or_pos, margin): """Cut samples that get too close to the specified object (e.g. "Sun" or "Moon") or celestial position ([ra,dec] in racians). Margin specifies how much to avoid the object by.""" cmargin = np.cos(margin) mjd = utils.ctime2mjd(bore[0]) obj_pos = coordinates.interpol_pos("cel","hor",name_or_pos,mjd,site) obj_pos[0] = utils.rewind(obj_pos[0], bore[1]) cosel = np.cos(obj_pos[1]) # Only cut if above horizon above_horizon = obj_pos[1]>0 null_cut = sampcut.empty(det_offs.shape[0], bore.shape[1]) if np.all(~above_horizon): return null_cut # Find center of array, and radius arr_center = np.mean(det_offs,0) arr_rad = np.max(np.sum((det_offs-arr_center)**2,1)**0.5) def calc_mask(det_pos, rad, mask=slice(None)): offs = (det_pos-obj_pos[:,mask]) offs[0] *= cosel[mask] dists2= np.sum(offs**2,0) return dists2 < rad**2 # Find samples that could possibly be near object for any detector cand_mask = calc_mask(arr_center[:,None] + bore[1:], margin+arr_rad) cand_mask &= above_horizon cand_inds = np.where(cand_mask)[0] if len(cand_inds) == 0: return null_cut # Loop through all detectors and find out if each candidate actually intersects cuts = [] for di, off in enumerate(det_offs): det_pos = bore[1:,cand_inds]+off[:,None] det_mask = calc_mask(det_pos, margin, cand_inds) # Expand mask to full set det_mask_full = np.zeros(bore.shape[1], bool) det_mask_full[cand_inds] = det_mask # And use this to build actual cuts cuts.append(sampcut.from_mask(det_mask_full)) res = sampcut.stack(cuts) return res
) parser.add_argument("--already-arcmin", action="store_true") args = parser.parse_args() div = enmap.read_fits(args.div) if args.downgrade: div = enmap.downgrade(div, args.downgrade) div *= args.downgrade**2 div = div.reshape((-1, ) + div.shape[-2:])[0] # Individual pixel area if args.area_model == "average": pix_area = div * 0 + div.area() / div.size * (180 * 60 / np.pi)**2 else: pos = div.posmap() diffs = utils.rewind(pos[:, 1:, 1:] - pos[:, :-1, :-1], 0) pix_area = np.abs(diffs[0] * diffs[1]) * np.cos(pos[0, :-1, :-1]) del diffs # Go to square arcmins pix_area /= utils.arcmin**2 # Pad to recover edge pixels pix_area = np.concatenate([pix_area, pix_area[-1:]], 0) pix_area = np.concatenate([pix_area, pix_area[:, -1:]], 1) # Flatten everything div = div.reshape(-1) pix_area = pix_area.reshape(-1) with utils.nowarn(): rms = (pix_area / div)**0.5 if not args.already_arcmin else div**-0.5 inds = np.argsort(rms, axis=None)
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 err(t): ora, odec = coordinates.transform("hor", "cel", [az, el], time=t, site=site) return utils.rewind(ra - ora, 0)
allowed &= set(selected) f = open(args.odir + "/fits_%03d.txt" % comm.rank, "w") # 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])])))
utils.mkdir(args.odir) # Read in lines with format [id, az, el] # Compute drift angle and assign each id to a bin in angle. nbin = int(np.ceil(360 / args.binwidth)) with open(args.odir + "/bins.txt", "w") as f: for bi in range(nbin): f.write("%8.3f %8.3f\n" % (bi * args.binwidth, (bi + 1) * args.binwidth)) bfiles = [None for i in range(nbin)] site = None for line in open(args.todinfo, "r"): toks = line.split() id = toks[0] entry = db[id] az = utils.rewind(float(toks[1]), 0, 360) el = float(toks[2]) t = float(id[:id.index(".")]) if site is None: site = files.read_site(entry.site) angle = calc_driftangle([az * np.pi / 180, el * np.pi / 180], t, site) * 180 / np.pi if not np.isfinite(angle): continue # Assign bin bi = int(angle / args.binwidth) if bfiles[bi] is None: bfiles[bi] = open(args.odir + "/ang%03d.txt" % bi, "w") bfiles[bi].write("%s %8.2f\n" % (id, angle)) print "%s %8.2f %8.2f %8.2f %3d" % (id, az, el, angle, bi)
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 __init__(self, scan, area, dets): abox = area.box()[:, 1] self.scan, self.az0, self.daz = scan, abox[0], ( abox[1] - abox[0]) / area.shape[-1] self.az, self.dets = utils.rewind(self.scan.boresight[:, 1], 0), dets
parser.add_argument("-t", "--thin", type=int, default=1000) parser.add_argument("-A", "--area-model", type=str, default="exact", help="How to model pixel area. exact: Compute shape of each pixel. average: Use a single average number for all") args = parser.parse_args() div = enmap.read_fits(args.div) if args.downgrade: div = enmap.downgrade(div, args.downgrade) div *= args.downgrade**2 div = div.reshape((-1,)+div.shape[-2:])[0] # Individual pixel area if args.area_model == "average": pix_area = div*0 + div.area()/div.size*(180*60/np.pi)**2 else: pos = div.posmap() diffs = utils.rewind(pos[:,1:,1:]-pos[:,:-1,:-1],0) pix_area = np.abs(diffs[0]*diffs[1])*np.cos(pos[0,:-1,:-1]) del diffs # Go to square arcmins pix_area /= utils.arcmin**2 # Pad to recover edge pixels pix_area = np.concatenate([pix_area,pix_area[-1:]],0) pix_area = np.concatenate([pix_area,pix_area[:,-1:]],1) # Flatten everything div = div.reshape(-1) pix_area = pix_area.reshape(-1) with utils.nowarn(): rms = (pix_area/div)**0.5 inds = np.argsort(rms, axis=None)
filedb.init() db = filedb.data utils.mkdir(args.odir) # Read in lines with format [id, az, el] # Compute drift angle and assign each id to a bin in angle. nbin = int(np.ceil(360/args.binwidth)) with open(args.odir + "/bins.txt","w") as f: for bi in range(nbin): f.write("%8.3f %8.3f\n" % (bi*args.binwidth, (bi+1)*args.binwidth)) bfiles = [None for i in range(nbin)] site = None for line in open(args.todinfo,"r"): toks = line.split() id = toks[0] entry = db[id] az = utils.rewind(float(toks[1]),0,360) el = float(toks[2]) t = float(id[:id.index(".")]) if site is None: site = files.read_site(entry.site[0]) angle = calc_driftangle([az*np.pi/180,el*np.pi/180],t,site)*180/np.pi if not np.isfinite(angle): continue # Assign bin bi = int(angle/args.binwidth) if bfiles[bi] is None: bfiles[bi] = open(args.odir + "/ang%03d.txt"%bi,"w") bfiles[bi].write("%s %8.2f\n" % (id, angle)) print "%s %8.2f %8.2f %8.2f %3d" % (id, az, el, angle, bi)
def foc2cel(self, dfoc): foc = self.ref_foc + dfoc cel = foc2cel(foc, self.site, self.mjd, self.bore) dcel = utils.rewind(cel-self.ref_cel) return dcel
def cel2foc(self, dcel): cel = self.ref_cel + dcel foc = cel2foc(cel, self.site, self.mjd, self.bore) dfoc = utils.rewind(foc-self.ref_foc) return dfoc
selected = [int(w) for w in args.restrict.split(",")] allowed &= set(selected) for ind in range(comm.rank, len(ids), comm.size): id = ids[ind] oid = id.replace(":", "_") oname = "%s/%s.hdf" % (args.odir, oid) if args.cont and os.path.exists(oname): print "%s is done: skipping" % id continue # 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. poly = bounds[:, :, ind] * utils.degree poly[0] = utils.rewind(poly[0], poly[0, 0]) srcpos = srcpos.copy() srcpos[0] = utils.rewind(srcpos[0], poly[0, 0]) sids = np.where(utils.point_in_polygon(srcpos.T, poly.T))[0] sids = sorted(list(set(sids) & allowed)) if len(sids) == 0: print "%s has 0 srcs: skipping" % id continue nsrc = len(sids) print "%s has %d srcs: %s" % (id, nsrc, ", ".join( ["%d (%.1f)" % (i, a) for i, a in zip(sids, amps[sids])])) entry = filedb.data[id] try: with bench.mark("read"): d = actdata.read(entry)
oid = id.replace(":","_") oname = "%s/%s.fits" % (args.odir, oid) ename = oname + ".empty" if args.cont: if os.path.exists(oname): print "%5d/%d %s is done: skipping" % (ind+1, len(ids), id) continue if os.path.exists(ename): print "%5d/%d %s already failed: skipping" % (ind+1, len(ids), id) continue # 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. poly = bounds[:,:,ind]*utils.degree poly[0] = utils.rewind(poly[0],poly[0,0]) srcpos = srcpos.copy() srcpos[0] = utils.rewind(srcpos[0], poly[0,0]) sids = np.where(utils.point_in_polygon(srcpos.T, poly.T))[0] sids = np.array(sorted(list(set(sids)&allowed))) if len(sids) == 0: print "%5d/%d %s has 0 srcs: skipping" % (ind+1, len(ids), id) continue nsrc = len(sids) print "%5d/%d %s has %d srcs: %s" % (ind+1, len(ids), id,nsrc,", ".join(["%d (%.1f)" % (i,a) for i,a in zip(sids,amps[sids])])) def skip(msg): print "%s skipped: %s" % (id, msg) with open(ename, "w") as f: f.write(msg + "\n")
def err(t): ora, odec = coordinates.transform("hor","cel",[az,el],time=t,site=site) return utils.rewind(ra-ora, 0)
# Find out which sources are hit by each tod db = filedb.scans.select(filedb.scans[args.sel]) tod_srcs = {} for sid, src in enumerate(srcs): if args.src is not None and sid != args.src: continue if src.type == "planet": # This is a bit hacky, but sometimes the "t" member is unavailable # in the database. This also ignores the planet movement during the # TOD. We will take that into account in the final step in the mapping, though. t = np.char.partition(db.ids, ".")[:, 0].astype(float) + 300 ra, dec = ephemeris.ephem_pos(src.name, utils.ctime2mjd(t), dt=0)[:2] else: ra, dec = src.ra, src.dec points = np.array([ra, dec]) polys = db.data["bounds"] * utils.degree polys[0] = utils.rewind(polys[0], points[0]) polys[0] = utils.rewind(polys[0], polys[0, 0]) inside = utils.point_in_polygon(points.T, polys.T) dists = utils.poly_edge_dist(points.T, polys.T) dists = np.where(inside, 0, dists) hit = np.where(dists < args.hit_tol * utils.degree)[0] for id in db.ids[hit]: if not id in tod_srcs: tod_srcs[id] = [] tod_srcs[id].append(sid) # Prune those those that are done if args.cont: good = [] for id in tod_srcs: bid = id.replace(":", "_") ndone = 0
filedb.init() ids = filedb.scans[args.sel] db = filedb.scans.select(ids) ntod = len(db) nsplit = args.nsplit nopt = args.nopt if nsplit > 1 else 0 optimize_subsets = (args.mode == "crosslink" or args.mode=="scanpat") utils.mkdir(args.odir) # Determine which arrays we have. We can't process arrays independently, # as they in principle have correlated noise. But we also want to distinguish # between them pre, _, anames = np.char.rpartition(ids,".").T if args.mode == "crosslink": # Treat rising and setting as separate arrays" rise = utils.rewind(db.data["baz"],0,360) > 0 anames[rise] = np.char.add(anames[rise], "r") anames[~rise] = np.char.add(anames[~rise],"s") elif args.mode == "scanpat": # Treat each scanning pattern as a different array patterns = np.array([db.data["baz"],db.data["bel"],db.data["waz"]]).T pids = utils.label_unique(patterns, axes=(1,), atol=1.0) npat = np.max(pids)+1 for pid in range(npat): anames[pids==pid] = np.char.add(anames[pids==pid], "p%d" % pid) def ids2ctimes(ids): return np.char.partition(ids,".").T[0].astype(int) arrays, ais, nper = np.unique(anames, return_counts=True, return_inverse=True) narray = len(arrays) ctime = ids2ctimes(pre) sys.stderr.write("found arrays " + " ".join(arrays) + "\n")
def calc_profile(self, pos): dpos = self.posmap[::-1] - pos[:,None,None] if np.abs(dpos[0,0,0]) > np.pi: dpos[0] = utils.rewind(dpos[0]) return self.beam.eval(dpos)
# Find out which sources are hit by each tod db = filedb.scans.select(filedb.scans[args.sel]) tod_srcs = {} for sid, src in enumerate(srcs): if args.src is not None and sid != args.src: continue if src.type == "planet": # This is a bit hacky, but sometimes the "t" member is unavailable # in the database. This also ignores the planet movement during the # TOD. We will take that into account in the final step in the mapping, though. t = np.char.partition(db.ids,".")[:,0].astype(float)+300 ra, dec = ephemeris.ephem_pos(src.name, utils.ctime2mjd(t), dt=0)[:2] else: ra, dec = src.ra, src.dec points = np.array([ra, dec]) polys = db.data["bounds"]*utils.degree polys[0] = utils.rewind(polys[0], points[0]) polys[0] = utils.rewind(polys[0], polys[0,0]) inside = utils.point_in_polygon(points.T, polys.T) dists = utils.poly_edge_dist(points.T, polys.T) dists = np.where(inside, 0, dists) hit = np.where(dists < args.hit_tol*utils.degree)[0] for id in db.ids[hit]: if not id in tod_srcs: tod_srcs[id] = [] tod_srcs[id].append(sid) # Prune those those that are done if args.cont: good = [] for id in tod_srcs: bid = id.replace(":","_") ndone = 0
db = filedb.scans.select(ids) ntod = len(db) nsplit = args.nsplit nopt = args.nopt if nsplit > 1 else 0 optimize_subsets = (args.mode == "crosslink" or args.mode=="scanpat") detdir = args.odir + "/details" utils.mkdir(args.odir) utils.mkdir(detdir) # Determine which arrays we have. We can't process arrays independently, # as they in principle have correlated noise. But we also want to distinguish # between them pre, _, anames = np.char.rpartition(ids,".").T if args.mode == "crosslink": # Treat rising and setting as separate arrays" rise = utils.rewind(db.data["baz"],0,360) > 0 anames[rise] = np.char.add(anames[rise], "r") anames[~rise] = np.char.add(anames[~rise],"s") elif args.mode == "scanpat": # Treat each scanning pattern as a different array patterns = np.array([db.data["baz"],db.data["bel"],db.data["waz"]]).T pids = utils.label_unique(patterns, axes=(1,), atol=1.0) npat = np.max(pids)+1 for pid in range(npat): anames[pids==pid] = np.char.add(anames[pids==pid], "p%d" % pid) def ids2ctimes(ids): return np.char.partition(ids,".").T[0].astype(int) def fix_aname(aname): return aname.replace("ar","pa").replace(":","_") anames = np.array([fix_aname(aname) for aname in anames]) arrays, ais, nper = np.unique(anames, return_counts=True, return_inverse=True) narray = len(arrays)