23 Insert fakes into deepCoadds
26 from astropy.table
import Table
31 import lsst.pex.config
as pexConfig
34 from lsst.pipe.base import CmdLineTask, PipelineTask, PipelineTaskConfig, PipelineTaskConnections
38 from lsst.geom import SpherePoint, radians, Box2D
41 __all__ = [
"InsertFakesConfig",
"InsertFakesTask"]
45 dimensions=(
"tract",
"patch",
"abstract_filter",
"skymap")):
48 doc=
"Image into which fakes are to be added.",
49 name=
"{CoaddName}Coadd",
50 storageClass=
"ExposureF",
51 dimensions=(
"tract",
"patch",
"abstract_filter",
"skymap")
55 doc=
"Catalog of fake sources to draw inputs from.",
56 name=
"{CoaddName}Coadd_fakeSourceCat",
57 storageClass=
"Parquet",
58 dimensions=(
"tract",
"skymap")
61 imageWithFakes = cT.Output(
62 doc=
"Image with fake sources added.",
63 name=
"fakes_{CoaddName}Coadd",
64 storageClass=
"ExposureF",
65 dimensions=(
"tract",
"patch",
"abstract_filter",
"skymap")
70 pipelineConnections=InsertFakesConnections):
71 """Config for inserting fake sources
75 The default column names are those from the University of Washington sims database.
78 raColName = pexConfig.Field(
79 doc=
"RA column name used in the fake source catalog.",
84 decColName = pexConfig.Field(
85 doc=
"Dec. column name used in the fake source catalog.",
90 doCleanCat = pexConfig.Field(
91 doc=
"If true removes bad sources from the catalog.",
96 diskHLR = pexConfig.Field(
97 doc=
"Column name for the disk half light radius used in the fake source catalog.",
99 default=
"DiskHalfLightRadius",
102 bulgeHLR = pexConfig.Field(
103 doc=
"Column name for the bulge half light radius used in the fake source catalog.",
105 default=
"BulgeHalfLightRadius",
108 magVar = pexConfig.Field(
109 doc=
"The column name for the magnitude calculated taking variability into account. In the format "
110 "``filter name``magVar, e.g. imagVar for the magnitude in the i band.",
115 nDisk = pexConfig.Field(
116 doc=
"The column name for the sersic index of the disk component used in the fake source catalog.",
121 nBulge = pexConfig.Field(
122 doc=
"The column name for the sersic index of the bulge component used in the fake source catalog.",
127 aDisk = pexConfig.Field(
128 doc=
"The column name for the semi major axis length of the disk component used in the fake source"
134 aBulge = pexConfig.Field(
135 doc=
"The column name for the semi major axis length of the bulge component.",
140 bDisk = pexConfig.Field(
141 doc=
"The column name for the semi minor axis length of the disk component.",
146 bBulge = pexConfig.Field(
147 doc=
"The column name for the semi minor axis length of the bulge component used in the fake source "
153 paDisk = pexConfig.Field(
154 doc=
"The column name for the PA of the disk component used in the fake source catalog.",
159 paBulge = pexConfig.Field(
160 doc=
"The column name for the PA of the bulge component used in the fake source catalog.",
165 sourceType = pexConfig.Field(
166 doc=
"The column name for the source type used in the fake source catalog.",
168 default=
"sourceType",
171 fakeType = pexConfig.Field(
172 doc=
"What type of fake catalog to use, snapshot (includes variability in the magnitudes calculated "
173 "from the MJD of the image), static (no variability) or filename for a user defined fits"
179 calibFluxRadius = pexConfig.Field(
180 doc=
"Aperture radius (in pixels) that was used to define the calibration for this image+catalog. "
181 "This will be used to produce the correct instrumental fluxes within the radius. "
182 "This value should match that of the field defined in slot_CalibFlux_instFlux.",
187 coaddName = pexConfig.Field(
188 doc=
"The name of the type of coadd used",
195 """Insert fake objects into images.
197 Add fake stars and galaxies to the given image, read in through the dataRef. Galaxy parameters are read in
198 from the specified file and then modelled using galsim.
200 `InsertFakesTask` has five functions that make images of the fake sources and then add them to the
204 Use the WCS information to add the pixel coordinates of each source.
205 `mkFakeGalsimGalaxies`
206 Use Galsim to make fake double sersic galaxies for each set of galaxy parameters in the input file.
208 Use the PSF information from the image to make a fake star using the magnitude information from the
211 Remove rows of the input fake catalog which have half light radius, of either the bulge or the disk,
214 Add the fake sources to the image.
218 _DefaultName =
"insertFakes"
219 ConfigClass = InsertFakesConfig
221 def runDataRef(self, dataRef):
222 """Read in/write out the required data products and add fake sources to the deepCoadd.
226 dataRef : `lsst.daf.persistence.butlerSubset.ButlerDataRef`
227 Data reference defining the image to have fakes added to it
228 Used to access the following data products:
234 if self.
config.fakeType ==
"static":
235 fakeCat = dataRef.get(
"deepCoadd_fakeSourceCat").toDataFrame()
238 self.fakeSourceCatType =
"deepCoadd_fakeSourceCat"
240 fakeCat = Table.read(self.
config.fakeType).to_pandas()
242 coadd = dataRef.get(
"deepCoadd")
244 photoCalib = coadd.getPhotoCalib()
246 imageWithFakes = self.run(fakeCat, coadd, wcs, photoCalib)
248 dataRef.put(imageWithFakes.imageWithFakes,
"fakes_deepCoadd")
250 def runQuantum(self, butlerQC, inputRefs, outputRefs):
251 inputs = butlerQC.get(inputRefs)
252 inputs[
"wcs"] = inputs[
"image"].getWcs()
253 inputs[
"photoCalib"] = inputs[
"image"].getPhotoCalib()
255 outputs = self.run(**inputs)
256 butlerQC.put(outputs, outputRefs)
259 def _makeArgumentParser(cls):
260 parser = pipeBase.ArgumentParser(name=cls._DefaultName)
261 parser.add_id_argument(name=
"--id", datasetType=
"deepCoadd",
262 help=
"data IDs for the deepCoadd, e.g. --id tract=12345 patch=1,2 filter=r",
263 ContainerClass=ExistingCoaddDataIdContainer)
266 def run(self, fakeCat, image, wcs, photoCalib):
267 """Add fake sources to an image.
271 fakeCat : `pandas.core.frame.DataFrame`
272 The catalog of fake sources to be input
273 image : `lsst.afw.image.exposure.exposure.ExposureF`
274 The image into which the fake sources should be added
275 wcs : `lsst.afw.geom.SkyWcs`
276 WCS to use to add fake sources
277 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
278 Photometric calibration to be used to calibrate the fake sources
282 resultStruct : `lsst.pipe.base.struct.Struct`
283 contains : image : `lsst.afw.image.exposure.exposure.ExposureF`
287 Adds pixel coordinates for each source to the fakeCat and removes objects with bulge or disk half
288 light radius = 0 (if ``config.doCleanCat = True``).
290 Adds the ``Fake`` mask plane to the image which is then set by `addFakeSources` to mark where fake
291 sources have been added. Uses the information in the ``fakeCat`` to make fake galaxies (using galsim)
292 and fake stars, using the PSF models from the PSF information for the image. These are then added to
293 the image and the image with fakes included returned.
295 The galsim galaxies are made using a double sersic profile, one for the bulge and one for the disk,
296 this is then convolved with the PSF at that point.
299 image.mask.addMaskPlane(
"FAKE")
300 self.bitmask = image.mask.getPlaneBitMask(
"FAKE")
301 self.log.
info(
"Adding mask plane with bitmask %d" % self.bitmask)
303 fakeCat = self.addPixCoords(fakeCat, wcs)
304 if self.config.doCleanCat:
305 fakeCat = self.cleanCat(fakeCat)
306 fakeCat = self.trimFakeCat(fakeCat, image, wcs)
308 band = image.getFilter().getName()
309 pixelScale = wcs.getPixelScale().asArcseconds()
312 galaxies = (fakeCat[self.config.sourceType] ==
"galaxy")
313 galImages = self.mkFakeGalsimGalaxies(fakeCat[galaxies], band, photoCalib, pixelScale, psf, image)
314 image = self.addFakeSources(image, galImages,
"galaxy")
316 stars = (fakeCat[self.config.sourceType] ==
"star")
317 starImages = self.mkFakeStars(fakeCat[stars], band, photoCalib, psf, image)
318 image = self.addFakeSources(image, starImages,
"star")
319 resultStruct = pipeBase.Struct(imageWithFakes=image)
323 def addPixCoords(self, fakeCat, wcs):
325 """Add pixel coordinates to the catalog of fakes.
329 fakeCat : `pandas.core.frame.DataFrame`
330 The catalog of fake sources to be input
331 wcs : `lsst.afw.geom.SkyWcs`
332 WCS to use to add fake sources
336 fakeCat : `pandas.core.frame.DataFrame`
340 The default option is to use the WCS information from the image. If the ``useUpdatedCalibs`` config
341 option is set then it will use the updated WCS from jointCal.
344 ras = fakeCat[self.config.raColName].values
345 decs = fakeCat[self.config.decColName].values
346 skyCoords = [
SpherePoint(ra, dec, radians)
for (ra, dec)
in zip(ras, decs)]
347 pixCoords = wcs.skyToPixel(skyCoords)
348 xs = [coord.getX()
for coord
in pixCoords]
349 ys = [coord.getY()
for coord
in pixCoords]
355 def trimFakeCat(self, fakeCat, image, wcs):
356 """Trim the fake cat to about the size of the input image.
360 fakeCat : `pandas.core.frame.DataFrame`
361 The catalog of fake sources to be input
362 image : `lsst.afw.image.exposure.exposure.ExposureF`
363 The image into which the fake sources should be added
364 wcs : `lsst.afw.geom.SkyWcs`
365 WCS to use to add fake sources
369 fakeCat : `pandas.core.frame.DataFrame`
370 The original fakeCat trimmed to the area of the image
373 bbox =
Box2D(image.getBBox())
374 corners = bbox.getCorners()
376 skyCorners = wcs.pixelToSky(corners)
380 coord =
SpherePoint(row[self.config.raColName], row[self.config.decColName], radians)
381 return region.contains(coord.getVector())
383 return fakeCat[fakeCat.apply(trim, axis=1)]
385 def mkFakeGalsimGalaxies(self, fakeCat, band, photoCalib, pixelScale, psf, image):
386 """Make images of fake galaxies using GalSim.
392 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
393 The PSF information to use to make the PSF images
394 fakeCat : `pandas.core.frame.DataFrame`
395 The catalog of fake sources to be input
396 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
397 Photometric calibration to be used to calibrate the fake sources
401 galImages : `generator`
402 A generator of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
403 `lsst.geom.Point2D` of their locations.
408 Fake galaxies are made by combining two sersic profiles, one for the bulge and one for the disk. Each
409 component has an individual sersic index (n), a, b and position angle (PA). The combined profile is
410 then convolved with the PSF at the specified x, y position on the image.
412 The names of the columns in the ``fakeCat`` are configurable and are the column names from the
413 University of Washington simulations database as default. For more information see the doc strings
414 attached to the config options.
416 See mkFakeStars doc string for an explanation of calibration to instrumental flux.
419 self.log.
info(
"Making %d fake galaxy images" % len(fakeCat))
421 for (index, row)
in fakeCat.iterrows():
425 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
426 psfKernel = psf.computeKernelImage(xy).getArray()
427 psfKernel /= correctedFlux
429 except InvalidParameterError:
430 self.log.
info(
"Galaxy at %0.4f, %0.4f outside of image" % (row[
"x"], row[
"y"]))
434 flux = photoCalib.magnitudeToInstFlux(row[self.config.magVar % band], xy)
438 bulge = galsim.Sersic(row[self.config.nBulge], half_light_radius=row[self.config.bulgeHLR])
439 axisRatioBulge = row[self.config.bBulge]/row[self.config.aBulge]
440 bulge = bulge.shear(q=axisRatioBulge, beta=((90 - row[self.config.paBulge])*galsim.degrees))
442 disk = galsim.Sersic(row[self.config.nDisk], half_light_radius=row[self.config.diskHLR])
443 axisRatioDisk = row[self.config.bDisk]/row[self.config.aDisk]
444 disk = disk.shear(q=axisRatioDisk, beta=((90 - row[self.config.paDisk])*galsim.degrees))
447 gal = gal.withFlux(flux)
449 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
450 gal = galsim.Convolve([gal, psfIm])
452 galIm = gal.drawImage(scale=pixelScale, method=
"real_space").array
453 except (galsim.errors.GalSimFFTSizeError, MemoryError):
456 yield (afwImage.ImageF(galIm), xy)
458 def mkFakeStars(self, fakeCat, band, photoCalib, psf, image):
460 """Make fake stars based off the properties in the fakeCat.
465 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
466 The PSF information to use to make the PSF images
467 fakeCat : `pandas.core.frame.DataFrame`
468 The catalog of fake sources to be input
469 image : `lsst.afw.image.exposure.exposure.ExposureF`
470 The image into which the fake sources should be added
471 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
472 Photometric calibration to be used to calibrate the fake sources
476 starImages : `generator`
477 A generator of tuples of `lsst.afw.image.ImageF` of fake stars and
478 `lsst.geom.Point2D` of their locations.
482 To take a given magnitude and translate to the number of counts in the image
483 we use photoCalib.magnitudeToInstFlux, which returns the instrumental flux for the
484 given calibration radius used in the photometric calibration step.
485 Thus `calibFluxRadius` should be set to this same radius so that we can normalize
486 the PSF model to the correct instrumental flux within calibFluxRadius.
489 self.log.
info(
"Making %d fake star images" % len(fakeCat))
491 for (index, row)
in fakeCat.iterrows():
497 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
498 starIm = psf.computeImage(xy)
499 starIm /= correctedFlux
501 except InvalidParameterError:
502 self.log.
info(
"Star at %0.4f, %0.4f outside of image" % (row[
"x"], row[
"y"]))
506 flux = photoCalib.magnitudeToInstFlux(row[self.config.magVar % band], xy)
511 yield ((starIm.convertF(), xy))
513 def cleanCat(self, fakeCat):
514 """Remove rows from the fakes catalog which have HLR = 0 for either the buldge or disk component
518 fakeCat : `pandas.core.frame.DataFrame`
519 The catalog of fake sources to be input
523 fakeCat : `pandas.core.frame.DataFrame`
524 The input catalog of fake sources but with the bad objects removed
527 goodRows = ((fakeCat[self.config.bulgeHLR] != 0.0) & (fakeCat[self.config.diskHLR] != 0.0))
529 badRows = len(fakeCat) - len(goodRows)
530 self.log.
info(
"Removing %d rows with HLR = 0 for either the bulge or disk" % badRows)
532 return fakeCat[goodRows]
534 def addFakeSources(self, image, fakeImages, sourceType):
535 """Add the fake sources to the given image
539 image : `lsst.afw.image.exposure.exposure.ExposureF`
540 The image into which the fake sources should be added
541 fakeImages : `typing.Iterator` [`tuple` ['lsst.afw.image.ImageF`, `lsst.geom.Point2d`]]
542 An iterator of tuples that contains (or generates) images of fake sources,
543 and the locations they are to be inserted at.
545 The type (star/galaxy) of fake sources input
549 image : `lsst.afw.image.exposure.exposure.ExposureF`
553 Uses the x, y information in the ``fakeCat`` to position an image of the fake interpolated onto the
554 pixel grid of the image. Sets the ``FAKE`` mask plane for the pixels added with the fake source.
557 imageBBox = image.getBBox()
558 imageMI = image.maskedImage
560 for (fakeImage, xy)
in fakeImages:
561 X0 = xy.getX() - fakeImage.getWidth()/2 + 0.5
562 Y0 = xy.getY() - fakeImage.getHeight()/2 + 0.5
563 self.log.
debug(
"Adding fake source at %d, %d" % (xy.getX(), xy.getY()))
564 if sourceType ==
"galaxy":
566 interpFakeImBBox = interpFakeImage.getBBox()
568 interpFakeImage = fakeImage
569 interpFakeImBBox = fakeImage.getBBox()
571 interpFakeImBBox.clip(imageBBox)
572 imageMIView = imageMI.Factory(imageMI, interpFakeImBBox)
574 if interpFakeImBBox.getArea() > 0:
575 clippedFakeImage = interpFakeImage.Factory(interpFakeImage, interpFakeImBBox)
576 clippedFakeImageMI = afwImage.MaskedImageF(clippedFakeImage)
577 clippedFakeImageMI.mask.set(self.bitmask)
578 imageMIView += clippedFakeImageMI
582 def _getMetadataName(self):
583 """Disable metadata writing"""