|
| 1 | +import numpy as np |
| 2 | +import ngmix |
| 3 | +import galsim |
| 4 | +import pytest |
| 5 | + |
| 6 | + |
| 7 | +@pytest.mark.parametrize('psf', ['gauss', 'fitgauss', 'galsim_obj']) |
| 8 | +def test_metacal_accuracy(psf): |
| 9 | + |
| 10 | + ntrial = 100 |
| 11 | + seed = 99 |
| 12 | + |
| 13 | + if psf == 'galsim_obj': |
| 14 | + psf = galsim.Gaussian(fwhm=1.05) |
| 15 | + |
| 16 | + # Wacky WCS |
| 17 | + wcs_g1 = 0.1 |
| 18 | + wcs_g2 = 0.0 |
| 19 | + wcs = galsim.ShearWCS(0.263, galsim.Shear(g1=wcs_g1, g2=wcs_g2)).jacobian() |
| 20 | + |
| 21 | + print("WCS:", wcs.getDecomposition()) |
| 22 | + print() |
| 23 | + print() |
| 24 | + |
| 25 | + shear_true = 0.02 |
| 26 | + rng = np.random.RandomState(seed) |
| 27 | + |
| 28 | + # We will measure moments with a fixed gaussian weight function |
| 29 | + weight_fwhm = 1.2 |
| 30 | + fitter = ngmix.gaussmom.GaussMom(fwhm=weight_fwhm) |
| 31 | + psf_fitter = ngmix.gaussmom.GaussMom(fwhm=weight_fwhm) |
| 32 | + |
| 33 | + # these "runners" run the measurement code on observations |
| 34 | + psf_runner = ngmix.runners.PSFRunner(fitter=psf_fitter) |
| 35 | + runner = ngmix.runners.Runner(fitter=fitter) |
| 36 | + |
| 37 | + # this "bootstrapper" runs the metacal image shearing as well as both psf |
| 38 | + # and object measurements |
| 39 | + boot = ngmix.metacal.MetacalBootstrapper( |
| 40 | + runner=runner, psf_runner=psf_runner, |
| 41 | + rng=rng, |
| 42 | + psf=psf, |
| 43 | + types=['noshear', '1p', '1m'], |
| 44 | + ) |
| 45 | + dlist = [] |
| 46 | + for i in range(ntrial): |
| 47 | + im, psf_im, obs = _make_data(rng=rng, shear=shear_true, wcs=wcs) |
| 48 | + resdict, obsdict = boot.go(obs) |
| 49 | + for stype, sres in resdict.items(): |
| 50 | + st = _make_struct(res=sres, obs=obsdict[stype], shear_type=stype) |
| 51 | + dlist.append(st) |
| 52 | + |
| 53 | + print() |
| 54 | + data = np.hstack(dlist) |
| 55 | + |
| 56 | + w = _select(data=data, shear_type='noshear') |
| 57 | + w_1p = _select(data=data, shear_type='1p') |
| 58 | + w_1m = _select(data=data, shear_type='1m') |
| 59 | + |
| 60 | + g1 = data['g'][w, 0].mean() |
| 61 | + g1err = data['g'][w, 0].std() / np.sqrt(w.size) |
| 62 | + g1_1p = data['g'][w_1p, 0].mean() |
| 63 | + g1_1m = data['g'][w_1m, 0].mean() |
| 64 | + |
| 65 | + R11 = (g1_1p - g1_1m)/0.02 |
| 66 | + |
| 67 | + shear = g1 / R11 |
| 68 | + shear_err = g1err / R11 |
| 69 | + m = shear / shear_true - 1 |
| 70 | + merr = shear_err / shear_true |
| 71 | + |
| 72 | + s2n = data['s2n'][w].mean() |
| 73 | + print('S/N: %g' % s2n) |
| 74 | + print('R11: %g' % R11) |
| 75 | + print('m: %g +/- %g (99.7%% conf)' % (m, merr*3)) |
| 76 | + |
| 77 | + assert np.abs(m - 0.00034) < 1.0e-4 |
| 78 | + |
| 79 | + |
| 80 | +def _make_struct(res, obs, shear_type): |
| 81 | + """ |
| 82 | + make the data structure |
| 83 | + Parameters |
| 84 | + ---------- |
| 85 | + res: dict |
| 86 | + With keys 's2n', 'e', and 'T' |
| 87 | + obs: ngmix.Observation |
| 88 | + The observation for this shear type |
| 89 | + shear_type: str |
| 90 | + The shear type |
| 91 | + Returns |
| 92 | + ------- |
| 93 | + 1-element array with fields |
| 94 | + """ |
| 95 | + dt = [ |
| 96 | + ('flags', 'i4'), |
| 97 | + ('shear_type', 'U7'), |
| 98 | + ('s2n', 'f8'), |
| 99 | + ('g', 'f8', 2), |
| 100 | + ('T', 'f8'), |
| 101 | + ('Tpsf', 'f8'), |
| 102 | + ] |
| 103 | + data = np.zeros(1, dtype=dt) |
| 104 | + data['shear_type'] = shear_type |
| 105 | + data['flags'] = res['flags'] |
| 106 | + if res['flags'] == 0: |
| 107 | + data['s2n'] = res['s2n'] |
| 108 | + # for moments we are actually measureing e, the elliptity |
| 109 | + data['g'] = res['e'] |
| 110 | + data['T'] = res['T'] |
| 111 | + else: |
| 112 | + data['s2n'] = np.nan |
| 113 | + data['g'] = np.nan |
| 114 | + data['T'] = np.nan |
| 115 | + data['Tpsf'] = np.nan |
| 116 | + # we only have one epoch and band, so we can get the psf T from the |
| 117 | + # observation rather than averaging over epochs/bands |
| 118 | + data['Tpsf'] = obs.psf.meta['result']['T'] |
| 119 | + return data |
| 120 | + |
| 121 | + |
| 122 | +def _select(data, shear_type): |
| 123 | + """ |
| 124 | + select the data by shear type and size |
| 125 | + Parameters |
| 126 | + ---------- |
| 127 | + data: array |
| 128 | + The array with fields shear_type and T |
| 129 | + shear_type: str |
| 130 | + e.g. 'noshear', '1p', etc. |
| 131 | + Returns |
| 132 | + ------- |
| 133 | + array of indices |
| 134 | + """ |
| 135 | + # raw moments, so the T is the post-psf T. This the |
| 136 | + # selection is > 1.2 rather than something smaller like 0.5 |
| 137 | + # for pre-psf T from one of the maximum likelihood fitters |
| 138 | + wtype, = np.where( |
| 139 | + (data['shear_type'] == shear_type) & |
| 140 | + (data['flags'] == 0) |
| 141 | + ) |
| 142 | + w, = np.where(data['T'][wtype]/data['Tpsf'][wtype] > 1.2) |
| 143 | + print('%s kept: %d/%d' % (shear_type, w.size, wtype.size)) |
| 144 | + w = wtype[w] |
| 145 | + return w |
| 146 | + |
| 147 | + |
| 148 | +def _make_data(rng, shear, wcs): |
| 149 | + """ |
| 150 | + simulate an exponential object with moffat psf |
| 151 | + the hlr of the exponential is drawn from a gaussian |
| 152 | + with mean 0.4 arcseconds and sigma 0.2 |
| 153 | + Parameters |
| 154 | + ---------- |
| 155 | + rng: np.random.RandomState |
| 156 | + The random number generator |
| 157 | + noise: float |
| 158 | + Noise for the image |
| 159 | + shear: (g1, g2) |
| 160 | + The shear in each component |
| 161 | + Returns |
| 162 | + ------- |
| 163 | + ngmix.Observation |
| 164 | + """ |
| 165 | + noise = 1.0e-8 |
| 166 | + psf_noise = 1.0e-8 |
| 167 | + stamp_size = 91 |
| 168 | + |
| 169 | + psf_fwhm = 0.9 |
| 170 | + gal_hlr = 0.5 |
| 171 | + psf = galsim.Moffat( |
| 172 | + beta=2.5, |
| 173 | + fwhm=psf_fwhm, |
| 174 | + ).shear( |
| 175 | + g1=0.02, |
| 176 | + g2=-0.01, |
| 177 | + ) |
| 178 | + obj0 = galsim.Exponential( |
| 179 | + half_light_radius=gal_hlr, |
| 180 | + ).shear( |
| 181 | + g1=shear, |
| 182 | + ) |
| 183 | + obj = galsim.Convolve(psf, obj0) |
| 184 | + |
| 185 | + psf_im = psf.drawImage(nx=stamp_size, ny=stamp_size, wcs=wcs).array |
| 186 | + im = obj.drawImage(nx=stamp_size, ny=stamp_size, wcs=wcs).array |
| 187 | + |
| 188 | + psf_im += rng.normal(scale=psf_noise, size=psf_im.shape) |
| 189 | + im += rng.normal(scale=noise, size=im.shape) |
| 190 | + |
| 191 | + cen = (np.array(im.shape)-1.0)/2.0 |
| 192 | + psf_cen = (np.array(psf_im.shape)-1.0)/2.0 |
| 193 | + |
| 194 | + jacobian = ngmix.Jacobian( |
| 195 | + x=cen[1], y=cen[0], wcs=wcs.jacobian( |
| 196 | + image_pos=galsim.PositionD(cen[1], cen[0]) |
| 197 | + ), |
| 198 | + ) |
| 199 | + psf_jacobian = ngmix.Jacobian( |
| 200 | + x=psf_cen[1], y=psf_cen[0], wcs=wcs.jacobian( |
| 201 | + image_pos=galsim.PositionD(psf_cen[1], psf_cen[0]) |
| 202 | + ), |
| 203 | + ) |
| 204 | + |
| 205 | + wt = im*0 + 1.0/noise**2 |
| 206 | + psf_wt = psf_im*0 + 1.0/psf_noise**2 |
| 207 | + psf_obs = ngmix.Observation( |
| 208 | + psf_im, |
| 209 | + weight=psf_wt, |
| 210 | + jacobian=psf_jacobian, |
| 211 | + ) |
| 212 | + obs = ngmix.Observation( |
| 213 | + im, |
| 214 | + weight=wt, |
| 215 | + jacobian=jacobian, |
| 216 | + psf=psf_obs, |
| 217 | + ) |
| 218 | + return im, psf_im, obs |
0 commit comments