<jupyter_start><jupyter_text>Shape Measurement 102This notebook has the first part of a small-ish tutorial on galaxy shape measurement. The goals of this tutorial are as follows1. Become familiar with how weak lensing shear changes the shapes of objects.2. Understand the basic principles behind `metacalibration`-based shape measurements.3. Learn how to use `metadetection` and `ngmix` to measure the shapes of objects.**Please do the 101 tutorial before doing this one!**In order to run the code in this tutorial, you will need the following packages installed locally - `galsim` - `numpy` - `matplotlib` - `metadetect` - `ngmix` - `meds` - `joblib` - `schwimmbad` - `tqdm` - `joblib` - `fitsio` - `pyyaml` - `des-sxdes` I suggest using `conda`. You can run the command```conda install galsim numpy metaplotlib metadetect ngmix meds joblib schwimmbad tqdm fitsio pyyaml des-sxdes```in your environment to get things going.  A. How does a weak lensing shear change the shape of an object?Remember last time we learned about the moments of a surface brightness profile $I(x,y$$$\langle M_x\rangle = \frac{\int I(x,y) x}{\int I(x,y)}\\\langle M_y\rangle = \frac{\int I(x,y) y}{\int I(x,y)}\\\langle M_{xx}\rangle = \frac{\int I(x,y) (x - M_x)^2}{\int I(x,y)}\\\langle M_{xy}\rangle = \frac{\int I(x,y) (x - M_x)(y-M_y)}{\int I(x,y)}\\\langle M_{yy}\rangle = \frac{\int I(x,y) (y - M_y)^2}{\int I(x,y)}$$It turns out, one can compute specific combinations of these moments that are particularly sensitive to the shearing operation we discussed last time. These combinations are$$\langle M_{r}\rangle = M_{xx} + M_{yy}\\\langle M_{+}\rangle = M_{xx} - M_{yy}\\\langle M_{\times}\rangle = 2 M_{xy}\\$$which can then be combined into $$e_1 = \frac{M_{+}}{M_{r}}\\e_2 = \frac{M_{\times}}{M_{r}}\\$$I have not made the mathematical origin of these particular combinations clear at all, so this should be confusing. The key thing is to remember that one can compute moments and then use the equations above to compute the shape of the object $(e_1, e_2)$. Below we'll explore how shears change these moments. Exercise 1: How does it change?In this exercise, we are going to draw objects with several different shears, make plots of them, and then explore how the object's moments change with shear. To do this, we'll want to use `galsim` to shear an object. This can be done as follows:<jupyter_code>import galsim

obj = galsim.Gaussian(sigma=1)
sheared_obj = obj.shear(g1=0.01, g2=0.0)<jupyter_output><empty_output><jupyter_text>We have called the `.shear` command which returns a new object that has been sheared by the amount given. If we print the underlying representation of this object out, we can see that `galsim` is applying a transformation to the original object, which hints at what a shear actually means:<jupyter_code>print(repr(sheared_obj))<jupyter_output><empty_output><jupyter_text>With these preliminaries out of the way, do the following.1. For each of the shears $(g_1, g_2) = \{(0, 0), (0.1, 0), (-0.1, 0), (0, 0.1), (0, -0.1)\}$, use `galsim` to make the sheared object and draw an image of it. Describe to yourself how the object changes in relationship to the shear you applied. Why might we have used $+$ and $\times$ when describing the moments above?2. For a grid of shears on the 1-axis with $g_1$ going from -0.2 to 0.2 in steps of 0.025, measure the shapes `(e_1, e_2)` as defined above. Set $g_2=0$. What do you notice about the relationship between the shear and the shape when the shear is small?3. Repeat 2 but with the roles of $g_1$ and $g_2$ swapped so that $g_1=0$ and the grid of shears is in $g_2$.4. What is your guess for the generic relationship between shapes and shears?<jupyter_code># do your work here<jupyter_output><empty_output><jupyter_text>B. Counterfactual Images and MetacalibrationIn the last exercise, you should have discovered how the measured shape of an object changes as it is sheared by different amounts. In particular, for small or *weak* gravitational shears, we expect that$$e_i \approx e^{0} + R_i g_i$$where $R_i$ is called the *response*. The response is formally defined as a derivative $\left.de/dg\right.|_{g=0}$, but this is a detail that is not needed to complete this tutorial. They key bit of information here is that the response is the slope of the linear relationship between the observed shape and the underlying shear, for small shears. Exercise 2: Compute the response numerically with `galsim`!In this exercise, we will use galsim to simulate the same object with different shears. Then by measuring the moments of those simulated profiles, we will estimate the response.1. Using `galsim`, create a Gaussian object with `sigma=1` and a shear of `g1=0.01` and `g2=0`. Draw this object onto a grid of pixels with `scale=0.2` and measure the shape $(e_1, e_2)$.2. Repeat 2 above but with `g1=-0.01` and `g2=0.0`. 3. Combine the results from 1 and 2 to compute $R_1$ via a finite-difference derivative$$R_1 = \frac{e_1^{g1=+h} - e_1^{g1=-h}}{2h}$$This is called a central difference derivative is typically an accurate but efficient method for this kind of numerical work.4. Do the same steps 1-3 but with the roles of the shears/shapes on the 1-axis swapped with those on the 2-axis. In other words, use shears with `g1=0`, `g2=+/-0.01` and compute $R_2$ by using a central difference derivative with $e_2$.5. Now make the same object but apply the shear `g1=0.02` and `g2=0.0`. Measure its shape with the same pixel scale. Estimate the underlying shear applied to the object by inverting the linear relationship above$$g_{i}^{\rm obs} \approx e_i/R_i$$How close did you get?<jupyter_code># do your work here<jupyter_output><empty_output><jupyter_text>C. Metadetection/Metacalibration in real data with `ngmix`Congrats! By completing exercise 2 of this tutorial you have coded up an extremely simple, but non-trivial form of the `metacalibration` weak lensing shape measurement technique!Moving from here to applying this to real observed galaxies involves a lot of complications. Namely1. We don't know the true underlying surface brightness profile of real galaxies. Thus we cannot directly compute the response from the unsheared surface brightness profile like we did above.2. Real galaxies are observed through telescopes and we need to account for this effect when computing the response.3. Images of real galaxies have noise in them. If we take an image with noise in it and shear the image, we will get correlated noise which can cause bias in our shape measurements.The solutions to these problems are the subject of years of research, so I'll give you the answers.1. Instead of using the true surface brightness profile of an object, we use the observed image of the object itself. We can do this because the shear applied by nature is small, so that the difference between the response computed using the observed object and that using the true object is even smaller. This process generates a *counter-factual* image of the object at another shear, hence the title above.2. We measure the PSF of the telescope and atmosphere using stars around the galaxy. We can then use this observed PSF to remove these effects via a process called deconvolution. This process cannot fully recover the galaxy as it would look without the telescope and atmosphere, but it can remove enough of these effects for our purposes.3. We can apply special noise corrections to our images which account for the correlated noise. The details of this process are unimportant except to say that if we don't we see 5% to 10% biases our measurements.All of these algorithms have been coded up into two packages, `metadetect` and `ngmix`. These are the main tools you will be using. The next exercise introduces them to you and generates the first kinds of simulations you'll run to test shape measurement. Exercise 3: Making Sims w/ `ngmix` and `galsim`In this exercise, we will write a function to create a simple simulation of a single object using `ngmix` and `galsim`. Your goal tasks are to1. Fill in the parts of the function below to make the sim.2. Run the simulation with different shears and different psf sizes, measuring the output shape using the moments code above. Try psf sizes from 0.8 to 1.2 in steps of 0.1. What is the relationship between the slope of output shape vs input shape and the size of the PSF?The simulation code will output an `ngmix.Observation` object. You can read more about them in the docs: https://github.com/esheldon/ngmix/blob/master/ngmix/observation.pyL66. Here is a short set of things you can do:``` get the image in this observation as a numpy arrayobs.image  get the image of the PSF as a numpy arrayobs.psf.image```Note that these objects are read only, so you cannot modify the images etc once they are made without special syntax.<jupyter_code>import ngmix
import numpy as np

def sim_func(*, g1, g2, seed, psf_fwhm):
    # this is an RNG you can use to draw random numbers as needed
    # always use this RNG and not np.random directly
    # doing this makes sure the code is reproducible
    rng = np.random.RandomState(seed=seed)

    # make an Exponential object in galsim with a half light radius of 0.5
    gal =  # fill this in
    
    # make a Gaussian object in galsim with a fwhm of `psf_fwhm`
    psf =  # fill this in
    
    # apply the input shear `g1`, `g2` to the galaxy `gal`
    sheared_gal =  # fill this in
    
    # here we are going to apply a random shift to the object's center
    dx, dy = 2.0 * (rng.uniform(size=2)-0.5) * 0.2
    sheared_gal = sheared_gal.shift(dx, dy)
    
    # convolve the sheared galaxy with the psf
    obj =  # fill this in
    
    # render the object and the PSF on odd sized images of 53 pixels on a side with 
    # a pixel scale of 0.2
    obj_im =  # fill this in
    psf_im =  # fill this in
    
    # now we are going to add noise to the object image and setup the ngmix data
    cen = (53-1)/2
    nse_sd = 1e-5
    nse = rng.normal(size=obj_im.array.shape, scale=nse_sd)
    nse_im = rng.normal(size=obj_im.array.shape, scale=nse_sd)
    jac = ngmix.jacobian.DiagonalJacobian(scale=0.2, row=cen+dy/0.2, col=cen+dx/0.2)
    psf_jac = ngmix.jacobian.DiagonalJacobian(scale=0.2, row=cen, col=cen)
    
    # we have to add a little noise to the PSf to make things stable
    target_psf_s2n = 500.0
    target_psf_noise = np.sqrt(np.sum(psf_im.array ** 2)) / target_psf_s2n    
    psf_obs = ngmix.Observation(
        image=psf_im.array,
        weight=np.ones_like(psf_im.array)/target_psf_noise**2,
        jacobian=psf_jac,
    )
    
    # here we build the final observation
    obj_obs = ngmix.Observation(
        image=obj_im.array + nse,
        noise=nse_im,
        weight=np.ones_like(nse_im) / nse_sd**2,
        jacobian=psf_jac,
        bmask=np.zeros_like(nse_im, dtype=np.int32),
        ormask=np.zeros_like(nse_im, dtype=np.int32),
        psf=psf_obs,
    )

    return obj_obs
# do your work here<jupyter_output><empty_output><jupyter_text>Exercise 4: Run metadetect!In this exercise you will run metadetection for real! Yay! Before we do that, we need to understand a few things about the results. We are aiming to measure the shear and are using the estimate $g^{obs}$ as defined above. There is a standard way to characterize the bias in this estimate, which in our notation is$$g^{obs} = (1+m)g^{true} + c$$Here $m$ is called the *multiplicative bias* and is the fractional error in our shear estimate. The quantity $c$ is called the additive bias and denotes any mean bias in our shear estimate. For metadetect, we expect $$m\approx 0.4\times10^{-3}\\c\approx 0$$Due to measurement noise in the simulations, we usually check that these conditions are true to within the $3\sigma$ error bar.In the python source file `mdet_meas_tools.py`, I have put a bunch of code that you will use to run the simulation you made above through metadetection. The main functions to worry about are - `run_mdet_sims` - used to make the measurements - `write_sim_data` - used to write the simulation data to disk - `read_sim_data` - used to read the simulation data from disk - `estimate_m_and_c` - used to estimate $m$ and $c$ from a sim Read the doc strings on these functions for instructions on how to use them. Note that you need to pass a python dictionary for the `sim_kwargs` input and it should have the form `{"psf_fwhm": 0.9}`.Do the following:1. Use `run_mdet_sims` to run metadetection on your sim and verify that $m$ and $c$ meet the requirements above.2. Use the utilities to write your data to and then read your data from disk.3. Run your data through `estimate_m_and_c` and confirm you get the same answer as in 1.<jupyter_code># do your work here!<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.07891646441712988, 'fraction_numerical': 0.015658028654192437, 'mean_word_length': 4.0933014354066986, 'pattern_counts': {'":': 1, '<': 25, '<?xml version=': 0, '>': 25, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '6', 'dir': 'jupyter-structured-clean-dedup', 'id': '405574', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 3886, 'n_tokens_neox': 3708, 'n_words': 1839, 'path': 'shape_measurement_102.ipynb', 'repo_name': 'beckermr/shape-measurement-tutorials', 'size': '12773'}