Exemple #1
0
def get_footprint(tod,
                  wcs_kernel,
                  dets=None,
                  timestamps=None,
                  boresight=None,
                  focal_plane=None,
                  sight=None,
                  rot=None):
    """Find a geometry (in the sense of enmap) based on wcs_kernel that is
    big enough to contain all data from tod.  Returns (shape, wcs).

    """
    dets = _valid_arg(dets, tod.dets.vals, src=tod)
    fp0 = _valid_arg(focal_plane, 'focal_plane', src=tod)
    if sight is None and 'sight' in tod:
        sight = tod.sight
    sight = _valid_arg(sight, tod.get('sight'), src=tod)
    if sight is None:
        # Let's try to either require a sightline or boresight info.
        timestamps = _valid_arg(timestamps, 'timestamps', src=tod)
        boresight = _valid_arg(boresight, 'boresight', src=tod)
        sight = so3g.proj.CelestialSightLine.az_el(timestamps,
                                                   boresight.az,
                                                   boresight.el,
                                                   roll=boresight.roll,
                                                   site='so',
                                                   weather='typical').Q
    sight = _get_csl(sight)
    n_samp = len(sight.Q)

    # Do a simplest convex hull...
    q = so3g.proj.quat.rotation_xieta(fp0.xi, fp0.eta)
    xi, eta, _ = so3g.proj.quat.decompose_xieta(q)
    xi0, eta0 = xi.mean(), eta.mean()
    R = ((xi - xi0)**2 + (eta - eta0)**2).max()**.5

    n_circ = 16
    dphi = 2 * np.pi / n_circ
    phi = np.arange(n_circ) * dphi
    # cos(dphi/2) is the largest underestimate in radius one can make when
    # replacing a circle with an n_circ-sided polygon, as we do here.
    L = 1.01 * R / np.cos(dphi / 2)
    xi, eta = L * np.cos(phi) + xi0, L * np.sin(phi) + eta0
    fake_dets = ['hull%i' % i for i in range(n_circ)]
    fp1 = so3g.proj.FocalPlane.from_xieta(fake_dets, xi, eta, 0 * xi)

    asm = so3g.proj.Assembly.attach(sight, fp1)
    output = np.zeros((len(fake_dets), n_samp, 4))
    proj = so3g.proj.Projectionist.for_geom((1, 1), wcs_kernel)
    if rot:
        # Works whether rot is a quat or a vector of them.
        asm.Q = rot * asm.Q
    proj.get_planar(asm, output=output)

    output2 = output * 0
    proj.get_coords(asm, output=output2)

    # Get the pixel extrema in the form [{xmin,ymin},{xmax,ymax}]
    delts = wcs_kernel.wcs.cdelt * DEG
    planar = output[:, :, :2]
    ranges = utils.minmax(planar / delts, (0, 1))
    # These are in units of pixel *offsets* from crval. crval
    # might not correspond to a pixel center, though. So the
    # thing that should be integer-valued to preserve pixel compatibility
    # is crpix + ranges, not just ranges. Let's add crpix to transform this
    # into offsets from the bottom-left pixel to make it easier to reason
    # about integers
    ranges += wcs_kernel.wcs.crpix
    del output

    # Start a new WCS and set the lower left corner.
    w = wcs_kernel.deepcopy()
    corners = utils.nint(ranges)
    w.wcs.crpix -= corners[0]
    shape = tuple(corners[1] - corners[0] + 1)[::-1]
    return (shape, w)
Exemple #2
0
def read_area(ipathfmt,
              opix,
              itile1=(None, None),
              itile2=(None, None),
              verbose=False,
              cache=None,
              slice=None,
              wrap=True):
    """Given a set of tiles on disk with locations ipathfmt % {"y":...,"x":...},
    read the data corresponding to the pixel range opix[{from,to},{y,x}] in
    the full map."""
    opix = np.asarray(opix)
    # Find the range of input tiles
    itile1, itile2 = find_tile_range(ipathfmt, itile1, itile2)
    # To fill in the rest of the information we need to know more
    # about the input tiling, so read the first tile
    if cache is None or cache[2] is None:
        geo = read_tileset_geometry(ipathfmt, itile1=itile1, itile2=itile2)
    else:
        geo = cache[2]
    if cache is not None: cache[2] = geo
    # Determine tile wrapping
    npix_phi = np.abs(360. / geo.wcs.wcs.cdelt[0])
    ntile_phi = utils.nint(npix_phi / geo.tshape[-1])

    isize = geo.tshape
    osize = opix[1] - opix[0]
    omap = enmap.zeros(geo.shape[:-2] + tuple(osize), geo.wcs, geo.dtype)
    # Find out which input tiles overlap with this output tile.
    # Our tile stretches from opix1:opix2 relative to the global input pixels
    it1 = opix[0] // isize
    it2 = (opix[1] - 1) // isize + 1
    noverlap = 0
    for ity in range(it1[0], it2[0]):
        if ity < itile1[0] or ity >= itile2[0]: continue
        # Start/end of this tile in global input pixels
        ipy1, ipy2 = ity * isize[0], (ity + 1) * isize[0]
        overlap = range_overlap(opix[:, 0], [ipy1, ipy2])
        oy1, oy2 = overlap - opix[0, 0]
        iy1, iy2 = overlap - ipy1
        for itx in range(it1[1], it2[1]):
            if wrap: itx_wrap = itx % ntile_phi
            if itx_wrap < itile1[1] or itx_wrap >= itile2[1]: continue
            ipx1, ipx2 = itx * isize[1], (itx + 1) * isize[1]
            overlap = range_overlap(opix[:, 1], [ipx1, ipx2])
            ox1, ox2 = overlap - opix[0, 1]
            ix1, ix2 = overlap - ipx1
            # Read the input tile and copy over
            iname = ipathfmt % {"y": ity, "x": itx_wrap}
            if cache is None or cache[0] != iname:
                imap = enmap.read_map(iname)
                if slice: imap = eval("imap" + slice)
            else: imap = cache[1]
            if cache is not None:
                cache[0], cache[1] = iname, imap
            if verbose: print(iname)
            # Edge input tiles may be smaller than the standard
            # size.
            ysub = isize[0] - imap.shape[-2]
            xsub = isize[1] - imap.shape[-1]
            # If the input map is too small, there may actually be
            # zero overlap.
            if oy2 - ysub <= oy1 or ox2 - xsub <= ox1: continue
            omap[..., oy1:oy2 - ysub,
                 ox1:ox2 - xsub] = imap[..., iy1:iy2 - ysub, ix1:ix2 - xsub]
            noverlap += 1
    if noverlap == 0:
        raise IOError("No tiles for tiling %s in range %s" %
                      (ipathfmt, ",".join(
                          [":".join([str(p) for p in r]) for r in opix.T])))
    # Set up the wcs for the output tile
    omap.wcs.wcs.crpix -= opix[0, ::-1]
    return omap
Exemple #3
0
    def build(self, tod, srate, **kwargs):
        # Apply window before measuring noise model
        nwin = utils.nint(self.window / srate)
        apply_window(tod, nwin)
        ft = fft.rfft(tod)
        # Unapply window again
        apply_window(tod, nwin, -1)
        ndet, nfreq = ft.shape
        nsamp = tod.shape[1]
        # First build our set of eigenvectors in two bins. The first goes from
        # 0.25 to 4 Hz the second from 4Hz and up
        mode_bins = makebins(self.mode_bins, srate, nfreq, 1000,
                             rfun=np.round)[1:]
        # Then use these to get our set of basis vectors
        vecs = find_modes_jon(ft,
                              mode_bins,
                              eig_lim=self.eig_lim,
                              single_lim=self.single_lim,
                              verbose=self.verbose)
        nmode = vecs.shape[1]
        if vecs.size == 0:
            raise errors.ModelError("Could not find any noise modes")
        # Cut bins that extend beyond our max frequency
        bin_edges = self.bin_edges[self.bin_edges < srate / 2 * 0.99]
        bins = makebins(bin_edges, srate, nfreq, nmin=2 * nmode, rfun=np.round)
        nbin = len(bins)
        # Now measure the power of each basis vector in each bin. The residual
        # noise will be modeled as uncorrelated
        E = np.zeros([nbin, nmode])
        D = np.zeros([nbin, ndet])
        Nd = np.zeros([nbin, ndet])
        for bi, b in enumerate(bins):
            # Skip the DC mode, since it's it's unmeasurable and filtered away
            b = np.maximum(1, b)
            E[bi], D[bi], Nd[bi] = measure_detvecs(ft[:, b[0]:b[1]], vecs)
        # Optionally downweight the lowest frequency bins
        if self.downweight != None and len(self.downweight) > 0:
            D[:len(self.downweight)] /= np.array(self.downweight)[:, None]
        # Instead of VEV' we can have just VV' if we bake sqrt(E) into V
        V = vecs[None] * E[:, None]**0.5
        # At this point we have a model for the total noise covariance as
        # N = D + VV'. But since we're doing inverse covariance weighting
        # we need a similar representation for the inverse iN. The function
        # woodbury_invert computes iD, iV, s such that iN = iD + s iV iV'
        # where s usually is -1, but will become +1 if one inverts again
        iD, iV, s = woodbury_invert(D, V)
        # Also compute a representative white noise level
        bsize = bins[:, 1] - bins[:, 0]
        ivar = np.sum(iD * bsize[:, None], 0) / np.sum(bsize)
        # What about units? I haven't applied any fourier unit factors so far,
        # so we're in plain power units. From the uncorrelated model I found
        # that factor of tod.shape[1] is needed
        iD *= nsamp
        iV *= nsamp**0.5
        ivar *= nsamp

        # Fix dtype
        bins = np.ascontiguousarray(bins.astype(np.int32))
        D = np.ascontiguousarray(iD.astype(tod.dtype))
        V = np.ascontiguousarray(iV.astype(tod.dtype))
        iD = np.ascontiguousarray(D.astype(tod.dtype))
        iV = np.ascontiguousarray(V.astype(tod.dtype))

        return NmatDetvecs(bin_edges=self.bin_edges,
                           eig_lim=self.eig_lim,
                           single_lim=self.single_lim,
                           window=self.window,
                           nwin=nwin,
                           downweight=self.downweight,
                           verbose=self.verbose,
                           bins=bins,
                           D=D,
                           V=V,
                           iD=iD,
                           iV=iV,
                           s=s,
                           ivar=ivar)
Exemple #4
0
def retile(ipathfmt,
           opathfmt,
           itile1=(None, None),
           itile2=(None, None),
           otileoff=(0, 0),
           otilenum=(None, None),
           ocorner=(-np.pi / 2, -np.pi),
           otilesize=(675, 675),
           comm=None,
           verbose=False,
           slice=None,
           wrap=True):
    """Given a set of tiles on disk with locations ipathfmt % {"y":...,"x":...},
    retile them into a new tiling and write the result to opathfmt % {"y":...,"x":...}.
    The new tiling will have tile size given by otilesize[2]. Negative size means the
    tiling will to down/left instead of up/right. The corner of the tiling will
    be at sky coordinates ocorner[2] in radians. The new tiling will be pixel-
    compatible with the input tiling - w.g. the wcs will only differ by crpix.

    The output tiling will logically cover the whole sky, but only output tiles
    that overlap with input tiles will actually be written. This can be modified
    by using otileoff[2] and otilenum[2]. otileoff gives the tile indices of the
    corner tile, while otilenum indicates the number of tiles to write."""
    # Set up mpi
    rank, size = (comm.rank, comm.size) if comm is not None else (0, 1)
    # Expand any scalars
    if otilesize is None: otilesize = (675, 675)
    otilesize = np.zeros(2, int) + otilesize
    otileoff = np.zeros(2, int) + otileoff
    # Find the range of input tiles
    itile1, itile2 = find_tile_range(ipathfmt, itile1, itile2)
    # To fill in the rest of the information we need to know more
    # about the input tiling, so read the first tile
    ibase = enmap.read_map(ipathfmt % {"y": itile1[0], "x": itile1[1]})
    if slice: ibase = eval("ibase" + slice)
    itilesize = ibase.shape[-2:]
    ixres = ibase.wcs.wcs.cdelt[0]
    nphi = utils.nint(360 / np.abs(ixres))
    ntile_wrap = nphi // otilesize[1]
    # Find the pixel position of our output corners according to the wcs.
    # This is the last place we need to do a coordinate transformation.
    # All the rest can be done in pure pixel logic.
    pixoff = np.round(ibase.sky2pix(ocorner)).astype(int)

    # Find the range of output tiles
    def pix2otile(pix, ioff, osize):
        return (pix - ioff) // osize

    otile1 = pix2otile(itile1 * itilesize, pixoff, otilesize)
    otile2 = pix2otile(itile2 * itilesize - 1, pixoff, otilesize)
    otile1, otile2 = np.minimum(otile1, otile2), np.maximum(otile1, otile2)
    otile2 += 1
    # We can now loop over output tiles
    cache = [None, None, None]
    oyx = [(oy, ox) for oy in range(otile1[0], otile2[0])
           for ox in range(otile1[1], otile2[1])]
    for i in range(rank, len(oyx), size):
        otile = np.array(oyx[i])
        # Find out which input tiles overlap with this output tile.
        # Our tile stretches from opix1:opix2 relative to the global input pixels
        opix1 = otile * otilesize + pixoff
        opix2 = (otile + 1) * otilesize + pixoff
        # output tiles and input tiles may increase in opposite directions
        opix1, opix2 = np.minimum(opix1, opix2), np.maximum(opix1, opix2)
        try:
            omap = read_area(ipathfmt, [opix1, opix2],
                             itile1=itile1,
                             itile2=itile2,
                             cache=cache,
                             slice=slice)
        except (IOError, OSError):
            continue
        x = otile[1] + otileoff[1]
        if wrap: x %= ntile_wrap
        oname = opathfmt % {"y": otile[0] + otileoff[0], "x": x}
        utils.mkdir(os.path.dirname(oname))
        enmap.write_map(oname, omap)
        if verbose: print(oname)
Exemple #5
0
def sim_srcs(shape,
             wcs,
             srcs,
             beam,
             omap=None,
             dtype=None,
             nsigma=5,
             rmax=None,
             smul=1,
             return_padded=False,
             pixwin=False,
             op=np.add,
             wrap="auto",
             verbose=False,
             cache=None):
    """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. smul gives a factor to multiply the resulting
	source model by. This is mostly useful in conction with omap.
	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]
    # Set up wrapping
    if wrap is "auto": wrap = [0, utils.nint(360. / wcs.wcs.cdelt[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
    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
    minshape = np.min(omap[..., 5:-5:10, 5:-5:10].pixshapemap() / 10, (-2, -1))
    cres = np.maximum(1, utils.nint(rmax / minshape))
    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, wrap=wrap)
    # Optionally cache the posmap
    if cache is None or cache[0] is None: posmap = wmap.posmap()
    else: posmap = cache[0]
    if cache is not None: cache[0] = posmap
    model = eval_srcs_loop(posmap,
                           poss,
                           amps,
                           beam,
                           cres,
                           nhit,
                           cell_srcs,
                           dtype=wmap.dtype,
                           op=op,
                           verbose=verbose)
    del posmap
    if pixwin: model = enmap.apply_window(model)
    # Update our work map, through our view
    if smul != 1: model *= smul
    wmap = op(wmap, model, wmap)
    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