Why are fake sources "corrected" to mean the integrated flux within calibFluxRadius?

Answer

To take a given magnitude and translate to the number of counts in the image, insertFakes.py uses photoCalib.magnitudeToInstFlux, which, for a given magnitude, returns the instrumental flux for the given calibration radius used in the photometric calibration step. Thus calibFluxRadius should be set to this same radius so that we can normalize the PSF model to the correct instrumental flux within calibFluxRadius.

The implicit convention for this radius is 12 pixels (=4" for HSC), which is why this is the default for calibFluxRadius in insertFakes.py.

Original Question

What is the purpose calibFluxRadius in insertFakes.py in pipe_tasks ? Its use doesn’t make sense to me. My reading of it is that it changes the effective realized flux to be wrong for objects so that the flux that should be distributed over an infinite aperture is instead distributed just within calibFluxRadius .

effectively means takes the flux and boosts it by dividing by the enclosed energy within the aperture.

starIm = PSF * flux / correctedFlux

In more detail that relevant lines are:

correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
starIm = psf.computeImage(xy)
starIm /= correctedFlux
flux = photoCalib.magnitudeToInstFlux(row[self.config.magVar % band], xy)
starIm *= flux

I think this is a confusing way to do it at two levels:

  1. If I simulate a source in a finite region then I should expect that I’ve only laid down a fraction of the flux. I should expect, e.g., that my aperture corrections for a set of fake stars fail when my aperture hits the box size in which I laid down the fakes.
  2. Even if I thought that I wanted to correct for the finite box size and for some reason make sure that all of the infinite-aperture photons were deposited within the finite bounding box, it’s not even that . There’s a separate calibFluxRadius. What sets that? How is it related to the finite size of the generate fake image stamp? How do I know I’m consistently using that radius in generating my catalogs and analyzing the photometry?

I would have expected:

starIm = psf.computeImage(xy)
flux = photoCalib.magnitudeToInstFlux(row[self.config.magVar % band], xy)
starIm *= flux

and I would expect that fake sources placed on an image do not deposit their full 100% of energy within the finite box in which they are simulated. If I wanted to get all of the flux placed on the image, i would create fakes with very large boxes (100s of pixels).

When we inserted the fakes we wanted to make them easily usable for QA work. This meant making them as similar to the what the catalogue outputs as possible. While not using the calibFluxRadius might be more intuitive (indeed it was what I originally had) it produces an offset when comparing the input and output magnitudes. While you can just say the offset is caused by this fact it is easier to take it into account and then have the magnitude/flux difference closer to zero. We did not want people either confused by this offset when looking at plots or having to account for it themselves.

This code is supposed to run quickly so that it can be used at large scales and creating lots of very large fakes is probably not going to help that aim. I also would be wary of creating PSF sources that are larger than the PSF used to input them, maybe it is trivial to extend the PSF models to larger boxes but I wanted to use what the stack produced as standard without recalculating anything.

The calibFluxRadius is set to be the same as is used elsewhere in the stack and would need setting if you are planning on using a different radius in subsequent processing steps. (@jbosch can correct me if I am wrong or missing any subtleties here.)

We have been talking about refactoring the code to allow people to pregenerate their own images and insert them. Perhaps this would be useful for you?

But why? I presume this would only be true if you are measuring the fakes beyond the radius at which you are simulating them.

You should be simulating the fakes at least out to the radius at which you are planning to measure their photometry. And then you should apply the same aperture correction to those measurements as you do to the other stars. If you do this, then you should not get an offset.

When we inserted the fakes we wanted to make them easily usable for QA work. This meant making them as similar to the what the catalogue outputs as possible.

These seem like non sequitors to the issue here. The input catalog should be infinite aperture fluxes, and the output catalog should be infinite aperture fluxes. The above arguments sounds suspiciously like someone had an input catalog of positions and fluxes and they wanted to generate fakes, measure the fakes at some fixed aperture, and compare the fluxes without doing aperture corrections. But this isn’t how photometry works.

I want to generate fakes from the PSF of the image. Ignoring galaxies for the moment, I want to have an input catalog of

ra, dec, mag [or maybe x, y, mag]

put them down on an already-calibrated image using the photometric and astrometric calibration known for that image, and then trace how they propagate through future steps – in particular through to DIA for my interests.

Thinking about this a little further, I should clarify my expectations when I said I expected the code to be the following:

starIm = psf.computeImage(xy, x_boxsize, y_boxsize)
flux = photoCalib.magnitudeToInstFlux(row[self.config.magVar % band], xy)
starIm *= flux

I assume that psf.computeImage returns the PSF realized over the defined boxsize (I don’t know actually where the boxsize is defined in this insertFakes.py code). The PSF is normalized to infinite size, so any finite realization of it adds up to less than 1.

Turns out this didn’t reply to an individual comment so editing. In the context of wanting to add fakes with ra, dec, mag.

That is entirely possibly with this code. You will have to give it some other parameters because it doesn’t know you don’t want to add galaxies but they aren’t used so you can set them to whatever you like. If you are wanting to run it through alert production steps then I think @cmorrison is adapting this code to work for that.

Point number 13 in Sec. 3.1 of Bosch+2018 (https://arxiv.org/pdf/1705.06766.pdf) implies that all reported fluxes are corrected to represent flux within a 4 arcsec diameter, and not meant to be estimates of “total” flux. So if you’re feeding fake stars with “mag” that is intended to be the total flux, then we would expect to get back something less than the input flux? (Or perhaps I am misunderstanding?)

So to make the numbers comparable we allow for this correction. What we want is for the numbers to come out the same as what went in. This makes it easier to use them for QA without having to think about offsets and correction factors. Or having to simulate infinite sized objects.

The definitions of “went in”, “come out”, are for a pretty specific pair points in the process.

For clarity by what “goes in” I mean the number in the input fakes catalogue and by what “comes out” I mean the number in the catalogue produced after processing.

Maybe my entire confusion is that photoCalib.magnitudeToInstFlux does a different thing than I expect.

Does photoCalib.magnitudeToInstFlux return

  1. the infinite-aperture flux,
  2. the flux at some implicitly defined radius

If 2, how does it know to use the same calibFluxRadius as defined in the insertFakes.py config?

OK, so photoCalib.magnitudeToInstFlux doesn’t itself know.

http://doxygen.lsst.codes/stack/doxygen/x_masterDoxyDoc/src_2image_2photo_calib_8cc_source.html

It’s just using whatever scaling factor is stored.

So the magic value of calibFluxRatio=12 pixels in insertFakes.py is 4" / 0.17"/(HSC pixel) / 2 = 12 pixels?

0.17 "/pix * 12 pix * 2 = 4.08 "

(Where the extra factor of two is radius vs diameter)

Yes, and the actual configuration value is the 12 (pixel radius) not the 4 (arcsec diameter), so while it’s unfortunate that there are multiple configuration values that need to be kept in sync, they at least must kept in sync to the same (rather than merely related) values.

More generally, I think what we are doing here is the least-bad thing we could be doing in the absence of a PSF model that is “more true” (especially in the wings) than the one we have. Doing better is only possible when inserting fakes into simulated images (perhaps something we should consider supporting better by providing a way to use a different PSF), or if we both had a model of the extended wings of the PSF (we will some day) but chose not to use it here for performance reasons (which then might be a reasonable option).

Thank you all. I understand much better what is going on.

Where is the matching radius choice for the photometric calibration defined? I tried to look through the pipe_tasks routines but couldn’t find it defined there.

Oh, it’s not explicitly set anywhere by a defined number in a config.

It’s implicitly set by the definition of slot_CalibFlux_instFlux, which is set point to a named value. (Thanks @laurenam)

This concern was addressed by @erykoff in the FGCM code by parsing the field name to extract the aperture:

  1. Unsolicited suggestion: photoCalib.magnitudeToInstFlux should know what aperture radius it was calibrated to. Perhaps this overall issue of tracking this through basic calexp photometric calibration, FGCM, and insertion of fakes should be revisited to think about how to pass around this information in a common way.

  2. Helpful action: I’ve submitted a PR to improve the documentation in pipe_tasks insertFakes.py so that I at least won’t get confused again next time I look through this.

https://jira.lsstcorp.org/browse/DM-24546

Documentation addition added and merged to master. Thanks everyone.