Example #1
0
    def getPointSourcePatch(self,
                            px,
                            py,
                            minval=0.,
                            modelMask=None,
                            radius=None,
                            **kwargs):
        from astrometry.util.miscutils import get_overlapping_region

        img = self.getImage(px, py)
        assert (np.all(np.isfinite(img)))

        if radius is not None:
            R = int(np.ceil(radius))
            H, W = img.shape
            cx = W // 2
            cy = H // 2
            img = img[max(cy - R, 0):min(cy + R + 1, H - 1),
                      max(cx - R, 0):min(cx + R + 1, W - 1)]

        H, W = img.shape
        ix = int(np.round(px))
        iy = int(np.round(py))
        dx = px - ix
        dy = py - iy
        x0 = ix - W // 2
        y0 = iy - H // 2

        if modelMask is None:
            outimg = lanczos_shift_image(img, dx, dy)
            return Patch(x0, y0, outimg)

        mh, mw = modelMask.shape
        mx0, my0 = modelMask.x0, modelMask.y0

        # print 'PixelizedPSF + modelMask'
        # print 'mx0,my0', mx0,my0, '+ mw,mh', mw,mh
        # print 'PSF image x0,y0', x0,y0, '+ W,H', W,H

        if ((mx0 >= x0 + W) or (mx0 + mw <= x0) or (my0 >= y0 + H)
                or (my0 + mh <= y0)):
            # No overlap
            return None
        # Otherwise, we'll just produce the Lanczos-shifted PSF
        # image as usual, and then copy it into the modelMask
        # space.
        L = 3
        padding = L
        # Create a modelMask + padding sized stamp and insert PSF image into it
        mm = np.zeros((mh + 2 * padding, mw + 2 * padding), np.float32)
        yi, yo = get_overlapping_region(my0 - y0 - padding,
                                        my0 - y0 + mh - 1 + padding, 0, H - 1)
        xi, xo = get_overlapping_region(mx0 - x0 - padding,
                                        mx0 - x0 + mw - 1 + padding, 0, W - 1)
        mm[yo, xo] = img[yi, xi]
        mm = lanczos_shift_image(mm, dx, dy)
        mm = mm[padding:-padding, padding:-padding]
        assert (np.all(np.isfinite(mm)))

        return Patch(mx0, my0, mm)
Example #2
0
 def addTo(self, img, scale=1.):
     if self.patch is None:
         return
     (ih,iw) = img.shape
     (ph,pw) = self.shape
     (outx, inx) = get_overlapping_region(self.x0, self.x0+pw-1, 0, iw-1)
     (outy, iny) = get_overlapping_region(self.y0, self.y0+ph-1, 0, ih-1)
     if inx == [] or iny == []:
         return
     p = self.patch[iny,inx]
     img[outy, outx] += p * scale
Example #3
0
 def addTo(self, img, scale=1.):
     if self.patch is None:
         return
     (ih, iw) = img.shape
     (ph, pw) = self.shape
     (outx, inx) = get_overlapping_region(self.x0, self.x0 + pw - 1, 0,
                                          iw - 1)
     (outy, iny) = get_overlapping_region(self.y0, self.y0 + ph - 1, 0,
                                          ih - 1)
     if inx == [] or iny == []:
         return
     p = self.patch[iny, inx]
     img[outy, outx] += p * scale
Example #4
0
    def getSlices(self, shape):
        '''
        shape = (H,W).

        Returns (spatch, sparent), slices that yield the overlapping regions
        in this Patch and the given image.
        '''
        (ph,pw) = self.shape
        (ih,iw) = shape
        (outx, inx) = get_overlapping_region(self.x0, self.x0+pw-1, 0, iw-1)
        (outy, iny) = get_overlapping_region(self.y0, self.y0+ph-1, 0, ih-1)
        if inx == [] or iny == []:
            return (slice(0,0),slice(0,0)), (slice(0,0),slice(0,0))
        return (iny,inx), (outy,outx)
Example #5
0
 def hasNonzeroOverlapWith(self, other):
     if not self.hasBboxOverlapWith(other):
         return False
     ext = self.getExtent()
     (x0, x1, y0, y1) = ext
     oext = other.getExtent()
     (ox0, ox1, oy0, oy1) = oext
     ix, ox = get_overlapping_region(ox0, ox1 - 1, x0, x1 - 1)
     iy, oy = get_overlapping_region(oy0, oy1 - 1, y0, y1 - 1)
     ix = slice(ix.start - x0, ix.stop - x0)
     iy = slice(iy.start - y0, iy.stop - y0)
     sub = self.patch[iy, ix]
     osub = other.patch[oy, ox]
     assert (sub.shape == osub.shape)
     return np.sum(sub * osub) > 0.
Example #6
0
 def hasNonzeroOverlapWith(self, other):
     if not self.hasBboxOverlapWith(other):
         return False
     ext = self.getExtent()
     (x0,x1,y0,y1) = ext
     oext = other.getExtent()
     (ox0,ox1,oy0,oy1) = oext
     ix,ox = get_overlapping_region(ox0, ox1-1, x0, x1-1)
     iy,oy = get_overlapping_region(oy0, oy1-1, y0, y1-1)
     ix = slice(ix.start -  x0, ix.stop -  x0)
     iy = slice(iy.start -  y0, iy.stop -  y0)
     sub = self.patch[iy,ix]
     osub = other.patch[oy,ox]
     assert(sub.shape == osub.shape)
     return np.sum(sub * osub) > 0.
Example #7
0
    def getSlices(self, shape):
        '''
        shape = (H,W).

        Returns (spatch, sparent), slices that yield the overlapping regions
        in this Patch and the given image.
        '''
        (ph, pw) = self.shape
        (ih, iw) = shape
        (outx, inx) = get_overlapping_region(self.x0, self.x0 + pw - 1, 0,
                                             iw - 1)
        (outy, iny) = get_overlapping_region(self.y0, self.y0 + ph - 1, 0,
                                             ih - 1)
        if inx == [] or iny == []:
            return (slice(0, 0), slice(0, 0)), (slice(0, 0), slice(0, 0))
        return (iny, inx), (outy, outx)
Example #8
0
    def setMaskedPixels(self, name, img, val, roi=None):
        M = self.getMaskPlane(name)
        if M is None:
            return
        if roi is not None:
            x0,x1,y0,y1 = roi

        for (c0,c1,r0,r1,coff,roff) in zip(M.cmin,M.cmax,M.rmin,M.rmax,
                                           M.col0, M.row0):
            assert(coff == 0)
            assert(roff == 0)
            if roi is not None:
                (outx,nil) = get_overlapping_region(c0-x0, c1+1-x0, 0, x1-x0)
                (outy,nil) = get_overlapping_region(r0-y0, r1+1-y0, 0, y1-y0)
                img[outy,outx] = val
            else:
                img[r0:r1+1, c0:c1+1] = val
Example #9
0
    def setMaskedPixels(self, name, img, val, roi=None):
        M = self.getMaskPlane(name)
        if M is None:
            return
        if roi is not None:
            x0,x1,y0,y1 = roi

        for (c0,c1,r0,r1,coff,roff) in zip(M.cmin,M.cmax,M.rmin,M.rmax,
                                           M.col0, M.row0):
            assert(coff == 0)
            assert(roff == 0)
            if roi is not None:
                (outx,nil) = get_overlapping_region(c0-x0, c1+1-x0, 0, x1-x0)
                (outy,nil) = get_overlapping_region(r0-y0, r1+1-y0, 0, y1-y0)
                img[outy,outx] = val
            else:
                img[r0:r1+1, c0:c1+1] = val
Example #10
0
    def _realGetUnitFluxModelPatch(self, img, px, py, minval, modelMask=None):
        if modelMask is not None:
            x0, y0 = modelMask.x0, modelMask.y0
        else:
            # choose the patch size
            halfsize = self._getUnitFluxPatchSize(img,
                                                  px=px,
                                                  py=py,
                                                  minval=minval)
            # find overlapping pixels to render
            (outx,
             inx) = get_overlapping_region(int(np.floor(px - halfsize)),
                                           int(np.ceil(px + halfsize + 1)), 0,
                                           img.getWidth())
            (outy,
             iny) = get_overlapping_region(int(np.floor(py - halfsize)),
                                           int(np.ceil(py + halfsize + 1)), 0,
                                           img.getHeight())
            if inx == [] or iny == []:
                # no overlap
                return None
            x0, x1 = outx.start, outx.stop
            y0, y1 = outy.start, outy.stop

        psf = img.getPsf()

        # We have two methods of rendering profile galaxies: If the
        # PSF can be represented as a mixture of Gaussians, then we do
        # the analytic Gaussian convolution, producing a larger
        # mixture of Gaussians, and we render that.  Otherwise
        # (pixelized PSFs), we FFT the PSF, multiply by the analytic
        # FFT of the galaxy, and IFFT back to get the rendered
        # profile.

        # The "HybridPSF" class is just a marker to indicate whether this
        # code should treat the PSF as a hybrid.
        from tractor.psf import HybridPSF
        hybrid = isinstance(psf, HybridPSF)

        def run_mog(amix=None, mm=None):
            ''' This runs the mixture-of-Gaussians convolution method.
            '''
            if amix is None:
                amix = self._getAffineProfile(img, px, py)
            if mm is None:
                mm = modelMask
            # now convolve with the PSF, analytically
            # (note that the psf's center is *not* set to px,py; that's just
            #  the position to use for spatially-varying PSFs)
            psfmix = psf.getMixtureOfGaussians(px=px, py=py)
            cmix = amix.convolve(psfmix)
            if mm is None:
                #print('Mixture to patch: amix', amix, 'psfmix', psfmix, 'cmix', cmix)
                return mp.mixture_to_patch(cmix, x0, x1, y0, y1, minval)
            # The convolved mixture *already* has the px,py offset added
            # (via px,py to amix) so set px,py=0,0 in this call.
            if mm.mask is not None:
                p = cmix.evaluate_grid_masked(mm.x0, mm.y0, mm.mask, 0., 0.)
            else:
                p = cmix.evaluate_grid(mm.x0, mm.x1, mm.y0, mm.y1, 0., 0.)
            assert (p.shape == mm.shape)
            return p

        if hasattr(psf, 'getMixtureOfGaussians') and not hybrid:
            return run_mog(mm=modelMask)

        # Otherwise, FFT:
        imh, imw = img.shape
        if modelMask is None:
            # Avoid huge galaxies -> huge halfsize in a tiny image (blob)
            imsz = max(imh, imw)
            halfsize = min(halfsize, imsz)
            # FIXME -- should take some kind of combination of
            # modelMask, PSF, and Galaxy sizes!

        else:
            # ModelMask sets the sizes.
            mh, mw = modelMask.shape
            x1 = x0 + mw
            y1 = y0 + mh

            halfsize = max(mh / 2., mw / 2.)
            # How far from the source center to furthest modelMask edge?
            # FIXME -- add 1 for Lanczos margin?
            halfsize = max(
                halfsize,
                max(max(1 + px - x0, 1 + x1 - px), max(1 + py - y0,
                                                       1 + y1 - py)))
            psfh, psfw = psf.shape
            halfsize = max(halfsize, max(psfw / 2., psfh / 2.))
            #print('Halfsize:', halfsize)

            # is the source center outside the modelMask?
            sourceOut = (px < x0 or px > x1 - 1 or py < y0 or py > y1 - 1)
            # print('mh,mw', mh,mw, 'sourceout?', sourceOut)

            if sourceOut:
                if hybrid:
                    return run_mog(mm=modelMask)

                # Super Yuck -- FFT, modelMask, source is outside the
                # box.
                neardx, neardy = 0., 0.
                if px < x0:
                    neardx = x0 - px
                if px > x1:
                    neardx = px - x1
                if py < y0:
                    neardy = y0 - py
                if py > y1:
                    neardy = py - y1
                nearest = np.hypot(neardx, neardy)
                #print('Nearest corner:', nearest, 'vs radius', self.getRadius())
                if nearest > self.getRadius():
                    return None
                # how far is the furthest point from the source center?
                farw = max(abs(x0 - px), abs(x1 - px))
                farh = max(abs(y0 - py), abs(y1 - py))
                bigx0 = int(np.floor(px - farw))
                bigx1 = int(np.ceil(px + farw))
                bigy0 = int(np.floor(py - farh))
                bigy1 = int(np.ceil(py + farh))
                bigw = 1 + bigx1 - bigx0
                bigh = 1 + bigy1 - bigy0
                boffx = x0 - bigx0
                boffy = y0 - bigy0
                assert (bigw >= mw)
                assert (bigh >= mh)
                assert (boffx >= 0)
                assert (boffy >= 0)
                bigMask = np.zeros((bigh, bigw), bool)
                if modelMask.mask is not None:
                    bigMask[boffy:boffy + mh,
                            boffx:boffx + mw] = modelMask.mask
                else:
                    bigMask[boffy:boffy + mh, boffx:boffx + mw] = True
                bigMask = ModelMask(bigx0, bigy0, bigMask)
                # print('Recursing:', self, ':', (mh,mw), 'to', (bigh,bigw))
                bigmodel = self._realGetUnitFluxModelPatch(img,
                                                           px,
                                                           py,
                                                           minval,
                                                           modelMask=bigMask)
                return Patch(
                    x0, y0, bigmodel.patch[boffy:boffy + mh, boffx:boffx + mw])

        # print('Getting Fourier transform of PSF at', px,py)
        # print('Tim shape:', img.shape)
        P, (cx, cy), (pH, pW), (v,
                                w) = psf.getFourierTransform(px, py, halfsize)

        dx = px - cx
        dy = py - cy
        if modelMask is not None:
            # the Patch we return *must* have this origin.
            ix0 = x0
            iy0 = y0
            # the difference that we have to handle by shifting the model image
            mux = dx - ix0
            muy = dy - iy0
            # we will handle the integer portion by computing a shifted image
            # and copying it into the result
            sx = int(np.round(mux))
            sy = int(np.round(muy))
            # the subpixel portion will be handled with a Lanczos interpolation
            mux -= sx
            muy -= sy
        else:
            # Put the integer portion of the offset into Patch x0,y0
            ix0 = int(np.round(dx))
            iy0 = int(np.round(dy))
            # the subpixel portion will be handled with a Lanczos interpolation
            mux = dx - ix0
            muy = dy - iy0

        # At this point, mux,muy are both in [-0.5, 0.5]
        assert (np.abs(mux) <= 0.5)
        assert (np.abs(muy) <= 0.5)

        amix = self._getShearedProfile(img, px, py)
        fftmix = amix
        mogmix = None

        #print('Galaxy affine profile:', amix)

        if hybrid:
            # Split "amix" into terms that we will evaluate using MoG
            # vs FFT.
            vv = amix.var[:, 0, 0] + amix.var[:, 1, 1]
            nsigma = 3.
            # Terms that will wrap-around significantly...
            I = ((vv * nsigma**2) > (pW**2))
            if np.any(I):
                # print('Evaluating', np.sum(I), 'terms as MoGs')
                mogmix = mp.MixtureOfGaussians(
                    amix.amp[I],
                    amix.mean[I, :] + np.array([px, py])[np.newaxis, :],
                    amix.var[I, :, :],
                    quick=True)
            I = np.logical_not(I)
            if np.any(I):
                # print('Evaluating', np.sum(I), 'terms with FFT')
                fftmix = mp.MixtureOfGaussians(amix.amp[I],
                                               amix.mean[I, :],
                                               amix.var[I, :, :],
                                               quick=True)
            else:
                fftmix = None

        if fftmix is not None:
            Fsum = fftmix.getFourierTransform(v, w, zero_mean=True)
            # In Intel's mkl_fft library, the irfftn code path is faster than irfft2
            # (the irfft2 version sets args (to their default values) which triggers padding
            #  behavior, changing the FFT size and copying behavior)
            #G = np.fft.irfft2(Fsum * P, s=(pH, pW))
            G = np.fft.irfftn(Fsum * P)

            assert (G.shape == (pH, pW))
            # FIXME -- we could try to be sneaky and Lanczos-interp
            # after cutting G down to nearly its final size... tricky
            # tho

            # Lanczos-3 interpolation in ~the same way we do for
            # pixelized PSFs.
            from tractor.psf import lanczos_shift_image
            G = G.astype(np.float32)
            lanczos_shift_image(G, mux, muy, inplace=True)
        else:
            G = np.zeros((pH, pW), np.float32)

        if modelMask is not None:
            gh, gw = G.shape
            if sx != 0 or sy != 0:
                yi, yo = get_overlapping_region(-sy, -sy + mh - 1, 0, gh - 1)
                xi, xo = get_overlapping_region(-sx, -sx + mw - 1, 0, gw - 1)
                # shifted
                # FIXME -- are yo,xo always the whole image?  If so, optimize
                shG = np.zeros((mh, mw), G.dtype)
                shG[yo, xo] = G[yi, xi]

                if debug_ps is not None:
                    _fourier_galaxy_debug_plots(G, shG, xi, yi, xo, yo, P,
                                                Fsum, pW, pH, psf)

                G = shG
            if gh > mh or gw > mw:
                G = G[:mh, :mw]
            assert (G.shape == modelMask.shape)

        else:
            # Clip down to suggested "halfsize"
            if x0 > ix0:
                G = G[:, x0 - ix0:]
                ix0 = x0
            if y0 > iy0:
                G = G[y0 - iy0:, :]
                iy0 = y0
            gh, gw = G.shape
            if gw + ix0 > x1:
                G = G[:, :x1 - ix0]
            if gh + iy0 > y1:
                G = G[:y1 - iy0, :]

        if mogmix is not None:
            if modelMask is not None:
                mogpatch = run_mog(amix=mogmix, mm=modelMask)
            else:
                gh, gw = G.shape
                mogpatch = run_mog(amix=mogmix, mm=ModelMask(ix0, iy0, gw, gh))
            assert (mogpatch.patch.shape == G.shape)
            G += mogpatch.patch

        return Patch(ix0, iy0, G)
Example #11
0
def read_outlier_mask_file(survey,
                           tims,
                           brickname,
                           subimage=True,
                           output=True,
                           ps=None,
                           outlier_mask_file=None,
                           apply_masks=True):
    '''if subimage=True, assume that 'tims' are subimages, and demand that they have the same
    x0,y0 pixel offsets and size as the outlier mask files.

    if subimage=False, assume that 'tims' are full-CCD images, and
    apply the mask to the relevant subimage.

    *output* determines where we search for the file: treating it as output, or input?
    '''
    from legacypipe.bits import DQ_BITS
    if outlier_mask_file is None:
        fn = survey.find_file('outliers_mask', brick=brickname, output=output)
    else:
        fn = outlier_mask_file
    if not os.path.exists(fn):
        print('Failed to apply outlier mask: No such file:', fn)
        return False
    F = fitsio.FITS(fn)
    for tim in tims:
        extname = '%s-%s-%s' % (tim.imobj.camera, tim.imobj.expnum,
                                tim.imobj.ccdname)
        if not extname in F:
            print('WARNING: Did not find extension', extname,
                  'in outlier-mask file', fn)
            return False
        mask = F[extname].read()
        hdr = F[extname].read_header()
        if subimage:
            if mask.shape != tim.shape:
                print('Warning: Outlier mask', fn,
                      'does not match shape of tim', tim)
                return False
        x0 = hdr['X0']
        y0 = hdr['Y0']
        maskbits = get_bits_to_mask()
        if subimage:
            if x0 != tim.x0 or y0 != tim.y0:
                print('Warning: Outlier mask', fn,
                      'x0,y0 does not match that of tim', tim)
                return False
            if apply_masks:
                # Apply this mask!
                tim.dq |= ((mask & maskbits) > 0) * DQ_BITS['outlier']
                tim.inverr[(mask & maskbits) > 0] = 0.
        else:
            from astrometry.util.miscutils import get_overlapping_region
            mh, mw = mask.shape
            th, tw = tim.shape
            my, ty = get_overlapping_region(tim.y0, tim.y0 + th - 1, y0,
                                            y0 + mh - 1)
            mx, tx = get_overlapping_region(tim.x0, tim.x0 + tw - 1, x0,
                                            x0 + mw - 1)
            # have to shift the "m" slices down by x0,y0
            my = slice(my.start - y0, my.stop - y0)
            mx = slice(mx.start - x0, mx.stop - x0)
            if apply_masks:
                # Apply this mask!
                tim.dq[ty, tx] |= (
                    (mask[my, mx] & maskbits) > 0) * DQ_BITS['outlier']
                tim.inverr[ty, tx][(mask[my, mx] & maskbits) > 0] = 0.

            if ps is not None:
                import pylab as plt
                print('Mask extent: x [%i, %i], vs tim extent x [%i, %i]' %
                      (x0, x0 + mw, tim.x0, tim.x0 + tw))
                print('Mask extent: y [%i, %i], vs tim extent y [%i, %i]' %
                      (y0, y0 + mh, tim.y0, tim.y0 + th))
                print('x slice: mask', mx, 'tim', tx)
                print('y slice: mask', my, 'tim', ty)
                print('tim shape:', tim.shape)
                print('mask shape:', mask.shape)
                newdq = np.zeros(tim.shape, bool)
                newdq[ty, tx] = ((mask[my, mx] & maskbits) > 0)
                print('Total of', np.sum(newdq), 'pixels masked')
                plt.clf()
                plt.imshow(tim.getImage(),
                           interpolation='nearest',
                           origin='lower',
                           vmin=-2. * tim.sig1,
                           vmax=5. * tim.sig1,
                           cmap='gray')
                ax = plt.axis()
                from legacypipe.detection import plot_boundary_map
                plot_boundary_map(newdq, iterations=3, rgb=(0, 128, 255))
                plt.axis(ax)
                ps.savefig()
                plt.axis([tx.start, tx.stop, ty.start, ty.stop])
                ps.savefig()

    return True
Example #12
0
    def _realGetUnitFluxModelPatch(self, img, px, py, minval, extent=None,
                                   modelMask=None):
        '''
        extent: if not None, [x0,x1,y0,y1], where the range to render
        is [x0, x1), [y0,y1).
        '''

        #####
        global psfft

        if do_fft_timing:
            global fft_timing
            global fft_timing_id
            fft_timing_id += 1
            timing_id = fft_timing_id
            tpatch = CpuMeas()
            fft_timing.append((timing_id, 'get_unit_patch', 0,
                               (self,)))
        
        if modelMask is None:
            # now choose the patch size
            halfsize = self._getUnitFluxPatchSize(img, px, py, minval)

        if modelMask is not None:
            x0,y0 = modelMask.x0, modelMask.y0
        elif extent is None:
            # find overlapping pixels to render
            (outx, inx) = get_overlapping_region(
                int(np.floor(px-halfsize)), int(np.ceil(px+halfsize+1)),
                0, img.getWidth())
            (outy, iny) = get_overlapping_region(
                int(np.floor(py-halfsize)), int(np.ceil(py+halfsize+1)),
                0, img.getHeight())
            if inx == [] or iny == []:
                # no overlap

                if do_fft_timing:
                    fft_timing.append((timing_id, 'no_overlap', CpuMeas().cpu_seconds_since(tpatch)))

                return None
            x0,x1 = outx.start, outx.stop
            y0,y1 = outy.start, outy.stop
        else:
            x0,x1,y0,y1 = extent
        psf = img.getPsf()

        # We have two methods of rendering profile galaxies: If the
        # PSF can be represented as a mixture of Gaussians, then we do
        # the analytic Gaussian convolution, producing a larger
        # mixture of Gaussians, and we render that.  Otherwise
        # (pixelized PSFs), we FFT the PSF, multiply by the analytic
        # FFT of the galaxy, and IFFT back to get the rendered
        # profile.

        if hasattr(psf, 'getMixtureOfGaussians'):
            amix = self._getAffineProfile(img, px, py)
            # now convolve with the PSF, analytically
            psfmix = psf.getMixtureOfGaussians(px=px, py=py)
            cmix = amix.convolve(psfmix)

            # print('galaxy affine mixture:', amix)
            # print('psf mixture:', psfmix)
            # print('convolved mixture:', cmix)
            # print('_realGetUnitFluxModelPatch: extent', x0,x1,y0,y1)
            if modelMask is None:
                return mp.mixture_to_patch(cmix, x0, x1, y0, y1, minval,
                                           exactExtent=(extent is not None))
            else:
                # The convolved mixture *already* has the px,py offset added
                # (via px,py to amix) so set px,py=0,0 in this call.
                p = cmix.evaluate_grid_masked(x0, y0, modelMask.patch, 0., 0.)
                assert(p.shape == modelMask.shape)
                return p


        # Otherwise, FFT:
        imh,imw = img.shape

        haveExtent = (modelMask is not None) or (extent is not None)

        if not haveExtent:
            halfsize = self._getUnitFluxPatchSize(img, px, py, minval)

            # Avoid huge galaxies -> huge halfsize in a tiny image (blob)
            imsz = max(imh,imw)
            halfsize = min(halfsize, imsz)

        else:
            # FIXME -- max of modelMask, PSF, and Galaxy sizes!

            if modelMask is not None:
                mh,mw = modelMask.shape
                x1 = x0 + mw
                y1 = y0 + mh
            else:
                # x0,x1,y0,y1 were set to extent, above.
                mw = x1 - x0
                mh = y1 - y0

            # is the source center outside the modelMask?
            sourceOut = (px < x0 or px > x1-1 or py < y0 or py > y1-1)
            
            if sourceOut:
                # FIXME -- could also *think* about switching to a
                # Gaussian approximation when very far from the source
                # center...
                #
                #print('modelMask does not contain source center!  Fetching bigger model...')
                # If the source is *way* outside the patch, return zero.
                neardx,neardy = 0., 0.
                if px < x0:
                    neardx = x0 - px
                if px > x1:
                    neardx = px - x1
                if py < y0:
                    neardy = y0 - py
                if py > y1:
                    neardy = py - y1
                nearest = np.hypot(neardx, neardy)
                if nearest > self.getRadius():

                    if do_fft_timing:
                        fft_timing.append((timing_id, 'source_way_outside', CpuMeas().cpu_seconds_since(tpatch)))
                    return None

                # how far is the furthest point from the source center?
                farw = max(abs(x0 - px), abs(x1 - px))
                farh = max(abs(y0 - py), abs(y1 - py))
                bigx0 = int(np.floor(px - farw))
                bigx1 = int(np.ceil (px + farw))
                bigy0 = int(np.floor(py - farh))
                bigy1 = int(np.ceil (py + farh))
                bigw = 1 + bigx1 - bigx0
                bigh = 1 + bigy1 - bigy0
                boffx = x0 - bigx0
                boffy = y0 - bigy0
                assert(bigw >= mw)
                assert(bigh >= mh)
                assert(boffx >= 0)
                assert(boffy >= 0)
                if modelMask is None:
                    bigMask = np.ones((bigh,bigw), bool)
                else:
                    bigMask = np.zeros((bigh,bigw), bool)
                    bigMask[boffy:boffy+mh, boffx:boffx+mw] = modelMask.patch
                bigMask = Patch(bigx0, bigy0, bigMask)

                if do_fft_timing:
                    fft_timing.append((timing_id, 'calling_sourceout', None))
                    t0 = CpuMeas()

                # print('Recursing:', self, ':', (mh,mw), 'to', (bigh,bigw))
                bigmodel = self._realGetUnitFluxModelPatch(
                    img, px, py, minval, extent=None, modelMask=bigMask)

                if do_fft_timing:
                    t1 = CpuMeas()
                    fft_timing.append((timing_id, 'sourceout', t1.cpu_seconds_since(t0),
                                       (bigMask.shape, (mh,mw))))

                return Patch(x0, y0,
                             bigmodel.patch[boffy:boffy+mh, boffx:boffx+mw])
            
            halfsize = max(mh/2., mw/2.)

            psfh,psfw = psf.shape
            halfsize = max(halfsize, max(psfw/2., psfh/2.))

        if do_fft_timing:
            t0 = CpuMeas()

        P,(cx,cy),(pH,pW),(v,w) = psf.getFourierTransform(px, py, halfsize)

        #print('Computing', self, ': halfsize=', halfsize, 'FFT', (pH,pW))
        
        if do_fft_timing:
            t1 = CpuMeas()
            fft_timing.append((timing_id, 'psf_fft', t1.cpu_seconds_since(t0),
                               (haveExtent, halfsize)))

        # print('PSF Fourier transform size:', P.shape)
        # print('Padded size:', pH,pW)
        # print('PSF center in padded image:', cx,cy)
        # print('Source center px,py', px,py)

        # One example:
        # pH,pW = (256, 256)
        # P.shape = (256, 129)
        # cx,cy = (127, 127)
        
        dx = px - cx
        dy = py - cy
        if haveExtent:
            # the Patch we return *must* have this origin.
            ix0 = x0
            iy0 = y0

            # Put the difference into the galaxy FFT.
            mux = dx - ix0
            muy = dy - iy0

            # ASSUME square PSF
            assert(pH == pW)
            psfh,psfw = psf.shape
            # How much padding on the PSF image?
            psfmargin = cx - psfw/2

            gx0 = gy0 = 0
            if abs(mux) >= psfmargin or abs(muy) >= psfmargin:
                # Wrap-around is possible (likely).  Compute a shifted image
                # and then copy it into the result.
                gx0 = int(np.round(mux))
                gy0 = int(np.round(muy))
                mux -= gx0
                muy -= gy0
                
        else:
            # Put the integer portion of the offset into Patch x0,y0
            ix0 = int(np.round(dx))
            iy0 = int(np.round(dy))

            # Put the subpixel portion into the galaxy FFT.
            mux = dx - ix0
            muy = dy - iy0

        if do_fft_timing:
            t0 = CpuMeas()
        
        amix = self._getAffineProfile(img, mux, muy)

        if do_fft_timing:
            t1 = CpuMeas()
        
        Fsum = amix.getFourierTransform(v, w)
        
        if do_fft_timing:
            t2 = CpuMeas()

            fft_timing.append((timing_id, 'get_affine', t1.cpu_seconds_since(t0),
                               (haveExtent,)))
            fft_timing.append((timing_id, 'get_ft', t2.cpu_seconds_since(t1),
                               (haveExtent,)))

        if False:
            # for fakedx in []:#0]:#, 1, 10]:

            amix2 = self._getAffineProfile(img, mux + fakedx, muy)
            Fsum2 = amix2.getFourierTransform(v, w)

            plt.clf()
            plt.subplot(3,3,1)
            plt.imshow(Fsum2.real, interpolation='nearest', origin='lower')
            plt.title('Galaxy FFT')
            plt.subplot(3,3,2)
            plt.imshow(P.real, interpolation='nearest', origin='lower')
            plt.title('PSF FFT')
            plt.subplot(3,3,3)
            plt.imshow((Fsum2 * P).real,
                       interpolation='nearest', origin='lower')
            plt.title('Galaxy * PSF FFT')
            plt.subplot(3,3,4)
            plt.imshow(Fsum2.imag, interpolation='nearest', origin='lower')
            plt.title('Galaxy FFT')
            plt.subplot(3,3,5)
            plt.imshow(P.imag, interpolation='nearest', origin='lower')
            plt.title('PSF FFT')
            plt.subplot(3,3,6)
            plt.imshow((Fsum2 * P).imag,
                       interpolation='nearest', origin='lower')
            plt.title('Galaxy * PSF FFT')

            plt.subplot(3,3,7)
            plt.imshow(np.fft.irfft2(Fsum2, s=(pH,pW)),
                       interpolation='nearest', origin='lower')
            plt.title('iFFT Galaxy')
            plt.subplot(3,3,8)
            plt.imshow(np.fft.irfft2(P, s=(pH,pW)),
                       interpolation='nearest', origin='lower')
            plt.title('iFFT PSF')
            plt.subplot(3,3,9)
            plt.imshow(np.fft.irfft2(Fsum2*P, s=(pH,pW)),
                       interpolation='nearest', origin='lower')
            plt.title('iFFT Galaxy*PSF')
            
            plt.suptitle('dx = %i pixel' % fakedx)
            psfft.savefig()

        
        
        if haveExtent:

            if False:
                plt.clf()
                plt.imshow(np.fft.irfft2(Fsum * P, s=(pH,pW)),
                           interpolation='nearest', origin='lower')
                plt.title('iFFT in PSF shape')
                psfft.savefig()

            if do_fft_timing:
                t0 = CpuMeas()

            G = np.fft.irfft2(Fsum * P, s=(pH,pW))

            if do_fft_timing:
                t1 = CpuMeas()
                fft_timing.append((timing_id, 'irfft2', t1.cpu_seconds_since(t0),
                                   (haveExtent, (pH,pW))))
            

            gh,gw = G.shape

            if gx0 != 0 or gy0 != 0:
                #print('gx0,gy0', gx0,gy0)
                yi,yo = get_overlapping_region(-gy0, -gy0+mh-1, 0, gh-1)
                xi,xo = get_overlapping_region(-gx0, -gx0+mw-1, 0, gw-1)

                # shifted
                shG = np.zeros((mh,mw), G.dtype)
                shG[yo,xo] = G[yi,xi]
                G = shG
            
            if gh > mh or gw > mw:
                G = G[:mh,:mw]
            if modelMask is not None:
                assert(G.shape == modelMask.shape)
            else:
                assert(G.shape == (mh,mw))
            
        else:
            #print('iFFT', (pW,pH))

            # psfim = np.fft.irfft2(P)
            # print('psf iFFT', psfim.shape, psfim.sum())
            # psfim /= psfim.sum()
            # xx,yy = np.meshgrid(np.arange(pW), np.arange(pH))
            # print('centroid', np.sum(psfim*xx), np.sum(psfim*yy))

            if do_fft_timing:
                t0 = CpuMeas()

            G = np.fft.irfft2(Fsum * P, s=(pH,pW))

            if do_fft_timing:
                t1 = CpuMeas()
                fft_timing.append((timing_id, 'irfft2_b', t1.cpu_seconds_since(t0),
                                   (haveExtent, (pH,pW))))

            # print('Evaluating iFFT with shape', pH,pW)
            # print('G shape:', G.shape)

            if False:
                plt.clf()
                plt.imshow(G, interpolation='nearest', origin='lower')
                plt.title('iFFT in padded PSF shape')
                psfft.savefig()
            
            # Clip down to suggested "halfsize"
            if x0 > ix0:
                G = G[:,x0 - ix0:]
                ix0 = x0
            if y0 > iy0:
                G = G[y0 - iy0:, :]
                iy0 = y0
            gh,gw = G.shape
            if gw+ix0 > x1:
                G = G[:,:x1-ix0]
            if gh+iy0 > y1:
                G = G[:y1-iy0,:]

        if do_fft_timing:
            fft_timing.append((timing_id, 'get_unit_patch_finished', CpuMeas().cpu_seconds_since(tpatch),
                               (self,)))

        return Patch(ix0, iy0, G)
Example #13
0
    def _realGetUnitFluxModelPatch(self, img, px, py, minval, extent=None,
                                   modelMask=None):
        '''
        extent: if not None, [x0,x1,y0,y1], where the range to render
        is [x0, x1), [y0,y1).
        '''

        #####
        global psfft

        if do_fft_timing:
            global fft_timing
            global fft_timing_id
            fft_timing_id += 1
            timing_id = fft_timing_id
            tpatch = CpuMeas()
            fft_timing.append((timing_id, 'get_unit_patch', 0,
                               (self,)))
        
        if modelMask is None:
            # now choose the patch size
            halfsize = self._getUnitFluxPatchSize(img, px, py, minval)

        if modelMask is not None:
            x0,y0 = modelMask.x0, modelMask.y0
        elif extent is None:
            # find overlapping pixels to render
            (outx, inx) = get_overlapping_region(
                int(np.floor(px-halfsize)), int(np.ceil(px+halfsize+1)),
                0, img.getWidth())
            (outy, iny) = get_overlapping_region(
                int(np.floor(py-halfsize)), int(np.ceil(py+halfsize+1)),
                0, img.getHeight())
            if inx == [] or iny == []:
                # no overlap

                if do_fft_timing:
                    fft_timing.append((timing_id, 'no_overlap', CpuMeas().cpu_seconds_since(tpatch)))

                return None
            x0,x1 = outx.start, outx.stop
            y0,y1 = outy.start, outy.stop
        else:
            x0,x1,y0,y1 = extent
        psf = img.getPsf()

        # We have two methods of rendering profile galaxies: If the
        # PSF can be represented as a mixture of Gaussians, then we do
        # the analytic Gaussian convolution, producing a larger
        # mixture of Gaussians, and we render that.  Otherwise
        # (pixelized PSFs), we FFT the PSF, multiply by the analytic
        # FFT of the galaxy, and IFFT back to get the rendered
        # profile.

        if hasattr(psf, 'getMixtureOfGaussians'):
            amix = self._getAffineProfile(img, px, py)
            # now convolve with the PSF, analytically
            psfmix = psf.getMixtureOfGaussians(px=px, py=py)
            cmix = amix.convolve(psfmix)

            # print('galaxy affine mixture:', amix)
            # print('psf mixture:', psfmix)
            # print('convolved mixture:', cmix)
            # print('_realGetUnitFluxModelPatch: extent', x0,x1,y0,y1)
            if modelMask is None:
                return mp.mixture_to_patch(cmix, x0, x1, y0, y1, minval,
                                           exactExtent=(extent is not None))
            else:
                # The convolved mixture *already* has the px,py offset added
                # (via px,py to amix) so set px,py=0,0 in this call.
                p = cmix.evaluate_grid_masked(x0, y0, modelMask.patch, 0., 0.)
                assert(p.shape == modelMask.shape)
                return p


        # Otherwise, FFT:
        imh,imw = img.shape

        haveExtent = (modelMask is not None) or (extent is not None)

        if not haveExtent:
            halfsize = self._getUnitFluxPatchSize(img, px, py, minval)

            # Avoid huge galaxies -> huge halfsize in a tiny image (blob)
            imsz = max(imh,imw)
            halfsize = min(halfsize, imsz)

        else:
            # FIXME -- max of modelMask, PSF, and Galaxy sizes!

            if modelMask is not None:
                mh,mw = modelMask.shape
                x1 = x0 + mw
                y1 = y0 + mh
            else:
                # x0,x1,y0,y1 were set to extent, above.
                mw = x1 - x0
                mh = y1 - y0

            # is the source center outside the modelMask?
            sourceOut = (px < x0 or px > x1-1 or py < y0 or py > y1-1)
            
            if sourceOut:
                # FIXME -- could also *think* about switching to a
                # Gaussian approximation when very far from the source
                # center...
                #
                #print('modelMask does not contain source center!  Fetching bigger model...')
                # If the source is *way* outside the patch, return zero.
                neardx,neardy = 0., 0.
                if px < x0:
                    neardx = x0 - px
                if px > x1:
                    neardx = px - x1
                if py < y0:
                    neardy = y0 - py
                if py > y1:
                    neardy = py - y1
                nearest = np.hypot(neardx, neardy)
                if nearest > self.getRadius():

                    if do_fft_timing:
                        fft_timing.append((timing_id, 'source_way_outside', CpuMeas().cpu_seconds_since(tpatch)))
                    return None

                # how far is the furthest point from the source center?
                farw = max(abs(x0 - px), abs(x1 - px))
                farh = max(abs(y0 - py), abs(y1 - py))
                bigx0 = int(np.floor(px - farw))
                bigx1 = int(np.ceil (px + farw))
                bigy0 = int(np.floor(py - farh))
                bigy1 = int(np.ceil (py + farh))
                bigw = 1 + bigx1 - bigx0
                bigh = 1 + bigy1 - bigy0
                boffx = x0 - bigx0
                boffy = y0 - bigy0
                assert(bigw >= mw)
                assert(bigh >= mh)
                assert(boffx >= 0)
                assert(boffy >= 0)
                if modelMask is None:
                    bigMask = np.ones((bigh,bigw), bool)
                else:
                    bigMask = np.zeros((bigh,bigw), bool)
                    bigMask[boffy:boffy+mh, boffx:boffx+mw] = modelMask.patch
                bigMask = Patch(bigx0, bigy0, bigMask)

                if do_fft_timing:
                    fft_timing.append((timing_id, 'calling_sourceout', None))
                    t0 = CpuMeas()

                # print('Recursing:', self, ':', (mh,mw), 'to', (bigh,bigw))
                bigmodel = self._realGetUnitFluxModelPatch(
                    img, px, py, minval, extent=None, modelMask=bigMask)

                if do_fft_timing:
                    t1 = CpuMeas()
                    fft_timing.append((timing_id, 'sourceout', t1.cpu_seconds_since(t0),
                                       (bigMask.shape, (mh,mw))))

                return Patch(x0, y0,
                             bigmodel.patch[boffy:boffy+mh, boffx:boffx+mw])
            
            halfsize = max(mh/2., mw/2.)

            psfh,psfw = psf.shape
            halfsize = max(halfsize, max(psfw/2., psfh/2.))

        if do_fft_timing:
            t0 = CpuMeas()

        P,(cx,cy),(pH,pW),(v,w) = psf.getFourierTransform(px, py, halfsize)

        #print('Computing', self, ': halfsize=', halfsize, 'FFT', (pH,pW))
        
        if do_fft_timing:
            t1 = CpuMeas()
            fft_timing.append((timing_id, 'psf_fft', t1.cpu_seconds_since(t0),
                               (haveExtent, halfsize)))

        # print('PSF Fourier transform size:', P.shape)
        # print('Padded size:', pH,pW)
        # print('PSF center in padded image:', cx,cy)
        # print('Source center px,py', px,py)

        # One example:
        # pH,pW = (256, 256)
        # P.shape = (256, 129)
        # cx,cy = (127, 127)
        
        dx = px - cx
        dy = py - cy
        if haveExtent:
            # the Patch we return *must* have this origin.
            ix0 = x0
            iy0 = y0

            # Put the difference into the galaxy FFT.
            mux = dx - ix0
            muy = dy - iy0

            # ASSUME square PSF
            assert(pH == pW)
            psfh,psfw = psf.shape
            # How much padding on the PSF image?
            psfmargin = cx - psfw/2

            gx0 = gy0 = 0
            if abs(mux) >= psfmargin or abs(muy) >= psfmargin:
                # Wrap-around is possible (likely).  Compute a shifted image
                # and then copy it into the result.
                gx0 = int(np.round(mux))
                gy0 = int(np.round(muy))
                mux -= gx0
                muy -= gy0
                
        else:
            # Put the integer portion of the offset into Patch x0,y0
            ix0 = int(np.round(dx))
            iy0 = int(np.round(dy))

            # Put the subpixel portion into the galaxy FFT.
            mux = dx - ix0
            muy = dy - iy0

        if do_fft_timing:
            t0 = CpuMeas()
        
        amix = self._getAffineProfile(img, mux, muy)

        if do_fft_timing:
            t1 = CpuMeas()
        
        Fsum = amix.getFourierTransform(v, w)
        
        if do_fft_timing:
            t2 = CpuMeas()

            fft_timing.append((timing_id, 'get_affine', t1.cpu_seconds_since(t0),
                               (haveExtent,)))
            fft_timing.append((timing_id, 'get_ft', t2.cpu_seconds_since(t1),
                               (haveExtent,)))

        if False:
            # for fakedx in []:#0]:#, 1, 10]:

            amix2 = self._getAffineProfile(img, mux + fakedx, muy)
            Fsum2 = amix2.getFourierTransform(v, w)

            plt.clf()
            plt.subplot(3,3,1)
            plt.imshow(Fsum2.real, interpolation='nearest', origin='lower')
            plt.title('Galaxy FFT')
            plt.subplot(3,3,2)
            plt.imshow(P.real, interpolation='nearest', origin='lower')
            plt.title('PSF FFT')
            plt.subplot(3,3,3)
            plt.imshow((Fsum2 * P).real,
                       interpolation='nearest', origin='lower')
            plt.title('Galaxy * PSF FFT')
            plt.subplot(3,3,4)
            plt.imshow(Fsum2.imag, interpolation='nearest', origin='lower')
            plt.title('Galaxy FFT')
            plt.subplot(3,3,5)
            plt.imshow(P.imag, interpolation='nearest', origin='lower')
            plt.title('PSF FFT')
            plt.subplot(3,3,6)
            plt.imshow((Fsum2 * P).imag,
                       interpolation='nearest', origin='lower')
            plt.title('Galaxy * PSF FFT')

            plt.subplot(3,3,7)
            plt.imshow(np.fft.irfft2(Fsum2, s=(pH,pW)),
                       interpolation='nearest', origin='lower')
            plt.title('iFFT Galaxy')
            plt.subplot(3,3,8)
            plt.imshow(np.fft.irfft2(P, s=(pH,pW)),
                       interpolation='nearest', origin='lower')
            plt.title('iFFT PSF')
            plt.subplot(3,3,9)
            plt.imshow(np.fft.irfft2(Fsum2*P, s=(pH,pW)),
                       interpolation='nearest', origin='lower')
            plt.title('iFFT Galaxy*PSF')
            
            plt.suptitle('dx = %i pixel' % fakedx)
            psfft.savefig()

        
        
        if haveExtent:

            if False:
                plt.clf()
                plt.imshow(np.fft.irfft2(Fsum * P, s=(pH,pW)),
                           interpolation='nearest', origin='lower')
                plt.title('iFFT in PSF shape')
                psfft.savefig()

            if do_fft_timing:
                t0 = CpuMeas()

            G = np.fft.irfft2(Fsum * P, s=(pH,pW))

            if do_fft_timing:
                t1 = CpuMeas()
                fft_timing.append((timing_id, 'irfft2', t1.cpu_seconds_since(t0),
                                   (haveExtent, (pH,pW))))
            

            gh,gw = G.shape

            if gx0 != 0 or gy0 != 0:
                #print('gx0,gy0', gx0,gy0)
                yi,yo = get_overlapping_region(-gy0, -gy0+mh-1, 0, gh-1)
                xi,xo = get_overlapping_region(-gx0, -gx0+mw-1, 0, gw-1)

                # shifted
                shG = np.zeros((mh,mw), G.dtype)
                shG[yo,xo] = G[yi,xi]
                G = shG
            
            if gh > mh or gw > mw:
                G = G[:mh,:mw]
            if modelMask is not None:
                assert(G.shape == modelMask.shape)
            else:
                assert(G.shape == (mh,mw))
            
        else:
            #print('iFFT', (pW,pH))

            # psfim = np.fft.irfft2(P)
            # print('psf iFFT', psfim.shape, psfim.sum())
            # psfim /= psfim.sum()
            # xx,yy = np.meshgrid(np.arange(pW), np.arange(pH))
            # print('centroid', np.sum(psfim*xx), np.sum(psfim*yy))

            if do_fft_timing:
                t0 = CpuMeas()

            G = np.fft.irfft2(Fsum * P, s=(pH,pW))

            if do_fft_timing:
                t1 = CpuMeas()
                fft_timing.append((timing_id, 'irfft2_b', t1.cpu_seconds_since(t0),
                                   (haveExtent, (pH,pW))))

            # print('Evaluating iFFT with shape', pH,pW)
            # print('G shape:', G.shape)

            if False:
                plt.clf()
                plt.imshow(G, interpolation='nearest', origin='lower')
                plt.title('iFFT in padded PSF shape')
                psfft.savefig()
            
            # Clip down to suggested "halfsize"
            if x0 > ix0:
                G = G[:,x0 - ix0:]
                ix0 = x0
            if y0 > iy0:
                G = G[y0 - iy0:, :]
                iy0 = y0
            gh,gw = G.shape
            if gw+ix0 > x1:
                G = G[:,:x1-ix0]
            if gh+iy0 > y1:
                G = G[:y1-iy0,:]

        if do_fft_timing:
            fft_timing.append((timing_id, 'get_unit_patch_finished', CpuMeas().cpu_seconds_since(tpatch),
                               (self,)))

        return Patch(ix0, iy0, G)