23 Insert fakes into deepCoadds
26 from astropy.table
import Table
35 from lsst.pipe.base import CmdLineTask, PipelineTask, PipelineTaskConfig, PipelineTaskConnections
36 import lsst.pipe.base.connectionTypes
as cT
39 from lsst.geom import SpherePoint, radians, Box2D
41 __all__ = [
"InsertFakesConfig",
"InsertFakesTask"]
44 def _add_fake_sources(exposure, objects, calibFluxRadius=12.0, logger=None):
45 """Add fake sources to the given exposure
49 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
50 The exposure into which the fake sources should be added
51 objects : `typing.Iterator` [`tuple` ['lsst.geom.SpherePoint`, `galsim.GSObject`]]
52 An iterator of tuples that contains (or generates) locations and object
53 surface brightness profiles to inject.
54 calibFluxRadius : `float`, optional
55 Aperture radius (in pixels) used to define the calibration for this
56 exposure+catalog. This is used to produce the correct instrumental fluxes
57 within the radius. The value should match that of the field defined in
58 slot_CalibFlux_instFlux.
59 logger : `lsst.log.log.log.Log` or `logging.Logger`, optional
62 exposure.mask.addMaskPlane(
"FAKE")
63 bitmask = exposure.mask.getPlaneBitMask(
"FAKE")
65 logger.info(f
"Adding mask plane with bitmask {bitmask}")
67 wcs = exposure.getWcs()
68 psf = exposure.getPsf()
70 bbox = exposure.getBBox()
71 fullBounds = galsim.BoundsI(bbox.minX, bbox.maxX, bbox.minY, bbox.maxY)
72 gsImg = galsim.Image(exposure.image.array, bounds=fullBounds)
74 for spt, gsObj
in objects:
75 pt = wcs.skyToPixel(spt)
76 posd = galsim.PositionD(pt.x, pt.y)
77 posi = galsim.PositionI(pt.x//1, pt.y//1)
79 logger.debug(f
"Adding fake source at {pt}")
81 mat = wcs.linearizePixelToSky(spt, geom.arcseconds).getMatrix()
82 gsWCS = galsim.JacobianWCS(mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1])
84 psfArr = psf.computeKernelImage(pt).array
85 apCorr = psf.computeApertureFlux(calibFluxRadius)
87 gsPSF = galsim.InterpolatedImage(galsim.Image(psfArr), wcs=gsWCS)
89 conv = galsim.Convolve(gsObj, gsPSF)
90 stampSize = conv.getGoodImageSize(gsWCS.minLinearScale())
91 subBounds = galsim.BoundsI(posi).withBorder(stampSize//2)
92 subBounds &= fullBounds
94 if subBounds.area() > 0:
95 subImg = gsImg[subBounds]
96 offset = posd - subBounds.true_center
112 exposure[subBox].mask.array |= bitmask
115 def _isWCSGalsimDefault(wcs, hdr):
116 """Decide if wcs = galsim.PixelScale(1.0) is explicitly present in header,
117 or if it's just the galsim default.
122 Potentially default WCS.
123 hdr : galsim.fits.FitsHeader
124 Header as read in by galsim.
129 True if default, False if explicitly set in header.
131 if wcs != galsim.PixelScale(1.0):
133 if hdr.get(
'GS_WCS')
is not None:
135 if hdr.get(
'CTYPE1',
'LINEAR') ==
'LINEAR':
136 return not any(k
in hdr
for k
in [
'CD1_1',
'CDELT1'])
137 for wcs_type
in galsim.fitswcs.fits_wcs_types:
140 wcs_type._readHeader(hdr)
145 return not any(k
in hdr
for k
in [
'CD1_1',
'CDELT1'])
149 defaultTemplates={
"coaddName":
"deep",
150 "fakesType":
"fakes_"},
151 dimensions=(
"tract",
"patch",
"band",
"skymap")):
154 doc=
"Image into which fakes are to be added.",
155 name=
"{coaddName}Coadd",
156 storageClass=
"ExposureF",
157 dimensions=(
"tract",
"patch",
"band",
"skymap")
161 doc=
"Catalog of fake sources to draw inputs from.",
162 name=
"{fakesType}fakeSourceCat",
163 storageClass=
"DataFrame",
164 dimensions=(
"tract",
"skymap")
167 imageWithFakes = cT.Output(
168 doc=
"Image with fake sources added.",
169 name=
"{fakesType}{coaddName}Coadd",
170 storageClass=
"ExposureF",
171 dimensions=(
"tract",
"patch",
"band",
"skymap")
175 class InsertFakesConfig(PipelineTaskConfig,
176 pipelineConnections=InsertFakesConnections):
177 """Config for inserting fake sources
182 doCleanCat = pexConfig.Field(
183 doc=
"If true removes bad sources from the catalog.",
188 fakeType = pexConfig.Field(
189 doc=
"What type of fake catalog to use, snapshot (includes variability in the magnitudes calculated "
190 "from the MJD of the image), static (no variability) or filename for a user defined fits"
196 calibFluxRadius = pexConfig.Field(
197 doc=
"Aperture radius (in pixels) that was used to define the calibration for this image+catalog. "
198 "This will be used to produce the correct instrumental fluxes within the radius. "
199 "This value should match that of the field defined in slot_CalibFlux_instFlux.",
204 coaddName = pexConfig.Field(
205 doc=
"The name of the type of coadd used",
210 doSubSelectSources = pexConfig.Field(
211 doc=
"Set to True if you wish to sub select sources to be input based on the value in the column"
212 "set in the sourceSelectionColName config option.",
217 insertImages = pexConfig.Field(
218 doc=
"Insert images directly? True or False.",
223 doProcessAllDataIds = pexConfig.Field(
224 doc=
"If True, all input data IDs will be processed, even those containing no fake sources.",
229 trimBuffer = pexConfig.Field(
230 doc=
"Size of the pixel buffer surrounding the image. Only those fake sources with a centroid"
231 "falling within the image+buffer region will be considered for fake source injection.",
236 sourceType = pexConfig.Field(
237 doc=
"The column name for the source type used in the fake source catalog.",
239 default=
"sourceType",
244 ra_col = pexConfig.Field(
245 doc=
"Source catalog column name for RA (in radians).",
250 dec_col = pexConfig.Field(
251 doc=
"Source catalog column name for dec (in radians).",
256 bulge_semimajor_col = pexConfig.Field(
257 doc=
"Source catalog column name for the semimajor axis (in arcseconds) "
258 "of the bulge half-light ellipse.",
260 default=
"bulge_semimajor",
263 bulge_axis_ratio_col = pexConfig.Field(
264 doc=
"Source catalog column name for the axis ratio of the bulge "
265 "half-light ellipse.",
267 default=
"bulge_axis_ratio",
270 bulge_pa_col = pexConfig.Field(
271 doc=
"Source catalog column name for the position angle (measured from "
272 "North through East in degrees) of the semimajor axis of the bulge "
273 "half-light ellipse.",
278 bulge_n_col = pexConfig.Field(
279 doc=
"Source catalog column name for the Sersic index of the bulge.",
284 disk_semimajor_col = pexConfig.Field(
285 doc=
"Source catalog column name for the semimajor axis (in arcseconds) "
286 "of the disk half-light ellipse.",
288 default=
"disk_semimajor",
291 disk_axis_ratio_col = pexConfig.Field(
292 doc=
"Source catalog column name for the axis ratio of the disk "
293 "half-light ellipse.",
295 default=
"disk_axis_ratio",
298 disk_pa_col = pexConfig.Field(
299 doc=
"Source catalog column name for the position angle (measured from "
300 "North through East in degrees) of the semimajor axis of the disk "
301 "half-light ellipse.",
306 disk_n_col = pexConfig.Field(
307 doc=
"Source catalog column name for the Sersic index of the disk.",
312 bulge_disk_flux_ratio_col = pexConfig.Field(
313 doc=
"Source catalog column name for the bulge/disk flux ratio.",
315 default=
"bulge_disk_flux_ratio",
318 mag_col = pexConfig.Field(
319 doc=
"Source catalog column name template for magnitudes, in the format "
320 "``filter name``_mag_col. E.g., if this config variable is set to "
321 "``%s_mag``, then the i-band magnitude will be searched for in the "
322 "``i_mag`` column of the source catalog.",
327 select_col = pexConfig.Field(
328 doc=
"Source catalog column name to be used to select which sources to "
336 raColName = pexConfig.Field(
337 doc=
"RA column name used in the fake source catalog.",
340 deprecated=
"Use `ra_col` instead."
343 decColName = pexConfig.Field(
344 doc=
"Dec. column name used in the fake source catalog.",
347 deprecated=
"Use `dec_col` instead."
350 diskHLR = pexConfig.Field(
351 doc=
"Column name for the disk half light radius used in the fake source catalog.",
353 default=
"DiskHalfLightRadius",
355 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
356 " to specify disk half-light ellipse."
360 aDisk = pexConfig.Field(
361 doc=
"The column name for the semi major axis length of the disk component used in the fake source"
366 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
367 " to specify disk half-light ellipse."
371 bDisk = pexConfig.Field(
372 doc=
"The column name for the semi minor axis length of the disk component.",
376 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
377 " to specify disk half-light ellipse."
381 paDisk = pexConfig.Field(
382 doc=
"The column name for the PA of the disk component used in the fake source catalog.",
386 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
387 " to specify disk half-light ellipse."
391 nDisk = pexConfig.Field(
392 doc=
"The column name for the sersic index of the disk component used in the fake source catalog.",
395 deprecated=
"Use `disk_n` instead."
398 bulgeHLR = pexConfig.Field(
399 doc=
"Column name for the bulge half light radius used in the fake source catalog.",
401 default=
"BulgeHalfLightRadius",
403 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
404 "`bulge_pa_col` to specify disk half-light ellipse."
408 aBulge = pexConfig.Field(
409 doc=
"The column name for the semi major axis length of the bulge component.",
413 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
414 "`bulge_pa_col` to specify disk half-light ellipse."
418 bBulge = pexConfig.Field(
419 doc=
"The column name for the semi minor axis length of the bulge component used in the fake source "
424 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
425 "`bulge_pa_col` to specify disk half-light ellipse."
429 paBulge = pexConfig.Field(
430 doc=
"The column name for the PA of the bulge component used in the fake source catalog.",
434 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
435 "`bulge_pa_col` to specify disk half-light ellipse."
439 nBulge = pexConfig.Field(
440 doc=
"The column name for the sersic index of the bulge component used in the fake source catalog.",
443 deprecated=
"Use `bulge_n` instead."
446 magVar = pexConfig.Field(
447 doc=
"The column name for the magnitude calculated taking variability into account. In the format "
448 "``filter name``magVar, e.g. imagVar for the magnitude in the i band.",
451 deprecated=
"Use `mag_col` instead."
454 sourceSelectionColName = pexConfig.Field(
455 doc=
"The name of the column in the input fakes catalogue to be used to determine which sources to"
456 "add, default is none and when this is used all sources are added.",
458 default=
"templateSource",
459 deprecated=
"Use `select_col` instead."
463 class InsertFakesTask(PipelineTask, CmdLineTask):
464 """Insert fake objects into images.
466 Add fake stars and galaxies to the given image, read in through the dataRef. Galaxy parameters are read in
467 from the specified file and then modelled using galsim.
469 `InsertFakesTask` has five functions that make images of the fake sources and then add them to the
473 Use the WCS information to add the pixel coordinates of each source.
474 `mkFakeGalsimGalaxies`
475 Use Galsim to make fake double sersic galaxies for each set of galaxy parameters in the input file.
477 Use the PSF information from the image to make a fake star using the magnitude information from the
480 Remove rows of the input fake catalog which have half light radius, of either the bulge or the disk,
481 that are 0. Also removes rows that have Sersic index outside of galsim's allowed paramters. If
482 the config option sourceSelectionColName is set then this function limits the catalog of input fakes
483 to only those which are True in this column.
485 Add the fake sources to the image.
489 _DefaultName =
"insertFakes"
490 ConfigClass = InsertFakesConfig
492 def runDataRef(self, dataRef):
493 """Read in/write out the required data products and add fake sources to the deepCoadd.
497 dataRef : `lsst.daf.persistence.butlerSubset.ButlerDataRef`
498 Data reference defining the image to have fakes added to it
499 Used to access the following data products:
503 infoStr =
"Adding fakes to: tract: %d, patch: %s, filter: %s" % (dataRef.dataId[
"tract"],
504 dataRef.dataId[
"patch"],
505 dataRef.dataId[
"filter"])
506 self.log.info(infoStr)
510 if self.config.fakeType ==
"static":
511 fakeCat = dataRef.get(
"deepCoadd_fakeSourceCat").toDataFrame()
514 self.fakeSourceCatType =
"deepCoadd_fakeSourceCat"
516 fakeCat = Table.read(self.config.fakeType).to_pandas()
518 coadd = dataRef.get(
"deepCoadd")
520 photoCalib = coadd.getPhotoCalib()
522 imageWithFakes = self.run(fakeCat, coadd, wcs, photoCalib)
524 dataRef.put(imageWithFakes.imageWithFakes,
"fakes_deepCoadd")
526 def runQuantum(self, butlerQC, inputRefs, outputRefs):
527 inputs = butlerQC.get(inputRefs)
528 inputs[
"wcs"] = inputs[
"image"].getWcs()
529 inputs[
"photoCalib"] = inputs[
"image"].getPhotoCalib()
531 outputs = self.run(**inputs)
532 butlerQC.put(outputs, outputRefs)
535 def _makeArgumentParser(cls):
536 parser = pipeBase.ArgumentParser(name=cls._DefaultName)
537 parser.add_id_argument(name=
"--id", datasetType=
"deepCoadd",
538 help=
"data IDs for the deepCoadd, e.g. --id tract=12345 patch=1,2 filter=r",
539 ContainerClass=ExistingCoaddDataIdContainer)
542 def run(self, fakeCat, image, wcs, photoCalib):
543 """Add fake sources to an image.
547 fakeCat : `pandas.core.frame.DataFrame`
548 The catalog of fake sources to be input
549 image : `lsst.afw.image.exposure.exposure.ExposureF`
550 The image into which the fake sources should be added
551 wcs : `lsst.afw.geom.SkyWcs`
552 WCS to use to add fake sources
553 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
554 Photometric calibration to be used to calibrate the fake sources
558 resultStruct : `lsst.pipe.base.struct.Struct`
559 contains : image : `lsst.afw.image.exposure.exposure.ExposureF`
563 Adds pixel coordinates for each source to the fakeCat and removes objects with bulge or disk half
564 light radius = 0 (if ``config.doCleanCat = True``).
566 Adds the ``Fake`` mask plane to the image which is then set by `addFakeSources` to mark where fake
567 sources have been added. Uses the information in the ``fakeCat`` to make fake galaxies (using galsim)
568 and fake stars, using the PSF models from the PSF information for the image. These are then added to
569 the image and the image with fakes included returned.
571 The galsim galaxies are made using a double sersic profile, one for the bulge and one for the disk,
572 this is then convolved with the PSF at that point.
576 origWcs = image.getWcs()
577 origPhotoCalib = image.getPhotoCalib()
579 image.setPhotoCalib(photoCalib)
581 band = image.getFilterLabel().bandLabel
582 fakeCat = self._standardizeColumns(fakeCat, band)
584 fakeCat = self.addPixCoords(fakeCat, image)
585 fakeCat = self.trimFakeCat(fakeCat, image)
588 if isinstance(fakeCat[self.config.sourceType].iloc[0], str):
589 galCheckVal =
"galaxy"
590 starCheckVal =
"star"
591 elif isinstance(fakeCat[self.config.sourceType].iloc[0], bytes):
592 galCheckVal = b
"galaxy"
593 starCheckVal = b
"star"
594 elif isinstance(fakeCat[self.config.sourceType].iloc[0], (int, float)):
598 raise TypeError(
"sourceType column does not have required type, should be str, bytes or int")
600 if not self.config.insertImages:
601 if self.config.doCleanCat:
602 fakeCat = self.cleanCat(fakeCat, starCheckVal)
604 generator = self._generateGSObjectsFromCatalog(image, fakeCat, galCheckVal, starCheckVal)
606 generator = self._generateGSObjectsFromImages(image, fakeCat)
607 _add_fake_sources(image, generator, calibFluxRadius=self.config.calibFluxRadius, logger=self.log)
608 elif len(fakeCat) == 0
and self.config.doProcessAllDataIds:
609 self.log.warn(
"No fakes found for this dataRef; processing anyway.")
610 image.mask.addMaskPlane(
"FAKE")
612 raise RuntimeError(
"No fakes found for this dataRef.")
615 image.setWcs(origWcs)
616 image.setPhotoCalib(origPhotoCalib)
618 resultStruct = pipeBase.Struct(imageWithFakes=image)
622 def _standardizeColumns(self, fakeCat, band):
623 """Use config variables to 'standardize' the expected columns and column
624 names in the input catalog.
628 fakeCat : `pandas.core.frame.DataFrame`
629 The catalog of fake sources to be input
631 Label for the current band being processed.
635 outCat : `pandas.core.frame.DataFrame`
636 The standardized catalog of fake sources
644 for new_name, depr_name, std_name
in [
645 (cfg.ra_col, cfg.raColName,
'ra'),
646 (cfg.dec_col, cfg.decColName,
'dec'),
647 (cfg.bulge_n_col, cfg.nBulge,
'bulge_n'),
648 (cfg.bulge_pa_col, cfg.paBulge,
'bulge_pa'),
649 (cfg.disk_n_col, cfg.nDisk,
'disk_n'),
650 (cfg.disk_pa_col, cfg.paDisk,
'disk_pa'),
651 (cfg.mag_col%band, cfg.magVar%band,
'mag'),
652 (cfg.select_col, cfg.sourceSelectionColName,
'select')
655 if not cfg.doSubSelectSources
and std_name ==
'select':
657 if new_name
in fakeCat.columns:
658 replace_dict[new_name] = std_name
659 elif depr_name
in fakeCat.columns:
660 replace_dict[depr_name] = std_name
662 raise ValueError(f
"Could not determine column for {std_name}.")
663 fakeCat = fakeCat.rename(columns=replace_dict, copy=
False)
669 cfg.bulge_semimajor_col
in fakeCat.columns
670 and cfg.bulge_axis_ratio_col
in fakeCat.columns
672 fakeCat = fakeCat.rename(
674 cfg.bulge_semimajor_col:
'bulge_semimajor',
675 cfg.bulge_axis_ratio_col:
'bulge_axis_ratio',
676 cfg.disk_semimajor_col:
'disk_semimajor',
677 cfg.disk_axis_ratio_col:
'disk_axis_ratio',
682 cfg.bulgeHLR
in fakeCat.columns
683 and cfg.aBulge
in fakeCat.columns
684 and cfg.bBulge
in fakeCat.columns
686 fakeCat[
'bulge_axis_ratio'] = (
687 fakeCat[cfg.bBulge]/fakeCat[cfg.aBulge]
689 fakeCat[
'bulge_semimajor'] = (
690 fakeCat[cfg.bulgeHLR]/np.sqrt(fakeCat[
'bulge_axis_ratio'])
692 fakeCat[
'disk_axis_ratio'] = (
693 fakeCat[cfg.bDisk]/fakeCat[cfg.aDisk]
695 fakeCat[
'disk_semimajor'] = (
696 fakeCat[cfg.diskHLR]/np.sqrt(fakeCat[
'disk_axis_ratio'])
700 "Could not determine columns for half-light radius and axis "
705 if cfg.bulge_disk_flux_ratio_col
in fakeCat.columns:
706 fakeCat = fakeCat.rename(
708 cfg.bulge_disk_flux_ratio_col:
'bulge_disk_flux_ratio'
713 fakeCat[
'bulge_disk_flux_ratio'] = 1.0
717 def _generateGSObjectsFromCatalog(self, exposure, fakeCat, galCheckVal, starCheckVal):
718 """Process catalog to generate `galsim.GSObject` s.
722 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
723 The exposure into which the fake sources should be added
724 fakeCat : `pandas.core.frame.DataFrame`
725 The catalog of fake sources to be input
726 galCheckVal : `str`, `bytes` or `int`
727 The value that is set in the sourceType column to specifiy an object is a galaxy.
728 starCheckVal : `str`, `bytes` or `int`
729 The value that is set in the sourceType column to specifiy an object is a star.
733 gsObjects : `generator`
734 A generator of tuples of `lsst.geom.SpherePoint` and `galsim.GSObject`.
736 wcs = exposure.getWcs()
737 photoCalib = exposure.getPhotoCalib()
739 self.log.info(f
"Making {len(fakeCat)} objects for insertion")
741 for (index, row)
in fakeCat.iterrows():
745 xy = wcs.skyToPixel(skyCoord)
748 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
752 sourceType = row[self.config.sourceType]
753 if sourceType == galCheckVal:
755 bulge_gs_HLR = row[
'bulge_semimajor']*np.sqrt(row[
'bulge_axis_ratio'])
756 bulge = galsim.Sersic(n=row[
'bulge_n'], half_light_radius=bulge_gs_HLR)
757 bulge = bulge.shear(q=row[
'bulge_axis_ratio'], beta=((90 - row[
'bulge_pa'])*galsim.degrees))
759 disk_gs_HLR = row[
'disk_semimajor']*np.sqrt(row[
'disk_axis_ratio'])
760 disk = galsim.Sersic(n=row[
'disk_n'], half_light_radius=disk_gs_HLR)
761 disk = disk.shear(q=row[
'disk_axis_ratio'], beta=((90 - row[
'disk_pa'])*galsim.degrees))
763 gal = bulge*row[
'bulge_disk_flux_ratio'] + disk
764 gal = gal.withFlux(flux)
767 elif sourceType == starCheckVal:
768 star = galsim.DeltaFunction()
769 star = star.withFlux(flux)
772 raise TypeError(f
"Unknown sourceType {sourceType}")
774 def _generateGSObjectsFromImages(self, exposure, fakeCat):
775 """Process catalog to generate `galsim.GSObject` s.
779 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
780 The exposure into which the fake sources should be added
781 fakeCat : `pandas.core.frame.DataFrame`
782 The catalog of fake sources to be input
786 gsObjects : `generator`
787 A generator of tuples of `lsst.geom.SpherePoint` and `galsim.GSObject`.
789 band = exposure.getFilterLabel().bandLabel
790 wcs = exposure.getWcs()
791 photoCalib = exposure.getPhotoCalib()
793 self.log.info(f
"Processing {len(fakeCat)} fake images")
795 for (index, row)
in fakeCat.iterrows():
799 xy = wcs.skyToPixel(skyCoord)
802 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
806 imFile = row[band+
"imFilename"]
808 imFile = imFile.decode(
"utf-8")
809 except AttributeError:
811 imFile = imFile.strip()
812 im = galsim.fits.read(imFile, read_header=
True)
820 if _isWCSGalsimDefault(im.wcs, im.header):
821 im.wcs = galsim.PixelScale(
822 wcs.getPixelScale().asArcseconds()
825 obj = galsim.InterpolatedImage(im)
826 obj = obj.withFlux(flux)
830 """Process images from files into the format needed for insertion.
834 fakeCat : `pandas.core.frame.DataFrame`
835 The catalog of fake sources to be input
836 wcs : `lsst.afw.geom.skyWcs.skyWcs.SkyWc`
837 WCS to use to add fake sources
838 psf : `lsst.meas.algorithms.coaddPsf.coaddPsf.CoaddPsf` or
839 `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
840 The PSF information to use to make the PSF images
841 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
842 Photometric calibration to be used to calibrate the fake sources
844 The filter band that the observation was taken in.
846 The pixel scale of the image the sources are to be added to.
851 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
852 `lsst.geom.Point2D` of their locations.
853 For sources labelled as galaxy.
855 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
856 `lsst.geom.Point2D` of their locations.
857 For sources labelled as star.
861 The input fakes catalog needs to contain the absolute path to the image in the
862 band that is being used to add images to. It also needs to have the R.A. and
863 declination of the fake source in radians and the sourceType of the object.
868 self.log.info(
"Processing %d fake images" % len(fakeCat))
870 for (imFile, sourceType, mag, x, y)
in zip(fakeCat[band +
"imFilename"].array,
871 fakeCat[
"sourceType"].array,
872 fakeCat[
'mag'].array,
873 fakeCat[
"x"].array, fakeCat[
"y"].array):
875 im = afwImage.ImageF.readFits(imFile)
882 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
883 psfKernel = psf.computeKernelImage(xy).getArray()
884 psfKernel /= correctedFlux
886 except InvalidParameterError:
887 self.log.info(
"%s at %0.4f, %0.4f outside of image" % (sourceType, x, y))
890 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
891 galsimIm = galsim.InterpolatedImage(galsim.Image(im.array), scale=pixelScale)
892 convIm = galsim.Convolve([galsimIm, psfIm])
895 outIm = convIm.drawImage(scale=pixelScale, method=
"real_space").array
896 except (galsim.errors.GalSimFFTSizeError, MemoryError):
899 imSum = np.sum(outIm)
903 flux = photoCalib.magnitudeToInstFlux(mag, xy)
907 imWithFlux = flux*divIm
909 if sourceType == b
"galaxy":
910 galImages.append((afwImage.ImageF(imWithFlux), xy))
911 if sourceType == b
"star":
912 starImages.append((afwImage.ImageF(imWithFlux), xy))
914 return galImages, starImages
918 """Add pixel coordinates to the catalog of fakes.
922 fakeCat : `pandas.core.frame.DataFrame`
923 The catalog of fake sources to be input
924 image : `lsst.afw.image.exposure.exposure.ExposureF`
925 The image into which the fake sources should be added
929 fakeCat : `pandas.core.frame.DataFrame`
932 ras = fakeCat[
'ra'].values
933 decs = fakeCat[
'dec'].values
934 xs, ys = wcs.skyToPixelArray(ras, decs)
941 """Trim the fake cat to about the size of the input image.
943 `fakeCat` must be processed with addPixCoords before using this method.
947 fakeCat : `pandas.core.frame.DataFrame`
948 The catalog of fake sources to be input
949 image : `lsst.afw.image.exposure.exposure.ExposureF`
950 The image into which the fake sources should be added
954 fakeCat : `pandas.core.frame.DataFrame`
955 The original fakeCat trimmed to the area of the image
958 bbox =
Box2D(image.getBBox()).dilatedBy(self.config.trimBuffer)
959 xs = fakeCat[
"x"].values
960 ys = fakeCat[
"y"].values
962 isContained = xs >= bbox.minX
963 isContained &= xs <= bbox.maxX
964 isContained &= ys >= bbox.minY
965 isContained &= ys <= bbox.maxY
967 return fakeCat[isContained]
970 """Make images of fake galaxies using GalSim.
976 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
977 The PSF information to use to make the PSF images
978 fakeCat : `pandas.core.frame.DataFrame`
979 The catalog of fake sources to be input
980 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
981 Photometric calibration to be used to calibrate the fake sources
985 galImages : `generator`
986 A generator of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
987 `lsst.geom.Point2D` of their locations.
992 Fake galaxies are made by combining two sersic profiles, one for the bulge and one for the disk. Each
993 component has an individual sersic index (n), a, b and position angle (PA). The combined profile is
994 then convolved with the PSF at the specified x, y position on the image.
996 The names of the columns in the ``fakeCat`` are configurable and are the column names from the
997 University of Washington simulations database as default. For more information see the doc strings
998 attached to the config options.
1000 See mkFakeStars doc string for an explanation of calibration to instrumental flux.
1003 self.log.info(
"Making %d fake galaxy images" % len(fakeCat))
1005 for (index, row)
in fakeCat.iterrows():
1011 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1012 psfKernel = psf.computeKernelImage(xy).getArray()
1013 psfKernel /= correctedFlux
1015 except InvalidParameterError:
1016 self.log.info(
"Galaxy at %0.4f, %0.4f outside of image" % (row[
"x"], row[
"y"]))
1020 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
1025 bulge_gs_HLR = row[
'bulge_semimajor']*np.sqrt(row[
'bulge_axis_ratio'])
1026 bulge = galsim.Sersic(n=row[
'bulge_n'], half_light_radius=bulge_gs_HLR)
1027 bulge = bulge.shear(q=row[
'bulge_axis_ratio'], beta=((90 - row[
'bulge_pa'])*galsim.degrees))
1029 disk_gs_HLR = row[
'disk_semimajor']*np.sqrt(row[
'disk_axis_ratio'])
1030 disk = galsim.Sersic(n=row[
'disk_n'], half_light_radius=disk_gs_HLR)
1031 disk = disk.shear(q=row[
'disk_axis_ratio'], beta=((90 - row[
'disk_pa'])*galsim.degrees))
1033 gal = bulge*row[
'bulge_disk_flux_ratio'] + disk
1034 gal = gal.withFlux(flux)
1036 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
1037 gal = galsim.Convolve([gal, psfIm])
1039 galIm = gal.drawImage(scale=pixelScale, method=
"real_space").array
1040 except (galsim.errors.GalSimFFTSizeError, MemoryError):
1043 yield (afwImage.ImageF(galIm), xy)
1047 """Make fake stars based off the properties in the fakeCat.
1052 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
1053 The PSF information to use to make the PSF images
1054 fakeCat : `pandas.core.frame.DataFrame`
1055 The catalog of fake sources to be input
1056 image : `lsst.afw.image.exposure.exposure.ExposureF`
1057 The image into which the fake sources should be added
1058 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
1059 Photometric calibration to be used to calibrate the fake sources
1063 starImages : `generator`
1064 A generator of tuples of `lsst.afw.image.ImageF` of fake stars and
1065 `lsst.geom.Point2D` of their locations.
1069 To take a given magnitude and translate to the number of counts in the image
1070 we use photoCalib.magnitudeToInstFlux, which returns the instrumental flux for the
1071 given calibration radius used in the photometric calibration step.
1072 Thus `calibFluxRadius` should be set to this same radius so that we can normalize
1073 the PSF model to the correct instrumental flux within calibFluxRadius.
1076 self.log.info(
"Making %d fake star images" % len(fakeCat))
1078 for (index, row)
in fakeCat.iterrows():
1084 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1085 starIm = psf.computeImage(xy)
1086 starIm /= correctedFlux
1088 except InvalidParameterError:
1089 self.log.info(
"Star at %0.4f, %0.4f outside of image" % (row[
"x"], row[
"y"]))
1093 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
1098 yield ((starIm.convertF(), xy))
1101 """Remove rows from the fakes catalog which have HLR = 0 for either the buldge or disk component,
1102 also remove galaxies that have Sersic index outside the galsim min and max
1103 allowed (0.3 <= n <= 6.2).
1107 fakeCat : `pandas.core.frame.DataFrame`
1108 The catalog of fake sources to be input
1109 starCheckVal : `str`, `bytes` or `int`
1110 The value that is set in the sourceType column to specifiy an object is a star.
1114 fakeCat : `pandas.core.frame.DataFrame`
1115 The input catalog of fake sources but with the bad objects removed
1118 rowsToKeep = (((fakeCat[
'bulge_semimajor'] != 0.0) & (fakeCat[
'disk_semimajor'] != 0.0))
1119 | (fakeCat[self.config.sourceType] == starCheckVal))
1120 numRowsNotUsed = len(fakeCat) - len(np.where(rowsToKeep)[0])
1121 self.log.info(
"Removing %d rows with HLR = 0 for either the bulge or disk" % numRowsNotUsed)
1122 fakeCat = fakeCat[rowsToKeep]
1124 minN = galsim.Sersic._minimum_n
1125 maxN = galsim.Sersic._maximum_n
1126 rowsWithGoodSersic = (((fakeCat[
'bulge_n'] >= minN) & (fakeCat[
'bulge_n'] <= maxN)
1127 & (fakeCat[
'disk_n'] >= minN) & (fakeCat[
'disk_n'] <= maxN))
1128 | (fakeCat[self.config.sourceType] == starCheckVal))
1129 numRowsNotUsed = len(fakeCat) - len(np.where(rowsWithGoodSersic)[0])
1130 self.log.info(
"Removing %d rows of galaxies with nBulge or nDisk outside of %0.2f <= n <= %0.2f" %
1131 (numRowsNotUsed, minN, maxN))
1132 fakeCat = fakeCat[rowsWithGoodSersic]
1134 if self.config.doSubSelectSources:
1135 numRowsNotUsed = len(fakeCat) - len(fakeCat[
'select'])
1136 self.log.info(
"Removing %d rows which were not designated as template sources" % numRowsNotUsed)
1137 fakeCat = fakeCat[fakeCat[
'select']]
1142 """Add the fake sources to the given image
1146 image : `lsst.afw.image.exposure.exposure.ExposureF`
1147 The image into which the fake sources should be added
1148 fakeImages : `typing.Iterator` [`tuple` ['lsst.afw.image.ImageF`, `lsst.geom.Point2d`]]
1149 An iterator of tuples that contains (or generates) images of fake sources,
1150 and the locations they are to be inserted at.
1152 The type (star/galaxy) of fake sources input
1156 image : `lsst.afw.image.exposure.exposure.ExposureF`
1160 Uses the x, y information in the ``fakeCat`` to position an image of the fake interpolated onto the
1161 pixel grid of the image. Sets the ``FAKE`` mask plane for the pixels added with the fake source.
1164 imageBBox = image.getBBox()
1165 imageMI = image.maskedImage
1167 for (fakeImage, xy)
in fakeImages:
1168 X0 = xy.getX() - fakeImage.getWidth()/2 + 0.5
1169 Y0 = xy.getY() - fakeImage.getHeight()/2 + 0.5
1170 self.log.debug(
"Adding fake source at %d, %d" % (xy.getX(), xy.getY()))
1171 if sourceType ==
"galaxy":
1172 interpFakeImage = afwMath.offsetImage(fakeImage, X0, Y0,
"lanczos3")
1174 interpFakeImage = fakeImage
1176 interpFakeImBBox = interpFakeImage.getBBox()
1177 interpFakeImBBox.clip(imageBBox)
1179 if interpFakeImBBox.getArea() > 0:
1180 imageMIView = imageMI[interpFakeImBBox]
1181 clippedFakeImage = interpFakeImage[interpFakeImBBox]
1182 clippedFakeImageMI = afwImage.MaskedImageF(clippedFakeImage)
1183 clippedFakeImageMI.mask.set(self.bitmask)
1184 imageMIView += clippedFakeImageMI
1188 def _getMetadataName(self):
1189 """Disable metadata writing"""
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, altMaskList=None, mask=None, supplementaryData=None)
def addPixCoords(self, fakeCat, image)
def mkFakeStars(self, fakeCat, band, photoCalib, psf, image)
def mkFakeGalsimGalaxies(self, fakeCat, band, photoCalib, pixelScale, psf, image)
def cleanCat(self, fakeCat, starCheckVal)
def processImagesForInsertion(self, fakeCat, wcs, psf, photoCalib, band, pixelScale)
def trimFakeCat(self, fakeCat, image)
def addFakeSources(self, image, fakeImages, sourceType)