def __init__(self, *args): ''' MogParams(amp, mean, var) or MogParams(a0,a1,a2, mx0,my0,mx1,my1,mx2,my2, vxx0,vyy0,vxy0, vxx1,vyy1,vxy1, vxx2,vyy2,vxy2) amp: np array (size K) of Gaussian amplitudes mean: np array (size K,2) of means var: np array (size K,2,2) of variances ''' from tractor import mixture_profiles as mp if len(args) == 3: amp, mean, var = args else: assert (len(args) % 6 == 0) K = len(args) // 6 amp = np.array(args[:K]) mean = np.array(args[K:3 * K]).reshape((K, 2)) args = args[3 * K:] var = np.zeros((K, 2, 2)) var[:, 0, 0] = args[::3] var[:, 1, 1] = args[1::3] var[:, 0, 1] = var[:, 1, 0] = args[2::3] self.mog = mp.MixtureOfGaussians(amp, mean, var) K = self.mog.K assert (self.mog.D == 2) super(MogParams, self).__init__() # drop the ParamList storage del self.vals self._set_param_names(K)
def getCircularMog(amps, sigmas): K = len(amps) amps = np.array(amps).astype(np.float32) means = np.zeros((K, 2), np.float32) vars = np.zeros((K, 2, 2), np.float32) for k in range(K): vars[k, 0, 0] = vars[k, 1, 1] = sigmas[k]**2 return mp.MixtureOfGaussians(amps, means, vars, quick=True)
def getMixtureOfGaussians(self, px=None, py=None): K = len(self.myweights) amps = np.array(self.myweights) means = np.zeros((K, 2)) vars = np.zeros((K, 2, 2)) for k in range(K): vars[k, 0, 0] = vars[k, 1, 1] = self.mysigmas[k]**2 return mp.MixtureOfGaussians(amps, means, vars)
class GaussianGalaxy(HoggGalaxy): nre = 6. profile = mp.MixtureOfGaussians(np.array([1.]), np.zeros((1, 2)), np.array([[[1., 0.], [0., 1.]]])) profile.normalize() def __init__(self, *args, **kwargs): self.nre = GaussianGalaxy.nre super(GaussianGalaxy, self).__init__(*args, **kwargs) def getName(self): return 'GaussianGalaxy' def getProfile(self): return GaussianGalaxy.profile
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)
plt.clf() plt.subplot(2,2,2) dimshow(np.log10(np.maximum(floor, patch2.patch)), **ima) plt.subplot(2,2,3) dimshow(dx2.patch) plt.subplot(2,2,4) dimshow(dy2.patch) ps.savefig() sys.exit(0) mg = mp.MixtureOfGaussians([1.], [0.,0.], np.array([1.])) x = mg.evaluate(np.array([0,0])) x = mg.evaluate(np.array([0.,1.])) x = mg.evaluate(np.array([-3.,2.])) x = mg.evaluate(np.array([[-3.,2.], [-17,4], [4,-2]])) mg = mp.MixtureOfGaussians([1.], [0.,1.], np.array([2.])) x = mg.evaluate(np.array([0.,1.])) x = mg.evaluate(np.array([0,0])) x = mg.evaluate(np.array([-3.,2.])) x = mg.evaluate(np.array([[-3.,2.], [-17,4], [4,-2]])) mg = mp.MixtureOfGaussians([1.], [0.,1.], np.array([ [[1.3, 0.1],[0.1,3.1]], ])) x = mg.evaluate(np.array([0.,1.])) x = mg.evaluate(np.array([0,0])) x = mg.evaluate(np.array([-3.,2.]))