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")
69 class InsertFakesConfig(PipelineTaskConfig,
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=
"Radius for the calib flux (in pixels).",
185 coaddName = pexConfig.Field(
186 doc=
"The name of the type of coadd used",
192 class InsertFakesTask(PipelineTask, CmdLineTask):
193 """Insert fake objects into images.
195 Add fake stars and galaxies to the given image, read in through the dataRef. Galaxy parameters are read in
196 from the specified file and then modelled using galsim.
198 `InsertFakesTask` has five functions that make images of the fake sources and then add them to the
202 Use the WCS information to add the pixel coordinates of each source.
203 `mkFakeGalsimGalaxies`
204 Use Galsim to make fake double sersic galaxies for each set of galaxy parameters in the input file.
206 Use the PSF information from the image to make a fake star using the magnitude information from the
209 Remove rows of the input fake catalog which have half light radius, of either the bulge or the disk,
212 Add the fake sources to the image.
216 _DefaultName =
"insertFakes"
217 ConfigClass = InsertFakesConfig
219 def runDataRef(self, dataRef):
220 """Read in/write out the required data products and add fake sources to the deepCoadd.
224 dataRef : `lsst.daf.persistence.butlerSubset.ButlerDataRef`
225 Data reference defining the image to have fakes added to it
226 Used to access the following data products:
232 if self.config.fakeType ==
"static":
233 fakeCat = dataRef.get(
"deepCoadd_fakeSourceCat").toDataFrame()
236 self.fakeSourceCatType =
"deepCoadd_fakeSourceCat"
238 fakeCat = Table.read(self.config.fakeType).to_pandas()
240 coadd = dataRef.get(
"deepCoadd")
242 photoCalib = coadd.getPhotoCalib()
244 imageWithFakes = self.run(fakeCat, coadd, wcs, photoCalib)
246 dataRef.put(imageWithFakes.imageWithFakes,
"fakes_deepCoadd")
248 def runQuantum(self, butlerQC, inputRefs, outputRefs):
249 inputs = butlerQC.get(inputRefs)
250 inputs[
"wcs"] = inputs[
"image"].getWcs()
251 inputs[
"photoCalib"] = inputs[
"image"].getPhotoCalib()
253 outputs = self.run(**inputs)
254 butlerQC.put(outputs, outputRefs)
257 def _makeArgumentParser(cls):
258 parser = pipeBase.ArgumentParser(name=cls._DefaultName)
259 parser.add_id_argument(name=
"--id", datasetType=
"deepCoadd",
260 help=
"data IDs for the deepCoadd, e.g. --id tract=12345 patch=1,2 filter=r",
261 ContainerClass=ExistingCoaddDataIdContainer)
264 def run(self, fakeCat, image, wcs, photoCalib):
265 """Add fake sources to an image.
269 fakeCat : `pandas.core.frame.DataFrame`
270 The catalog of fake sources to be input
271 image : `lsst.afw.image.exposure.exposure.ExposureF`
272 The image into which the fake sources should be added
273 wcs : `lsst.afw.geom.SkyWcs`
274 WCS to use to add fake sources
275 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
276 Photometric calibration to be used to calibrate the fake sources
280 resultStruct : `lsst.pipe.base.struct.Struct`
281 contains : image : `lsst.afw.image.exposure.exposure.ExposureF`
285 Adds pixel coordinates for each source to the fakeCat and removes objects with bulge or disk half
286 light radius = 0 (if ``config.doCleanCat = True``).
288 Adds the ``Fake`` mask plane to the image which is then set by `addFakeSources` to mark where fake
289 sources have been added. Uses the information in the ``fakeCat`` to make fake galaxies (using galsim)
290 and fake stars, using the PSF models from the PSF information for the image. These are then added to
291 the image and the image with fakes included returned.
293 The galsim galaxies are made using a double sersic profile, one for the bulge and one for the disk,
294 this is then convolved with the PSF at that point.
297 image.mask.addMaskPlane(
"FAKE")
298 self.bitmask = image.mask.getPlaneBitMask(
"FAKE")
299 self.log.info(
"Adding mask plane with bitmask %d" % self.bitmask)
301 fakeCat = self.addPixCoords(fakeCat, wcs)
302 if self.config.doCleanCat:
303 fakeCat = self.cleanCat(fakeCat)
304 fakeCat = self.trimFakeCat(fakeCat, image, wcs)
306 band = image.getFilter().getName()
307 pixelScale = wcs.getPixelScale().asArcseconds()
310 galaxies = (fakeCat[self.config.sourceType] ==
"galaxy")
311 galImages = self.mkFakeGalsimGalaxies(fakeCat[galaxies], band, photoCalib, pixelScale, psf, image)
312 image = self.addFakeSources(image, galImages,
"galaxy")
314 stars = (fakeCat[self.config.sourceType] ==
"star")
315 starImages = self.mkFakeStars(fakeCat[stars], band, photoCalib, psf, image)
316 image = self.addFakeSources(image, starImages,
"star")
317 resultStruct = pipeBase.Struct(imageWithFakes=image)
321 def addPixCoords(self, fakeCat, wcs):
323 """Add pixel coordinates to the catalog of fakes.
327 fakeCat : `pandas.core.frame.DataFrame`
328 The catalog of fake sources to be input
329 wcs : `lsst.afw.geom.SkyWcs`
330 WCS to use to add fake sources
334 fakeCat : `pandas.core.frame.DataFrame`
338 The default option is to use the WCS information from the image. If the ``useUpdatedCalibs`` config
339 option is set then it will use the updated WCS from jointCal.
342 ras = fakeCat[self.config.raColName].values
343 decs = fakeCat[self.config.decColName].values
344 skyCoords = [
SpherePoint(ra, dec, radians)
for (ra, dec)
in zip(ras, decs)]
345 pixCoords = wcs.skyToPixel(skyCoords)
346 xs = [coord.getX()
for coord
in pixCoords]
347 ys = [coord.getY()
for coord
in pixCoords]
353 def trimFakeCat(self, fakeCat, image, wcs):
354 """Trim the fake cat to about the size of the input image.
358 fakeCat : `pandas.core.frame.DataFrame`
359 The catalog of fake sources to be input
360 image : `lsst.afw.image.exposure.exposure.ExposureF`
361 The image into which the fake sources should be added
362 wcs : `lsst.afw.geom.SkyWcs`
363 WCS to use to add fake sources
367 fakeCat : `pandas.core.frame.DataFrame`
368 The original fakeCat trimmed to the area of the image
371 bbox =
Box2D(image.getBBox())
372 corners = bbox.getCorners()
374 skyCorners = wcs.pixelToSky(corners)
378 coord =
SpherePoint(row[self.config.raColName], row[self.config.decColName], radians)
379 return region.contains(coord.getVector())
381 return fakeCat[fakeCat.apply(trim, axis=1)]
383 def mkFakeGalsimGalaxies(self, fakeCat, band, photoCalib, pixelScale, psf, image):
384 """Make images of fake galaxies using GalSim.
390 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
391 The PSF information to use to make the PSF images
392 fakeCat : `pandas.core.frame.DataFrame`
393 The catalog of fake sources to be input
394 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
395 Photometric calibration to be used to calibrate the fake sources
399 galImages : `generator`
400 A generator of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
401 `lsst.geom.Point2D` of their locations.
406 Fake galaxies are made by combining two sersic profiles, one for the bulge and one for the disk. Each
407 component has an individual sersic index (n), a, b and position angle (PA). The combined profile is
408 then convolved with the PSF at the specified x, y position on the image.
410 The names of the columns in the ``fakeCat`` are configurable and are the column names from the
411 University of Washington simulations database as default. For more information see the doc strings
412 attached to the config options.
415 self.log.info(
"Making %d fake galaxy images" % len(fakeCat))
417 for (index, row)
in fakeCat.iterrows():
423 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
424 psfKernel = psf.computeKernelImage(xy).getArray()
425 psfKernel /= correctedFlux
427 except InvalidParameterError:
428 self.log.info(
"Galaxy at %0.4f, %0.4f outside of image" % (row[
"x"], row[
"y"]))
432 flux = photoCalib.magnitudeToInstFlux(row[self.config.magVar % band], xy)
436 bulge = galsim.Sersic(row[self.config.nBulge], half_light_radius=row[self.config.bulgeHLR])
437 axisRatioBulge = row[self.config.bBulge]/row[self.config.aBulge]
438 bulge = bulge.shear(q=axisRatioBulge, beta=((90 - row[self.config.paBulge])*galsim.degrees))
440 disk = galsim.Sersic(row[self.config.nDisk], half_light_radius=row[self.config.diskHLR])
441 axisRatioDisk = row[self.config.bDisk]/row[self.config.aDisk]
442 disk = disk.shear(q=axisRatioDisk, beta=((90 - row[self.config.paDisk])*galsim.degrees))
445 gal = gal.withFlux(flux)
447 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
448 gal = galsim.Convolve([gal, psfIm])
450 galIm = gal.drawImage(scale=pixelScale, method=
"real_space").array
451 except (galsim.errors.GalSimFFTSizeError, MemoryError):
454 yield (afwImage.ImageF(galIm), xy)
456 def mkFakeStars(self, fakeCat, band, photoCalib, psf, image):
458 """Make fake stars based off the properties in the fakeCat.
463 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
464 The PSF information to use to make the PSF images
465 fakeCat : `pandas.core.frame.DataFrame`
466 The catalog of fake sources to be input
467 image : `lsst.afw.image.exposure.exposure.ExposureF`
468 The image into which the fake sources should be added
469 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
470 Photometric calibration to be used to calibrate the fake sources
474 starImages : `generator`
475 A generator of tuples of `lsst.afw.image.ImageF` of fake stars and
476 `lsst.geom.Point2D` of their locations.
479 self.log.info(
"Making %d fake star images" % len(fakeCat))
481 for (index, row)
in fakeCat.iterrows():
487 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
488 starIm = psf.computeImage(xy)
489 starIm /= correctedFlux
491 except InvalidParameterError:
492 self.log.info(
"Star at %0.4f, %0.4f outside of image" % (row[
"x"], row[
"y"]))
496 flux = photoCalib.magnitudeToInstFlux(row[self.config.magVar % band], xy)
501 yield ((starIm.convertF(), xy))
503 def cleanCat(self, fakeCat):
504 """Remove rows from the fakes catalog which have HLR = 0 for either the buldge or disk component
508 fakeCat : `pandas.core.frame.DataFrame`
509 The catalog of fake sources to be input
513 fakeCat : `pandas.core.frame.DataFrame`
514 The input catalog of fake sources but with the bad objects removed
517 goodRows = ((fakeCat[self.config.bulgeHLR] != 0.0) & (fakeCat[self.config.diskHLR] != 0.0))
519 badRows = len(fakeCat) - len(goodRows)
520 self.log.info(
"Removing %d rows with HLR = 0 for either the bulge or disk" % badRows)
522 return fakeCat[goodRows]
524 def addFakeSources(self, image, fakeImages, sourceType):
525 """Add the fake sources to the given image
529 image : `lsst.afw.image.exposure.exposure.ExposureF`
530 The image into which the fake sources should be added
531 fakeImages : `typing.Iterator` [`tuple` ['lsst.afw.image.ImageF`, `lsst.geom.Point2d`]]
532 An iterator of tuples that contains (or generates) images of fake sources,
533 and the locations they are to be inserted at.
535 The type (star/galaxy) of fake sources input
539 image : `lsst.afw.image.exposure.exposure.ExposureF`
543 Uses the x, y information in the ``fakeCat`` to position an image of the fake interpolated onto the
544 pixel grid of the image. Sets the ``FAKE`` mask plane for the pixels added with the fake source.
547 imageBBox = image.getBBox()
548 imageMI = image.maskedImage
550 for (fakeImage, xy)
in fakeImages:
551 X0 = xy.getX() - fakeImage.getWidth()/2 + 0.5
552 Y0 = xy.getY() - fakeImage.getHeight()/2 + 0.5
553 self.log.debug(
"Adding fake source at %d, %d" % (xy.getX(), xy.getY()))
554 if sourceType ==
"galaxy":
555 interpFakeImage = afwMath.offsetImage(fakeImage, X0, Y0,
"lanczos3")
556 interpFakeImBBox = interpFakeImage.getBBox()
558 interpFakeImage = fakeImage
559 interpFakeImBBox = fakeImage.getBBox()
561 interpFakeImBBox.clip(imageBBox)
562 imageMIView = imageMI.Factory(imageMI, interpFakeImBBox)
564 if interpFakeImBBox.getArea() > 0:
565 clippedFakeImage = interpFakeImage.Factory(interpFakeImage, interpFakeImBBox)
566 clippedFakeImageMI = afwImage.MaskedImageF(clippedFakeImage)
567 clippedFakeImageMI.mask.set(self.bitmask)
568 imageMIView += clippedFakeImageMI
572 def _getMetadataName(self):
573 """Disable metadata writing"""