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)
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
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
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)
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.
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.
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)
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
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)
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
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)