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, Point2D
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])
85 psfArr = psf.computeKernelImage(pt).array
86 except InvalidParameterError:
89 np.clip(pt.x, bbox.minX, bbox.maxX),
90 np.clip(pt.y, bbox.minY, bbox.maxY)
92 if pt == contained_pt:
95 "Cannot compute Psf for object at {}; skipping",
101 psfArr = psf.computeKernelImage(contained_pt).array
102 except InvalidParameterError:
105 "Cannot compute Psf for object at {}; skipping",
109 apCorr = psf.computeApertureFlux(calibFluxRadius)
111 gsPSF = galsim.InterpolatedImage(galsim.Image(psfArr), wcs=gsWCS)
113 conv = galsim.Convolve(gsObj, gsPSF)
114 stampSize = conv.getGoodImageSize(gsWCS.minLinearScale())
115 subBounds = galsim.BoundsI(posi).withBorder(stampSize//2)
116 subBounds &= fullBounds
118 if subBounds.area() > 0:
119 subImg = gsImg[subBounds]
120 offset = posd - subBounds.true_center
136 exposure[subBox].mask.array |= bitmask
139 def _isWCSGalsimDefault(wcs, hdr):
140 """Decide if wcs = galsim.PixelScale(1.0) is explicitly present in header,
141 or if it's just the galsim default.
146 Potentially default WCS.
147 hdr : galsim.fits.FitsHeader
148 Header as read in by galsim.
153 True if default, False if explicitly set in header.
155 if wcs != galsim.PixelScale(1.0):
157 if hdr.get(
'GS_WCS')
is not None:
159 if hdr.get(
'CTYPE1',
'LINEAR') ==
'LINEAR':
160 return not any(k
in hdr
for k
in [
'CD1_1',
'CDELT1'])
161 for wcs_type
in galsim.fitswcs.fits_wcs_types:
164 wcs_type._readHeader(hdr)
169 return not any(k
in hdr
for k
in [
'CD1_1',
'CDELT1'])
173 defaultTemplates={
"coaddName":
"deep",
174 "fakesType":
"fakes_"},
175 dimensions=(
"tract",
"patch",
"band",
"skymap")):
178 doc=
"Image into which fakes are to be added.",
179 name=
"{coaddName}Coadd",
180 storageClass=
"ExposureF",
181 dimensions=(
"tract",
"patch",
"band",
"skymap")
185 doc=
"Catalog of fake sources to draw inputs from.",
186 name=
"{fakesType}fakeSourceCat",
187 storageClass=
"DataFrame",
188 dimensions=(
"tract",
"skymap")
191 imageWithFakes = cT.Output(
192 doc=
"Image with fake sources added.",
193 name=
"{fakesType}{coaddName}Coadd",
194 storageClass=
"ExposureF",
195 dimensions=(
"tract",
"patch",
"band",
"skymap")
199 class InsertFakesConfig(PipelineTaskConfig,
200 pipelineConnections=InsertFakesConnections):
201 """Config for inserting fake sources
206 doCleanCat = pexConfig.Field(
207 doc=
"If true removes bad sources from the catalog.",
212 fakeType = pexConfig.Field(
213 doc=
"What type of fake catalog to use, snapshot (includes variability in the magnitudes calculated "
214 "from the MJD of the image), static (no variability) or filename for a user defined fits"
220 calibFluxRadius = pexConfig.Field(
221 doc=
"Aperture radius (in pixels) that was used to define the calibration for this image+catalog. "
222 "This will be used to produce the correct instrumental fluxes within the radius. "
223 "This value should match that of the field defined in slot_CalibFlux_instFlux.",
228 coaddName = pexConfig.Field(
229 doc=
"The name of the type of coadd used",
234 doSubSelectSources = pexConfig.Field(
235 doc=
"Set to True if you wish to sub select sources to be input based on the value in the column"
236 "set in the sourceSelectionColName config option.",
241 insertImages = pexConfig.Field(
242 doc=
"Insert images directly? True or False.",
247 doProcessAllDataIds = pexConfig.Field(
248 doc=
"If True, all input data IDs will be processed, even those containing no fake sources.",
253 trimBuffer = pexConfig.Field(
254 doc=
"Size of the pixel buffer surrounding the image. Only those fake sources with a centroid"
255 "falling within the image+buffer region will be considered for fake source injection.",
260 sourceType = pexConfig.Field(
261 doc=
"The column name for the source type used in the fake source catalog.",
263 default=
"sourceType",
268 ra_col = pexConfig.Field(
269 doc=
"Source catalog column name for RA (in radians).",
274 dec_col = pexConfig.Field(
275 doc=
"Source catalog column name for dec (in radians).",
280 bulge_semimajor_col = pexConfig.Field(
281 doc=
"Source catalog column name for the semimajor axis (in arcseconds) "
282 "of the bulge half-light ellipse.",
284 default=
"bulge_semimajor",
287 bulge_axis_ratio_col = pexConfig.Field(
288 doc=
"Source catalog column name for the axis ratio of the bulge "
289 "half-light ellipse.",
291 default=
"bulge_axis_ratio",
294 bulge_pa_col = pexConfig.Field(
295 doc=
"Source catalog column name for the position angle (measured from "
296 "North through East in degrees) of the semimajor axis of the bulge "
297 "half-light ellipse.",
302 bulge_n_col = pexConfig.Field(
303 doc=
"Source catalog column name for the Sersic index of the bulge.",
308 disk_semimajor_col = pexConfig.Field(
309 doc=
"Source catalog column name for the semimajor axis (in arcseconds) "
310 "of the disk half-light ellipse.",
312 default=
"disk_semimajor",
315 disk_axis_ratio_col = pexConfig.Field(
316 doc=
"Source catalog column name for the axis ratio of the disk "
317 "half-light ellipse.",
319 default=
"disk_axis_ratio",
322 disk_pa_col = pexConfig.Field(
323 doc=
"Source catalog column name for the position angle (measured from "
324 "North through East in degrees) of the semimajor axis of the disk "
325 "half-light ellipse.",
330 disk_n_col = pexConfig.Field(
331 doc=
"Source catalog column name for the Sersic index of the disk.",
336 bulge_disk_flux_ratio_col = pexConfig.Field(
337 doc=
"Source catalog column name for the bulge/disk flux ratio.",
339 default=
"bulge_disk_flux_ratio",
342 mag_col = pexConfig.Field(
343 doc=
"Source catalog column name template for magnitudes, in the format "
344 "``filter name``_mag_col. E.g., if this config variable is set to "
345 "``%s_mag``, then the i-band magnitude will be searched for in the "
346 "``i_mag`` column of the source catalog.",
351 select_col = pexConfig.Field(
352 doc=
"Source catalog column name to be used to select which sources to "
360 raColName = pexConfig.Field(
361 doc=
"RA column name used in the fake source catalog.",
364 deprecated=
"Use `ra_col` instead."
367 decColName = pexConfig.Field(
368 doc=
"Dec. column name used in the fake source catalog.",
371 deprecated=
"Use `dec_col` instead."
374 diskHLR = pexConfig.Field(
375 doc=
"Column name for the disk half light radius used in the fake source catalog.",
377 default=
"DiskHalfLightRadius",
379 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
380 " to specify disk half-light ellipse."
384 aDisk = pexConfig.Field(
385 doc=
"The column name for the semi major axis length of the disk component used in the fake source"
390 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
391 " to specify disk half-light ellipse."
395 bDisk = pexConfig.Field(
396 doc=
"The column name for the semi minor axis length of the disk component.",
400 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
401 " to specify disk half-light ellipse."
405 paDisk = pexConfig.Field(
406 doc=
"The column name for the PA of the disk component used in the fake source catalog.",
410 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
411 " to specify disk half-light ellipse."
415 nDisk = pexConfig.Field(
416 doc=
"The column name for the sersic index of the disk component used in the fake source catalog.",
419 deprecated=
"Use `disk_n` instead."
422 bulgeHLR = pexConfig.Field(
423 doc=
"Column name for the bulge half light radius used in the fake source catalog.",
425 default=
"BulgeHalfLightRadius",
427 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
428 "`bulge_pa_col` to specify disk half-light ellipse."
432 aBulge = pexConfig.Field(
433 doc=
"The column name for the semi major axis length of the bulge component.",
437 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
438 "`bulge_pa_col` to specify disk half-light ellipse."
442 bBulge = pexConfig.Field(
443 doc=
"The column name for the semi minor axis length of the bulge component used in the fake source "
448 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
449 "`bulge_pa_col` to specify disk half-light ellipse."
453 paBulge = pexConfig.Field(
454 doc=
"The column name for the PA of the bulge component used in the fake source catalog.",
458 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
459 "`bulge_pa_col` to specify disk half-light ellipse."
463 nBulge = pexConfig.Field(
464 doc=
"The column name for the sersic index of the bulge component used in the fake source catalog.",
467 deprecated=
"Use `bulge_n` instead."
470 magVar = pexConfig.Field(
471 doc=
"The column name for the magnitude calculated taking variability into account. In the format "
472 "``filter name``magVar, e.g. imagVar for the magnitude in the i band.",
475 deprecated=
"Use `mag_col` instead."
478 sourceSelectionColName = pexConfig.Field(
479 doc=
"The name of the column in the input fakes catalogue to be used to determine which sources to"
480 "add, default is none and when this is used all sources are added.",
482 default=
"templateSource",
483 deprecated=
"Use `select_col` instead."
487 class InsertFakesTask(PipelineTask, CmdLineTask):
488 """Insert fake objects into images.
490 Add fake stars and galaxies to the given image, read in through the dataRef. Galaxy parameters are read in
491 from the specified file and then modelled using galsim.
493 `InsertFakesTask` has five functions that make images of the fake sources and then add them to the
497 Use the WCS information to add the pixel coordinates of each source.
498 `mkFakeGalsimGalaxies`
499 Use Galsim to make fake double sersic galaxies for each set of galaxy parameters in the input file.
501 Use the PSF information from the image to make a fake star using the magnitude information from the
504 Remove rows of the input fake catalog which have half light radius, of either the bulge or the disk,
505 that are 0. Also removes rows that have Sersic index outside of galsim's allowed paramters. If
506 the config option sourceSelectionColName is set then this function limits the catalog of input fakes
507 to only those which are True in this column.
509 Add the fake sources to the image.
513 _DefaultName =
"insertFakes"
514 ConfigClass = InsertFakesConfig
516 def runDataRef(self, dataRef):
517 """Read in/write out the required data products and add fake sources to the deepCoadd.
521 dataRef : `lsst.daf.persistence.butlerSubset.ButlerDataRef`
522 Data reference defining the image to have fakes added to it
523 Used to access the following data products:
527 self.log.
info(
"Adding fakes to: tract: %d, patch: %s, filter: %s",
528 dataRef.dataId[
"tract"], dataRef.dataId[
"patch"], dataRef.dataId[
"filter"])
532 if self.config.fakeType ==
"static":
533 fakeCat = dataRef.get(
"deepCoadd_fakeSourceCat").toDataFrame()
536 self.fakeSourceCatType =
"deepCoadd_fakeSourceCat"
538 fakeCat = Table.read(self.config.fakeType).to_pandas()
540 coadd = dataRef.get(
"deepCoadd")
542 photoCalib = coadd.getPhotoCalib()
544 imageWithFakes = self.run(fakeCat, coadd, wcs, photoCalib)
546 dataRef.put(imageWithFakes.imageWithFakes,
"fakes_deepCoadd")
548 def runQuantum(self, butlerQC, inputRefs, outputRefs):
549 inputs = butlerQC.get(inputRefs)
550 inputs[
"wcs"] = inputs[
"image"].getWcs()
551 inputs[
"photoCalib"] = inputs[
"image"].getPhotoCalib()
553 outputs = self.run(**inputs)
554 butlerQC.put(outputs, outputRefs)
557 def _makeArgumentParser(cls):
558 parser = pipeBase.ArgumentParser(name=cls._DefaultName)
559 parser.add_id_argument(name=
"--id", datasetType=
"deepCoadd",
560 help=
"data IDs for the deepCoadd, e.g. --id tract=12345 patch=1,2 filter=r",
561 ContainerClass=ExistingCoaddDataIdContainer)
564 def run(self, fakeCat, image, wcs, photoCalib):
565 """Add fake sources to an image.
569 fakeCat : `pandas.core.frame.DataFrame`
570 The catalog of fake sources to be input
571 image : `lsst.afw.image.exposure.exposure.ExposureF`
572 The image into which the fake sources should be added
573 wcs : `lsst.afw.geom.SkyWcs`
574 WCS to use to add fake sources
575 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
576 Photometric calibration to be used to calibrate the fake sources
580 resultStruct : `lsst.pipe.base.struct.Struct`
581 contains : image : `lsst.afw.image.exposure.exposure.ExposureF`
585 Adds pixel coordinates for each source to the fakeCat and removes objects with bulge or disk half
586 light radius = 0 (if ``config.doCleanCat = True``).
588 Adds the ``Fake`` mask plane to the image which is then set by `addFakeSources` to mark where fake
589 sources have been added. Uses the information in the ``fakeCat`` to make fake galaxies (using galsim)
590 and fake stars, using the PSF models from the PSF information for the image. These are then added to
591 the image and the image with fakes included returned.
593 The galsim galaxies are made using a double sersic profile, one for the bulge and one for the disk,
594 this is then convolved with the PSF at that point.
598 origWcs = image.getWcs()
599 origPhotoCalib = image.getPhotoCalib()
601 image.setPhotoCalib(photoCalib)
603 band = image.getFilterLabel().bandLabel
604 fakeCat = self._standardizeColumns(fakeCat, band)
606 fakeCat = self.addPixCoords(fakeCat, image)
607 fakeCat = self.trimFakeCat(fakeCat, image)
610 if isinstance(fakeCat[self.config.sourceType].iloc[0], str):
611 galCheckVal =
"galaxy"
612 starCheckVal =
"star"
613 elif isinstance(fakeCat[self.config.sourceType].iloc[0], bytes):
614 galCheckVal = b
"galaxy"
615 starCheckVal = b
"star"
616 elif isinstance(fakeCat[self.config.sourceType].iloc[0], (int, float)):
620 raise TypeError(
"sourceType column does not have required type, should be str, bytes or int")
622 if not self.config.insertImages:
623 if self.config.doCleanCat:
624 fakeCat = self.cleanCat(fakeCat, starCheckVal)
626 generator = self._generateGSObjectsFromCatalog(image, fakeCat, galCheckVal, starCheckVal)
628 generator = self._generateGSObjectsFromImages(image, fakeCat)
629 _add_fake_sources(image, generator, calibFluxRadius=self.config.calibFluxRadius, logger=self.log)
630 elif len(fakeCat) == 0
and self.config.doProcessAllDataIds:
631 self.log.
warning(
"No fakes found for this dataRef; processing anyway.")
632 image.mask.addMaskPlane(
"FAKE")
634 raise RuntimeError(
"No fakes found for this dataRef.")
637 image.setWcs(origWcs)
638 image.setPhotoCalib(origPhotoCalib)
640 resultStruct = pipeBase.Struct(imageWithFakes=image)
644 def _standardizeColumns(self, fakeCat, band):
645 """Use config variables to 'standardize' the expected columns and column
646 names in the input catalog.
650 fakeCat : `pandas.core.frame.DataFrame`
651 The catalog of fake sources to be input
653 Label for the current band being processed.
657 outCat : `pandas.core.frame.DataFrame`
658 The standardized catalog of fake sources
666 for new_name, depr_name, std_name
in [
667 (cfg.ra_col, cfg.raColName,
'ra'),
668 (cfg.dec_col, cfg.decColName,
'dec'),
669 (cfg.bulge_n_col, cfg.nBulge,
'bulge_n'),
670 (cfg.bulge_pa_col, cfg.paBulge,
'bulge_pa'),
671 (cfg.disk_n_col, cfg.nDisk,
'disk_n'),
672 (cfg.disk_pa_col, cfg.paDisk,
'disk_pa'),
673 (cfg.mag_col%band, cfg.magVar%band,
'mag'),
674 (cfg.select_col, cfg.sourceSelectionColName,
'select')
677 if not cfg.doSubSelectSources
and std_name ==
'select':
679 if new_name
in fakeCat.columns:
680 replace_dict[new_name] = std_name
681 elif depr_name
in fakeCat.columns:
682 replace_dict[depr_name] = std_name
684 raise ValueError(f
"Could not determine column for {std_name}.")
685 fakeCat = fakeCat.rename(columns=replace_dict, copy=
False)
691 cfg.bulge_semimajor_col
in fakeCat.columns
692 and cfg.bulge_axis_ratio_col
in fakeCat.columns
694 fakeCat = fakeCat.rename(
696 cfg.bulge_semimajor_col:
'bulge_semimajor',
697 cfg.bulge_axis_ratio_col:
'bulge_axis_ratio',
698 cfg.disk_semimajor_col:
'disk_semimajor',
699 cfg.disk_axis_ratio_col:
'disk_axis_ratio',
704 cfg.bulgeHLR
in fakeCat.columns
705 and cfg.aBulge
in fakeCat.columns
706 and cfg.bBulge
in fakeCat.columns
708 fakeCat[
'bulge_axis_ratio'] = (
709 fakeCat[cfg.bBulge]/fakeCat[cfg.aBulge]
711 fakeCat[
'bulge_semimajor'] = (
712 fakeCat[cfg.bulgeHLR]/np.sqrt(fakeCat[
'bulge_axis_ratio'])
714 fakeCat[
'disk_axis_ratio'] = (
715 fakeCat[cfg.bDisk]/fakeCat[cfg.aDisk]
717 fakeCat[
'disk_semimajor'] = (
718 fakeCat[cfg.diskHLR]/np.sqrt(fakeCat[
'disk_axis_ratio'])
722 "Could not determine columns for half-light radius and axis "
727 if cfg.bulge_disk_flux_ratio_col
in fakeCat.columns:
728 fakeCat = fakeCat.rename(
730 cfg.bulge_disk_flux_ratio_col:
'bulge_disk_flux_ratio'
735 fakeCat[
'bulge_disk_flux_ratio'] = 1.0
739 def _generateGSObjectsFromCatalog(self, exposure, fakeCat, galCheckVal, starCheckVal):
740 """Process catalog to generate `galsim.GSObject` s.
744 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
745 The exposure into which the fake sources should be added
746 fakeCat : `pandas.core.frame.DataFrame`
747 The catalog of fake sources to be input
748 galCheckVal : `str`, `bytes` or `int`
749 The value that is set in the sourceType column to specifiy an object is a galaxy.
750 starCheckVal : `str`, `bytes` or `int`
751 The value that is set in the sourceType column to specifiy an object is a star.
755 gsObjects : `generator`
756 A generator of tuples of `lsst.geom.SpherePoint` and `galsim.GSObject`.
758 wcs = exposure.getWcs()
759 photoCalib = exposure.getPhotoCalib()
761 self.log.
info(
"Making %d objects for insertion", len(fakeCat))
763 for (index, row)
in fakeCat.iterrows():
767 xy = wcs.skyToPixel(skyCoord)
770 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
774 sourceType = row[self.config.sourceType]
775 if sourceType == galCheckVal:
777 bulge_gs_HLR = row[
'bulge_semimajor']*np.sqrt(row[
'bulge_axis_ratio'])
778 bulge = galsim.Sersic(n=row[
'bulge_n'], half_light_radius=bulge_gs_HLR)
779 bulge = bulge.shear(q=row[
'bulge_axis_ratio'], beta=((90 - row[
'bulge_pa'])*galsim.degrees))
781 disk_gs_HLR = row[
'disk_semimajor']*np.sqrt(row[
'disk_axis_ratio'])
782 disk = galsim.Sersic(n=row[
'disk_n'], half_light_radius=disk_gs_HLR)
783 disk = disk.shear(q=row[
'disk_axis_ratio'], beta=((90 - row[
'disk_pa'])*galsim.degrees))
785 gal = bulge*row[
'bulge_disk_flux_ratio'] + disk
786 gal = gal.withFlux(flux)
789 elif sourceType == starCheckVal:
790 star = galsim.DeltaFunction()
791 star = star.withFlux(flux)
794 raise TypeError(f
"Unknown sourceType {sourceType}")
796 def _generateGSObjectsFromImages(self, exposure, fakeCat):
797 """Process catalog to generate `galsim.GSObject` s.
801 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
802 The exposure into which the fake sources should be added
803 fakeCat : `pandas.core.frame.DataFrame`
804 The catalog of fake sources to be input
808 gsObjects : `generator`
809 A generator of tuples of `lsst.geom.SpherePoint` and `galsim.GSObject`.
811 band = exposure.getFilterLabel().bandLabel
812 wcs = exposure.getWcs()
813 photoCalib = exposure.getPhotoCalib()
815 self.log.
info(
"Processing %d fake images", len(fakeCat))
817 for (index, row)
in fakeCat.iterrows():
821 xy = wcs.skyToPixel(skyCoord)
824 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
828 imFile = row[band+
"imFilename"]
830 imFile = imFile.decode(
"utf-8")
831 except AttributeError:
833 imFile = imFile.strip()
834 im = galsim.fits.read(imFile, read_header=
True)
842 if _isWCSGalsimDefault(im.wcs, im.header):
843 im.wcs = galsim.PixelScale(
844 wcs.getPixelScale().asArcseconds()
847 obj = galsim.InterpolatedImage(im)
848 obj = obj.withFlux(flux)
852 """Process images from files into the format needed for insertion.
856 fakeCat : `pandas.core.frame.DataFrame`
857 The catalog of fake sources to be input
858 wcs : `lsst.afw.geom.skyWcs.skyWcs.SkyWc`
859 WCS to use to add fake sources
860 psf : `lsst.meas.algorithms.coaddPsf.coaddPsf.CoaddPsf` or
861 `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
862 The PSF information to use to make the PSF images
863 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
864 Photometric calibration to be used to calibrate the fake sources
866 The filter band that the observation was taken in.
868 The pixel scale of the image the sources are to be added to.
873 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
874 `lsst.geom.Point2D` of their locations.
875 For sources labelled as galaxy.
877 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
878 `lsst.geom.Point2D` of their locations.
879 For sources labelled as star.
883 The input fakes catalog needs to contain the absolute path to the image in the
884 band that is being used to add images to. It also needs to have the R.A. and
885 declination of the fake source in radians and the sourceType of the object.
890 self.log.
info(
"Processing %d fake images", len(fakeCat))
892 for (imFile, sourceType, mag, x, y)
in zip(fakeCat[band +
"imFilename"].array,
893 fakeCat[
"sourceType"].array,
894 fakeCat[
'mag'].array,
895 fakeCat[
"x"].array, fakeCat[
"y"].array):
897 im = afwImage.ImageF.readFits(imFile)
904 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
905 psfKernel = psf.computeKernelImage(xy).getArray()
906 psfKernel /= correctedFlux
908 except InvalidParameterError:
909 self.log.
info(
"%s at %0.4f, %0.4f outside of image", sourceType, x, y)
912 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
913 galsimIm = galsim.InterpolatedImage(galsim.Image(im.array), scale=pixelScale)
914 convIm = galsim.Convolve([galsimIm, psfIm])
917 outIm = convIm.drawImage(scale=pixelScale, method=
"real_space").array
918 except (galsim.errors.GalSimFFTSizeError, MemoryError):
921 imSum = np.sum(outIm)
925 flux = photoCalib.magnitudeToInstFlux(mag, xy)
929 imWithFlux = flux*divIm
931 if sourceType == b
"galaxy":
932 galImages.append((afwImage.ImageF(imWithFlux), xy))
933 if sourceType == b
"star":
934 starImages.append((afwImage.ImageF(imWithFlux), xy))
936 return galImages, starImages
940 """Add pixel coordinates to the catalog of fakes.
944 fakeCat : `pandas.core.frame.DataFrame`
945 The catalog of fake sources to be input
946 image : `lsst.afw.image.exposure.exposure.ExposureF`
947 The image into which the fake sources should be added
951 fakeCat : `pandas.core.frame.DataFrame`
954 ras = fakeCat[
'ra'].values
955 decs = fakeCat[
'dec'].values
956 xs, ys = wcs.skyToPixelArray(ras, decs)
963 """Trim the fake cat to about the size of the input image.
965 `fakeCat` must be processed with addPixCoords before using this method.
969 fakeCat : `pandas.core.frame.DataFrame`
970 The catalog of fake sources to be input
971 image : `lsst.afw.image.exposure.exposure.ExposureF`
972 The image into which the fake sources should be added
976 fakeCat : `pandas.core.frame.DataFrame`
977 The original fakeCat trimmed to the area of the image
980 bbox =
Box2D(image.getBBox()).dilatedBy(self.config.trimBuffer)
981 xs = fakeCat[
"x"].values
982 ys = fakeCat[
"y"].values
984 isContained = xs >= bbox.minX
985 isContained &= xs <= bbox.maxX
986 isContained &= ys >= bbox.minY
987 isContained &= ys <= bbox.maxY
989 return fakeCat[isContained]
992 """Make images of fake galaxies using GalSim.
998 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
999 The PSF information to use to make the PSF images
1000 fakeCat : `pandas.core.frame.DataFrame`
1001 The catalog of fake sources to be input
1002 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
1003 Photometric calibration to be used to calibrate the fake sources
1007 galImages : `generator`
1008 A generator of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
1009 `lsst.geom.Point2D` of their locations.
1014 Fake galaxies are made by combining two sersic profiles, one for the bulge and one for the disk. Each
1015 component has an individual sersic index (n), a, b and position angle (PA). The combined profile is
1016 then convolved with the PSF at the specified x, y position on the image.
1018 The names of the columns in the ``fakeCat`` are configurable and are the column names from the
1019 University of Washington simulations database as default. For more information see the doc strings
1020 attached to the config options.
1022 See mkFakeStars doc string for an explanation of calibration to instrumental flux.
1025 self.log.
info(
"Making %d fake galaxy images", len(fakeCat))
1027 for (index, row)
in fakeCat.iterrows():
1033 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1034 psfKernel = psf.computeKernelImage(xy).getArray()
1035 psfKernel /= correctedFlux
1037 except InvalidParameterError:
1038 self.log.
info(
"Galaxy at %0.4f, %0.4f outside of image", row[
"x"], row[
"y"])
1042 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
1047 bulge_gs_HLR = row[
'bulge_semimajor']*np.sqrt(row[
'bulge_axis_ratio'])
1048 bulge = galsim.Sersic(n=row[
'bulge_n'], half_light_radius=bulge_gs_HLR)
1049 bulge = bulge.shear(q=row[
'bulge_axis_ratio'], beta=((90 - row[
'bulge_pa'])*galsim.degrees))
1051 disk_gs_HLR = row[
'disk_semimajor']*np.sqrt(row[
'disk_axis_ratio'])
1052 disk = galsim.Sersic(n=row[
'disk_n'], half_light_radius=disk_gs_HLR)
1053 disk = disk.shear(q=row[
'disk_axis_ratio'], beta=((90 - row[
'disk_pa'])*galsim.degrees))
1055 gal = bulge*row[
'bulge_disk_flux_ratio'] + disk
1056 gal = gal.withFlux(flux)
1058 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
1059 gal = galsim.Convolve([gal, psfIm])
1061 galIm = gal.drawImage(scale=pixelScale, method=
"real_space").array
1062 except (galsim.errors.GalSimFFTSizeError, MemoryError):
1065 yield (afwImage.ImageF(galIm), xy)
1069 """Make fake stars based off the properties in the fakeCat.
1074 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
1075 The PSF information to use to make the PSF images
1076 fakeCat : `pandas.core.frame.DataFrame`
1077 The catalog of fake sources to be input
1078 image : `lsst.afw.image.exposure.exposure.ExposureF`
1079 The image into which the fake sources should be added
1080 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
1081 Photometric calibration to be used to calibrate the fake sources
1085 starImages : `generator`
1086 A generator of tuples of `lsst.afw.image.ImageF` of fake stars and
1087 `lsst.geom.Point2D` of their locations.
1091 To take a given magnitude and translate to the number of counts in the image
1092 we use photoCalib.magnitudeToInstFlux, which returns the instrumental flux for the
1093 given calibration radius used in the photometric calibration step.
1094 Thus `calibFluxRadius` should be set to this same radius so that we can normalize
1095 the PSF model to the correct instrumental flux within calibFluxRadius.
1098 self.log.
info(
"Making %d fake star images", len(fakeCat))
1100 for (index, row)
in fakeCat.iterrows():
1106 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1107 starIm = psf.computeImage(xy)
1108 starIm /= correctedFlux
1110 except InvalidParameterError:
1111 self.log.
info(
"Star at %0.4f, %0.4f outside of image", row[
"x"], row[
"y"])
1115 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
1120 yield ((starIm.convertF(), xy))
1123 """Remove rows from the fakes catalog which have HLR = 0 for either the buldge or disk component,
1124 also remove galaxies that have Sersic index outside the galsim min and max
1125 allowed (0.3 <= n <= 6.2).
1129 fakeCat : `pandas.core.frame.DataFrame`
1130 The catalog of fake sources to be input
1131 starCheckVal : `str`, `bytes` or `int`
1132 The value that is set in the sourceType column to specifiy an object is a star.
1136 fakeCat : `pandas.core.frame.DataFrame`
1137 The input catalog of fake sources but with the bad objects removed
1140 rowsToKeep = (((fakeCat[
'bulge_semimajor'] != 0.0) & (fakeCat[
'disk_semimajor'] != 0.0))
1141 | (fakeCat[self.config.sourceType] == starCheckVal))
1142 numRowsNotUsed = len(fakeCat) - len(np.where(rowsToKeep)[0])
1143 self.log.
info(
"Removing %d rows with HLR = 0 for either the bulge or disk", numRowsNotUsed)
1144 fakeCat = fakeCat[rowsToKeep]
1146 minN = galsim.Sersic._minimum_n
1147 maxN = galsim.Sersic._maximum_n
1148 rowsWithGoodSersic = (((fakeCat[
'bulge_n'] >= minN) & (fakeCat[
'bulge_n'] <= maxN)
1149 & (fakeCat[
'disk_n'] >= minN) & (fakeCat[
'disk_n'] <= maxN))
1150 | (fakeCat[self.config.sourceType] == starCheckVal))
1151 numRowsNotUsed = len(fakeCat) - len(np.where(rowsWithGoodSersic)[0])
1152 self.log.
info(
"Removing %d rows of galaxies with nBulge or nDisk outside of %0.2f <= n <= %0.2f",
1153 numRowsNotUsed, minN, maxN)
1154 fakeCat = fakeCat[rowsWithGoodSersic]
1156 if self.config.doSubSelectSources:
1157 numRowsNotUsed = len(fakeCat) - len(fakeCat[
'select'])
1158 self.log.
info(
"Removing %d rows which were not designated as template sources", numRowsNotUsed)
1159 fakeCat = fakeCat[fakeCat[
'select']]
1164 """Add the fake sources to the given image
1168 image : `lsst.afw.image.exposure.exposure.ExposureF`
1169 The image into which the fake sources should be added
1170 fakeImages : `typing.Iterator` [`tuple` ['lsst.afw.image.ImageF`, `lsst.geom.Point2d`]]
1171 An iterator of tuples that contains (or generates) images of fake sources,
1172 and the locations they are to be inserted at.
1174 The type (star/galaxy) of fake sources input
1178 image : `lsst.afw.image.exposure.exposure.ExposureF`
1182 Uses the x, y information in the ``fakeCat`` to position an image of the fake interpolated onto the
1183 pixel grid of the image. Sets the ``FAKE`` mask plane for the pixels added with the fake source.
1186 imageBBox = image.getBBox()
1187 imageMI = image.maskedImage
1189 for (fakeImage, xy)
in fakeImages:
1190 X0 = xy.getX() - fakeImage.getWidth()/2 + 0.5
1191 Y0 = xy.getY() - fakeImage.getHeight()/2 + 0.5
1192 self.log.
debug(
"Adding fake source at %d, %d", xy.getX(), xy.getY())
1193 if sourceType ==
"galaxy":
1196 interpFakeImage = fakeImage
1198 interpFakeImBBox = interpFakeImage.getBBox()
1199 interpFakeImBBox.clip(imageBBox)
1201 if interpFakeImBBox.getArea() > 0:
1202 imageMIView = imageMI[interpFakeImBBox]
1203 clippedFakeImage = interpFakeImage[interpFakeImBBox]
1204 clippedFakeImageMI = afwImage.MaskedImageF(clippedFakeImage)
1205 clippedFakeImageMI.mask.set(self.bitmask)
1206 imageMIView += clippedFakeImageMI
1210 def _getMetadataName(self):
1211 """Disable metadata writing"""
A floating-point coordinate rectangle geometry.
An integer coordinate rectangle.
Point in an unspecified spherical coordinate system.
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
std::shared_ptr< ImageT > offsetImage(ImageT const &image, float dx, float dy, std::string const &algorithmName="lanczos5", unsigned int buffer=0)
Return an image offset by (dx, dy) using the specified algorithm.
bool any(CoordinateExpr< N > const &expr) noexcept
Return true if any elements are true.
Point< double, 2 > Point2D
def run(self, coaddExposures, bbox, wcs)
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)