23Insert fakes into deepCoadds
26from astropy.table
import Table
28from astropy
import units
as u
36from lsst.pipe.base import CmdLineTask, PipelineTask, PipelineTaskConfig, PipelineTaskConnections
37import lsst.pipe.base.connectionTypes
as cT
40from lsst.geom import SpherePoint, radians, Box2D, Point2D
42__all__ = [
"InsertFakesConfig",
"InsertFakesTask"]
45def _add_fake_sources(exposure, objects, calibFluxRadius=12.0, logger=None):
46 """Add fake sources to the given exposure
50 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
51 The exposure into which the fake sources should be added
52 objects : `typing.Iterator` [`tuple` ['lsst.geom.SpherePoint`, `galsim.GSObject`]]
53 An iterator of tuples that contains (or generates) locations
and object
54 surface brightness profiles to inject.
55 calibFluxRadius : `float`, optional
56 Aperture radius (
in pixels) used to define the calibration
for this
57 exposure+catalog. This
is used to produce the correct instrumental fluxes
58 within the radius. The value should match that of the field defined
in
59 slot_CalibFlux_instFlux.
60 logger : `lsst.log.log.log.Log`
or `logging.Logger`, optional
63 exposure.mask.addMaskPlane("FAKE")
64 bitmask = exposure.mask.getPlaneBitMask(
"FAKE")
66 logger.info(f
"Adding mask plane with bitmask {bitmask}")
68 wcs = exposure.getWcs()
69 psf = exposure.getPsf()
71 bbox = exposure.getBBox()
72 fullBounds = galsim.BoundsI(bbox.minX, bbox.maxX, bbox.minY, bbox.maxY)
73 gsImg = galsim.Image(exposure.image.array, bounds=fullBounds)
75 pixScale = wcs.getPixelScale(bbox.getCenter()).asArcseconds()
77 for spt, gsObj
in objects:
78 pt = wcs.skyToPixel(spt)
79 posd = galsim.PositionD(pt.x, pt.y)
80 posi = galsim.PositionI(pt.x//1, pt.y//1)
82 logger.debug(f
"Adding fake source at {pt}")
84 mat = wcs.linearizePixelToSky(spt, geom.arcseconds).getMatrix()
85 gsWCS = galsim.JacobianWCS(mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1])
90 gsPixScale = np.sqrt(gsWCS.pixelArea())
91 if gsPixScale < pixScale/2
or gsPixScale > pixScale*2:
95 psfArr = psf.computeKernelImage(pt).array
96 except InvalidParameterError:
99 np.clip(pt.x, bbox.minX, bbox.maxX),
100 np.clip(pt.y, bbox.minY, bbox.maxY)
102 if pt == contained_pt:
105 "Cannot compute Psf for object at {}; skipping",
111 psfArr = psf.computeKernelImage(contained_pt).array
112 except InvalidParameterError:
115 "Cannot compute Psf for object at {}; skipping",
120 apCorr = psf.computeApertureFlux(calibFluxRadius)
122 gsPSF = galsim.InterpolatedImage(galsim.Image(psfArr), wcs=gsWCS)
124 conv = galsim.Convolve(gsObj, gsPSF)
125 stampSize = conv.getGoodImageSize(gsWCS.minLinearScale())
126 subBounds = galsim.BoundsI(posi).withBorder(stampSize//2)
127 subBounds &= fullBounds
129 if subBounds.area() > 0:
130 subImg = gsImg[subBounds]
131 offset = posd - subBounds.true_center
148 exposure[subBox].mask.array |= bitmask
151def _isWCSGalsimDefault(wcs, hdr):
152 """Decide if wcs = galsim.PixelScale(1.0) is explicitly present in header,
153 or if it
's just the galsim default.
158 Potentially default WCS.
159 hdr : galsim.fits.FitsHeader
160 Header as read
in by galsim.
165 True if default,
False if explicitly set
in header.
167 if wcs != galsim.PixelScale(1.0):
169 if hdr.get(
'GS_WCS')
is not None:
171 if hdr.get(
'CTYPE1',
'LINEAR') ==
'LINEAR':
172 return not any(k
in hdr
for k
in [
'CD1_1',
'CDELT1'])
173 for wcs_type
in galsim.fitswcs.fits_wcs_types:
176 wcs_type._readHeader(hdr)
181 return not any(k
in hdr
for k
in [
'CD1_1',
'CDELT1'])
185 defaultTemplates={
"coaddName":
"deep",
186 "fakesType":
"fakes_"},
187 dimensions=(
"tract",
"patch",
"band",
"skymap")):
190 doc=
"Image into which fakes are to be added.",
191 name=
"{coaddName}Coadd",
192 storageClass=
"ExposureF",
193 dimensions=(
"tract",
"patch",
"band",
"skymap")
197 doc=
"Catalog of fake sources to draw inputs from.",
198 name=
"{fakesType}fakeSourceCat",
199 storageClass=
"DataFrame",
200 dimensions=(
"tract",
"skymap")
203 imageWithFakes = cT.Output(
204 doc=
"Image with fake sources added.",
205 name=
"{fakesType}{coaddName}Coadd",
206 storageClass=
"ExposureF",
207 dimensions=(
"tract",
"patch",
"band",
"skymap")
211class InsertFakesConfig(PipelineTaskConfig,
212 pipelineConnections=InsertFakesConnections):
213 """Config for inserting fake sources
218 doCleanCat = pexConfig.Field(
219 doc=
"If true removes bad sources from the catalog.",
224 fakeType = pexConfig.Field(
225 doc=
"What type of fake catalog to use, snapshot (includes variability in the magnitudes calculated "
226 "from the MJD of the image), static (no variability) or filename for a user defined fits"
232 calibFluxRadius = pexConfig.Field(
233 doc=
"Aperture radius (in pixels) that was used to define the calibration for this image+catalog. "
234 "This will be used to produce the correct instrumental fluxes within the radius. "
235 "This value should match that of the field defined in slot_CalibFlux_instFlux.",
240 coaddName = pexConfig.Field(
241 doc=
"The name of the type of coadd used",
246 doSubSelectSources = pexConfig.Field(
247 doc=
"Set to True if you wish to sub select sources to be input based on the value in the column"
248 "set in the sourceSelectionColName config option.",
253 insertImages = pexConfig.Field(
254 doc=
"Insert images directly? True or False.",
259 insertOnlyStars = pexConfig.Field(
260 doc=
"Insert only stars? True or False.",
265 doProcessAllDataIds = pexConfig.Field(
266 doc=
"If True, all input data IDs will be processed, even those containing no fake sources.",
271 trimBuffer = pexConfig.Field(
272 doc=
"Size of the pixel buffer surrounding the image. Only those fake sources with a centroid"
273 "falling within the image+buffer region will be considered for fake source injection.",
278 sourceType = pexConfig.Field(
279 doc=
"The column name for the source type used in the fake source catalog.",
281 default=
"sourceType",
284 fits_alignment = pexConfig.ChoiceField(
285 doc=
"How should injections from FITS files be aligned?",
289 "Input image will be transformed such that the local WCS in "
290 "the FITS header matches the local WCS in the target image. "
291 "I.e., North, East, and angular distances in the input image "
292 "will match North, East, and angular distances in the target "
296 "Input image will _not_ be transformed. Up, right, and pixel "
297 "distances in the input image will match up, right and pixel "
298 "distances in the target image."
306 ra_col = pexConfig.Field(
307 doc=
"Source catalog column name for RA (in radians).",
312 dec_col = pexConfig.Field(
313 doc=
"Source catalog column name for dec (in radians).",
318 bulge_semimajor_col = pexConfig.Field(
319 doc=
"Source catalog column name for the semimajor axis (in arcseconds) "
320 "of the bulge half-light ellipse.",
322 default=
"bulge_semimajor",
325 bulge_axis_ratio_col = pexConfig.Field(
326 doc=
"Source catalog column name for the axis ratio of the bulge "
327 "half-light ellipse.",
329 default=
"bulge_axis_ratio",
332 bulge_pa_col = pexConfig.Field(
333 doc=
"Source catalog column name for the position angle (measured from "
334 "North through East in degrees) of the semimajor axis of the bulge "
335 "half-light ellipse.",
340 bulge_n_col = pexConfig.Field(
341 doc=
"Source catalog column name for the Sersic index of the bulge.",
346 disk_semimajor_col = pexConfig.Field(
347 doc=
"Source catalog column name for the semimajor axis (in arcseconds) "
348 "of the disk half-light ellipse.",
350 default=
"disk_semimajor",
353 disk_axis_ratio_col = pexConfig.Field(
354 doc=
"Source catalog column name for the axis ratio of the disk "
355 "half-light ellipse.",
357 default=
"disk_axis_ratio",
360 disk_pa_col = pexConfig.Field(
361 doc=
"Source catalog column name for the position angle (measured from "
362 "North through East in degrees) of the semimajor axis of the disk "
363 "half-light ellipse.",
368 disk_n_col = pexConfig.Field(
369 doc=
"Source catalog column name for the Sersic index of the disk.",
374 bulge_disk_flux_ratio_col = pexConfig.Field(
375 doc=
"Source catalog column name for the bulge/disk flux ratio.",
377 default=
"bulge_disk_flux_ratio",
380 mag_col = pexConfig.Field(
381 doc=
"Source catalog column name template for magnitudes, in the format "
382 "``filter name``_mag_col. E.g., if this config variable is set to "
383 "``%s_mag``, then the i-band magnitude will be searched for in the "
384 "``i_mag`` column of the source catalog.",
389 select_col = pexConfig.Field(
390 doc=
"Source catalog column name to be used to select which sources to "
396 length_col = pexConfig.Field(
397 doc=
"Source catalog column name for trail length (in pixels).",
399 default=
"trail_length",
402 angle_col = pexConfig.Field(
403 doc=
"Source catalog column name for trail angle (in radians).",
405 default=
"trail_angle",
410 raColName = pexConfig.Field(
411 doc=
"RA column name used in the fake source catalog.",
414 deprecated=
"Use `ra_col` instead."
417 decColName = pexConfig.Field(
418 doc=
"Dec. column name used in the fake source catalog.",
421 deprecated=
"Use `dec_col` instead."
424 diskHLR = pexConfig.Field(
425 doc=
"Column name for the disk half light radius used in the fake source catalog.",
427 default=
"DiskHalfLightRadius",
429 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
430 " to specify disk half-light ellipse."
434 aDisk = pexConfig.Field(
435 doc=
"The column name for the semi major axis length of the disk component used in the fake source"
440 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
441 " to specify disk half-light ellipse."
445 bDisk = pexConfig.Field(
446 doc=
"The column name for the semi minor axis length of the disk component.",
450 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
451 " to specify disk half-light ellipse."
455 paDisk = pexConfig.Field(
456 doc=
"The column name for the PA of the disk component used in the fake source catalog.",
460 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
461 " to specify disk half-light ellipse."
465 nDisk = pexConfig.Field(
466 doc=
"The column name for the sersic index of the disk component used in the fake source catalog.",
469 deprecated=
"Use `disk_n_col` instead."
472 bulgeHLR = pexConfig.Field(
473 doc=
"Column name for the bulge half light radius used in the fake source catalog.",
475 default=
"BulgeHalfLightRadius",
477 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
478 "`bulge_pa_col` to specify disk half-light ellipse."
482 aBulge = pexConfig.Field(
483 doc=
"The column name for the semi major axis length of the bulge component.",
487 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
488 "`bulge_pa_col` to specify disk half-light ellipse."
492 bBulge = pexConfig.Field(
493 doc=
"The column name for the semi minor axis length of the bulge component used in the fake source "
498 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
499 "`bulge_pa_col` to specify disk half-light ellipse."
503 paBulge = pexConfig.Field(
504 doc=
"The column name for the PA of the bulge component used in the fake source catalog.",
508 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
509 "`bulge_pa_col` to specify disk half-light ellipse."
513 nBulge = pexConfig.Field(
514 doc=
"The column name for the sersic index of the bulge component used in the fake source catalog.",
517 deprecated=
"Use `bulge_n_col` instead."
520 magVar = pexConfig.Field(
521 doc=
"The column name for the magnitude calculated taking variability into account. In the format "
522 "``filter name``magVar, e.g. imagVar for the magnitude in the i band.",
525 deprecated=
"Use `mag_col` instead."
528 sourceSelectionColName = pexConfig.Field(
529 doc=
"The name of the column in the input fakes catalogue to be used to determine which sources to"
530 "add, default is none and when this is used all sources are added.",
532 default=
"templateSource",
533 deprecated=
"Use `select_col` instead."
537class InsertFakesTask(PipelineTask, CmdLineTask):
538 """Insert fake objects into images.
540 Add fake stars and galaxies to the given image, read
in through the dataRef. Galaxy parameters are read
in
541 from the specified file
and then modelled using galsim.
543 `InsertFakesTask` has five functions that make images of the fake sources
and then add them to the
547 Use the WCS information to add the pixel coordinates of each source.
548 `mkFakeGalsimGalaxies`
549 Use Galsim to make fake double sersic galaxies
for each set of galaxy parameters
in the input file.
551 Use the PSF information
from the image to make a fake star using the magnitude information
from the
554 Remove rows of the input fake catalog which have half light radius, of either the bulge
or the disk,
555 that are 0. Also removes rows that have Sersic index outside of galsim
's allowed paramters. If
556 the config option sourceSelectionColName is set then this function limits the catalog of input fakes
557 to only those which are
True in this column.
559 Add the fake sources to the image.
563 _DefaultName = "insertFakes"
564 ConfigClass = InsertFakesConfig
566 def runDataRef(self, dataRef):
567 """Read in/write out the required data products and add fake sources to the deepCoadd.
572 Data reference defining the image to have fakes added to it
573 Used to access the following data products:
577 self.log.info("Adding fakes to: tract: %d, patch: %s, filter: %s",
578 dataRef.dataId[
"tract"], dataRef.dataId[
"patch"], dataRef.dataId[
"filter"])
582 if self.config.fakeType ==
"static":
583 fakeCat = dataRef.get(
"deepCoadd_fakeSourceCat").toDataFrame()
586 self.fakeSourceCatType =
"deepCoadd_fakeSourceCat"
588 fakeCat = Table.read(self.config.fakeType).to_pandas()
590 coadd = dataRef.get(
"deepCoadd")
592 photoCalib = coadd.getPhotoCalib()
594 imageWithFakes = self.run(fakeCat, coadd, wcs, photoCalib)
596 dataRef.put(imageWithFakes.imageWithFakes,
"fakes_deepCoadd")
598 def runQuantum(self, butlerQC, inputRefs, outputRefs):
599 inputs = butlerQC.get(inputRefs)
600 inputs[
"wcs"] = inputs[
"image"].getWcs()
601 inputs[
"photoCalib"] = inputs[
"image"].getPhotoCalib()
603 outputs = self.run(**inputs)
604 butlerQC.put(outputs, outputRefs)
607 def _makeArgumentParser(cls):
608 parser = pipeBase.ArgumentParser(name=cls._DefaultName)
609 parser.add_id_argument(name=
"--id", datasetType=
"deepCoadd",
610 help=
"data IDs for the deepCoadd, e.g. --id tract=12345 patch=1,2 filter=r",
611 ContainerClass=ExistingCoaddDataIdContainer)
614 def run(self, fakeCat, image, wcs, photoCalib):
615 """Add fake sources to an image.
619 fakeCat : `pandas.core.frame.DataFrame`
620 The catalog of fake sources to be input
621 image : `lsst.afw.image.exposure.exposure.ExposureF`
622 The image into which the fake sources should be added
624 WCS to use to add fake sources
625 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
626 Photometric calibration to be used to calibrate the fake sources
630 resultStruct : `lsst.pipe.base.struct.Struct`
631 contains : image : `lsst.afw.image.exposure.exposure.ExposureF`
635 Adds pixel coordinates for each source to the fakeCat
and removes objects
with bulge
or disk half
636 light radius = 0 (
if ``config.doCleanCat =
True``).
638 Adds the ``Fake`` mask plane to the image which
is then set by `addFakeSources` to mark where fake
639 sources have been added. Uses the information
in the ``fakeCat`` to make fake galaxies (using galsim)
640 and fake stars, using the PSF models
from the PSF information
for the image. These are then added to
641 the image
and the image
with fakes included returned.
643 The galsim galaxies are made using a double sersic profile, one
for the bulge
and one
for the disk,
644 this
is then convolved
with the PSF at that point.
648 origWcs = image.getWcs()
649 origPhotoCalib = image.getPhotoCalib()
651 image.setPhotoCalib(photoCalib)
653 band = image.getFilter().bandLabel
654 fakeCat = self._standardizeColumns(fakeCat, band)
656 fakeCat = self.addPixCoords(fakeCat, image)
657 fakeCat = self.trimFakeCat(fakeCat, image)
660 if not self.config.insertImages:
661 if isinstance(fakeCat[self.config.sourceType].iloc[0], str):
662 galCheckVal =
"galaxy"
663 starCheckVal =
"star"
664 trailCheckVal =
"trail"
665 elif isinstance(fakeCat[self.config.sourceType].iloc[0], bytes):
666 galCheckVal = b
"galaxy"
667 starCheckVal = b
"star"
668 trailCheckVal = b
"trail"
669 elif isinstance(fakeCat[self.config.sourceType].iloc[0], (int, float)):
675 "sourceType column does not have required type, should be str, bytes or int"
677 if self.config.doCleanCat:
678 fakeCat = self.cleanCat(fakeCat, starCheckVal)
680 generator = self._generateGSObjectsFromCatalog(image, fakeCat, galCheckVal, starCheckVal,
683 generator = self._generateGSObjectsFromImages(image, fakeCat)
684 _add_fake_sources(image, generator, calibFluxRadius=self.config.calibFluxRadius, logger=self.log)
685 elif len(fakeCat) == 0
and self.config.doProcessAllDataIds:
686 self.log.
warning(
"No fakes found for this dataRef; processing anyway.")
687 image.mask.addMaskPlane(
"FAKE")
689 raise RuntimeError(
"No fakes found for this dataRef.")
692 image.setWcs(origWcs)
693 image.setPhotoCalib(origPhotoCalib)
695 resultStruct = pipeBase.Struct(imageWithFakes=image)
699 def _standardizeColumns(self, fakeCat, band):
700 """Use config variables to 'standardize' the expected columns and column
701 names in the input catalog.
705 fakeCat : `pandas.core.frame.DataFrame`
706 The catalog of fake sources to be input
708 Label
for the current band being processed.
712 outCat : `pandas.core.frame.DataFrame`
713 The standardized catalog of fake sources
718 def add_to_replace_dict(new_name, depr_name, std_name):
719 if new_name
in fakeCat.columns:
720 replace_dict[new_name] = std_name
721 elif depr_name
in fakeCat.columns:
722 replace_dict[depr_name] = std_name
724 raise ValueError(f
"Could not determine column for {std_name}.")
728 for new_name, depr_name, std_name
in [
729 (cfg.ra_col, cfg.raColName,
'ra'),
730 (cfg.dec_col, cfg.decColName,
'dec'),
731 (cfg.mag_col%band, cfg.magVar%band,
'mag')
733 add_to_replace_dict(new_name, depr_name, std_name)
735 if not cfg.insertImages
and not cfg.insertOnlyStars:
736 for new_name, depr_name, std_name
in [
737 (cfg.bulge_n_col, cfg.nBulge,
'bulge_n'),
738 (cfg.bulge_pa_col, cfg.paBulge,
'bulge_pa'),
739 (cfg.disk_n_col, cfg.nDisk,
'disk_n'),
740 (cfg.disk_pa_col, cfg.paDisk,
'disk_pa'),
742 add_to_replace_dict(new_name, depr_name, std_name)
744 if cfg.doSubSelectSources:
747 cfg.sourceSelectionColName,
750 fakeCat = fakeCat.rename(columns=replace_dict, copy=
False)
755 if not cfg.insertImages
and not cfg.insertOnlyStars:
757 cfg.bulge_semimajor_col
in fakeCat.columns
758 and cfg.bulge_axis_ratio_col
in fakeCat.columns
760 fakeCat = fakeCat.rename(
762 cfg.bulge_semimajor_col:
'bulge_semimajor',
763 cfg.bulge_axis_ratio_col:
'bulge_axis_ratio',
764 cfg.disk_semimajor_col:
'disk_semimajor',
765 cfg.disk_axis_ratio_col:
'disk_axis_ratio',
770 cfg.bulgeHLR
in fakeCat.columns
771 and cfg.aBulge
in fakeCat.columns
772 and cfg.bBulge
in fakeCat.columns
774 fakeCat[
'bulge_axis_ratio'] = (
775 fakeCat[cfg.bBulge]/fakeCat[cfg.aBulge]
777 fakeCat[
'bulge_semimajor'] = (
778 fakeCat[cfg.bulgeHLR]/np.sqrt(fakeCat[
'bulge_axis_ratio'])
780 fakeCat[
'disk_axis_ratio'] = (
781 fakeCat[cfg.bDisk]/fakeCat[cfg.aDisk]
783 fakeCat[
'disk_semimajor'] = (
784 fakeCat[cfg.diskHLR]/np.sqrt(fakeCat[
'disk_axis_ratio'])
788 "Could not determine columns for half-light radius and "
793 if cfg.bulge_disk_flux_ratio_col
in fakeCat.columns:
794 fakeCat = fakeCat.rename(
796 cfg.bulge_disk_flux_ratio_col:
'bulge_disk_flux_ratio'
801 fakeCat[
'bulge_disk_flux_ratio'] = 1.0
805 def _generateGSObjectsFromCatalog(self, exposure, fakeCat, galCheckVal, starCheckVal, trailCheckVal):
806 """Process catalog to generate `galsim.GSObject` s.
810 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
811 The exposure into which the fake sources should be added
812 fakeCat : `pandas.core.frame.DataFrame`
813 The catalog of fake sources to be input
814 galCheckVal : `str`, `bytes` or `int`
815 The value that
is set
in the sourceType column to specify an object
is a galaxy.
816 starCheckVal : `str`, `bytes`
or `int`
817 The value that
is set
in the sourceType column to specify an object
is a star.
818 trailCheckVal : `str`, `bytes`
or `int`
819 The value that
is set
in the sourceType column to specify an object
is a star
823 gsObjects : `generator`
826 wcs = exposure.getWcs()
827 photoCalib = exposure.getPhotoCalib()
829 self.log.info("Making %d objects for insertion", len(fakeCat))
831 for (index, row)
in fakeCat.iterrows():
835 xy = wcs.skyToPixel(skyCoord)
838 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
842 sourceType = row[self.config.sourceType]
843 if sourceType == galCheckVal:
845 bulge_gs_HLR = row[
'bulge_semimajor']*np.sqrt(row[
'bulge_axis_ratio'])
846 bulge = galsim.Sersic(n=row[
'bulge_n'], half_light_radius=bulge_gs_HLR)
847 bulge = bulge.shear(q=row[
'bulge_axis_ratio'], beta=((90 - row[
'bulge_pa'])*galsim.degrees))
849 disk_gs_HLR = row[
'disk_semimajor']*np.sqrt(row[
'disk_axis_ratio'])
850 disk = galsim.Sersic(n=row[
'disk_n'], half_light_radius=disk_gs_HLR)
851 disk = disk.shear(q=row[
'disk_axis_ratio'], beta=((90 - row[
'disk_pa'])*galsim.degrees))
853 gal = bulge*row[
'bulge_disk_flux_ratio'] + disk
854 gal = gal.withFlux(flux)
857 elif sourceType == starCheckVal:
858 star = galsim.DeltaFunction()
859 star = star.withFlux(flux)
861 elif sourceType == trailCheckVal:
862 length = row[
'trail_length']
863 angle = row[
'trail_angle']
867 theta = galsim.Angle(angle*galsim.radians)
868 trail = galsim.Box(length, thickness)
869 trail = trail.rotate(theta)
870 trail = trail.withFlux(flux*length)
875 mat = wcs.linearizePixelToSky(skyCoord, geom.arcseconds).getMatrix()
876 trail = trail.transform(mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1])
878 yield skyCoord, trail
880 raise TypeError(f
"Unknown sourceType {sourceType}")
882 def _generateGSObjectsFromImages(self, exposure, fakeCat):
883 """Process catalog to generate `galsim.GSObject` s.
887 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
888 The exposure into which the fake sources should be added
889 fakeCat : `pandas.core.frame.DataFrame`
890 The catalog of fake sources to be input
894 gsObjects : `generator`
897 band = exposure.getFilter().bandLabel
898 wcs = exposure.getWcs()
899 photoCalib = exposure.getPhotoCalib()
901 self.log.info("Processing %d fake images", len(fakeCat))
903 for (index, row)
in fakeCat.iterrows():
907 xy = wcs.skyToPixel(skyCoord)
910 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
914 imFile = row[band+
"imFilename"]
916 imFile = imFile.decode(
"utf-8")
917 except AttributeError:
919 imFile = imFile.strip()
920 im = galsim.fits.read(imFile, read_header=
True)
922 if self.config.fits_alignment ==
"wcs":
929 if _isWCSGalsimDefault(im.wcs, im.header):
931 f
"Cannot find WCS in input FITS file {imFile}"
933 elif self.config.fits_alignment ==
"pixel":
936 linWcs = wcs.linearizePixelToSky(skyCoord, geom.arcseconds)
937 mat = linWcs.getMatrix()
938 im.wcs = galsim.JacobianWCS(
939 mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1]
943 f
"Unknown fits_alignment type {self.config.fits_alignment}"
946 obj = galsim.InterpolatedImage(im, calculate_stepk=
False)
947 obj = obj.withFlux(flux)
951 """Process images from files into the format needed for insertion.
955 fakeCat : `pandas.core.frame.DataFrame`
956 The catalog of fake sources to be input
957 wcs : `lsst.afw.geom.skyWcs.skyWcs.SkyWc`
958 WCS to use to add fake sources
959 psf : `lsst.meas.algorithms.coaddPsf.coaddPsf.CoaddPsf` or
960 `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
961 The PSF information to use to make the PSF images
962 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
963 Photometric calibration to be used to calibrate the fake sources
965 The filter band that the observation was taken
in.
967 The pixel scale of the image the sources are to be added to.
972 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF`
and
974 For sources labelled
as galaxy.
976 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF`
and
978 For sources labelled
as star.
982 The input fakes catalog needs to contain the absolute path to the image
in the
983 band that
is being used to add images to. It also needs to have the R.A.
and
984 declination of the fake source
in radians
and the sourceType of the object.
989 self.log.info("Processing %d fake images", len(fakeCat))
991 for (imFile, sourceType, mag, x, y)
in zip(fakeCat[band +
"imFilename"].array,
992 fakeCat[
"sourceType"].array,
993 fakeCat[
'mag'].array,
994 fakeCat[
"x"].array, fakeCat[
"y"].array):
996 im = afwImage.ImageF.readFits(imFile)
1003 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1004 psfKernel = psf.computeKernelImage(xy).getArray()
1005 psfKernel /= correctedFlux
1007 except InvalidParameterError:
1008 self.log.
info(
"%s at %0.4f, %0.4f outside of image", sourceType, x, y)
1011 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
1012 galsimIm = galsim.InterpolatedImage(galsim.Image(im.array), scale=pixelScale)
1013 convIm = galsim.Convolve([galsimIm, psfIm])
1016 outIm = convIm.drawImage(scale=pixelScale, method=
"real_space").array
1017 except (galsim.errors.GalSimFFTSizeError, MemoryError):
1020 imSum = np.sum(outIm)
1024 flux = photoCalib.magnitudeToInstFlux(mag, xy)
1028 imWithFlux = flux*divIm
1030 if sourceType == b
"galaxy":
1031 galImages.append((afwImage.ImageF(imWithFlux), xy))
1032 if sourceType == b
"star":
1033 starImages.append((afwImage.ImageF(imWithFlux), xy))
1035 return galImages, starImages
1039 """Add pixel coordinates to the catalog of fakes.
1043 fakeCat : `pandas.core.frame.DataFrame`
1044 The catalog of fake sources to be input
1045 image : `lsst.afw.image.exposure.exposure.ExposureF`
1046 The image into which the fake sources should be added
1050 fakeCat : `pandas.core.frame.DataFrame`
1052 wcs = image.getWcs()
1053 ras = fakeCat['ra'].values
1054 decs = fakeCat[
'dec'].values
1055 xs, ys = wcs.skyToPixelArray(ras, decs)
1062 """Trim the fake cat to the size of the input image plus trimBuffer padding.
1064 `fakeCat` must be processed with addPixCoords before using this method.
1068 fakeCat : `pandas.core.frame.DataFrame`
1069 The catalog of fake sources to be input
1070 image : `lsst.afw.image.exposure.exposure.ExposureF`
1071 The image into which the fake sources should be added
1075 fakeCat : `pandas.core.frame.DataFrame`
1076 The original fakeCat trimmed to the area of the image
1078 wideBbox = Box2D(image.getBBox()).dilatedBy(self.config.trimBuffer)
1082 ras = fakeCat[self.config.ra_col].values * u.rad
1083 decs = fakeCat[self.config.dec_col].values * u.rad
1085 isContainedRaDec = image.containsSkyCoords(ras, decs, padding=self.config.trimBuffer)
1088 xs = fakeCat[
"x"].values
1089 ys = fakeCat[
"y"].values
1091 isContainedXy = xs >= wideBbox.minX
1092 isContainedXy &= xs <= wideBbox.maxX
1093 isContainedXy &= ys >= wideBbox.minY
1094 isContainedXy &= ys <= wideBbox.maxY
1096 return fakeCat[isContainedRaDec & isContainedXy]
1099 """Make images of fake galaxies using GalSim.
1104 pixelScale : `float`
1105 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
1106 The PSF information to use to make the PSF images
1107 fakeCat : `pandas.core.frame.DataFrame`
1108 The catalog of fake sources to be input
1109 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
1110 Photometric calibration to be used to calibrate the fake sources
1114 galImages : `generator`
1115 A generator of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
1121 Fake galaxies are made by combining two sersic profiles, one
for the bulge
and one
for the disk. Each
1122 component has an individual sersic index (n), a, b
and position angle (PA). The combined profile
is
1123 then convolved
with the PSF at the specified x, y position on the image.
1125 The names of the columns
in the ``fakeCat`` are configurable
and are the column names
from the
1126 University of Washington simulations database
as default. For more information see the doc strings
1127 attached to the config options.
1129 See mkFakeStars doc string
for an explanation of calibration to instrumental flux.
1132 self.log.info("Making %d fake galaxy images", len(fakeCat))
1134 for (index, row)
in fakeCat.iterrows():
1140 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1141 psfKernel = psf.computeKernelImage(xy).getArray()
1142 psfKernel /= correctedFlux
1144 except InvalidParameterError:
1145 self.log.
info(
"Galaxy at %0.4f, %0.4f outside of image", row[
"x"], row[
"y"])
1149 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
1154 bulge_gs_HLR = row[
'bulge_semimajor']*np.sqrt(row[
'bulge_axis_ratio'])
1155 bulge = galsim.Sersic(n=row[
'bulge_n'], half_light_radius=bulge_gs_HLR)
1156 bulge = bulge.shear(q=row[
'bulge_axis_ratio'], beta=((90 - row[
'bulge_pa'])*galsim.degrees))
1158 disk_gs_HLR = row[
'disk_semimajor']*np.sqrt(row[
'disk_axis_ratio'])
1159 disk = galsim.Sersic(n=row[
'disk_n'], half_light_radius=disk_gs_HLR)
1160 disk = disk.shear(q=row[
'disk_axis_ratio'], beta=((90 - row[
'disk_pa'])*galsim.degrees))
1162 gal = bulge*row[
'bulge_disk_flux_ratio'] + disk
1163 gal = gal.withFlux(flux)
1165 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
1166 gal = galsim.Convolve([gal, psfIm])
1168 galIm = gal.drawImage(scale=pixelScale, method=
"real_space").array
1169 except (galsim.errors.GalSimFFTSizeError, MemoryError):
1172 yield (afwImage.ImageF(galIm), xy)
1176 """Make fake stars based off the properties in the fakeCat.
1181 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
1182 The PSF information to use to make the PSF images
1183 fakeCat : `pandas.core.frame.DataFrame`
1184 The catalog of fake sources to be input
1185 image : `lsst.afw.image.exposure.exposure.ExposureF`
1186 The image into which the fake sources should be added
1187 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
1188 Photometric calibration to be used to calibrate the fake sources
1192 starImages : `generator`
1193 A generator of tuples of `lsst.afw.image.ImageF` of fake stars and
1198 To take a given magnitude
and translate to the number of counts
in the image
1199 we use photoCalib.magnitudeToInstFlux, which returns the instrumental flux
for the
1200 given calibration radius used
in the photometric calibration step.
1201 Thus `calibFluxRadius` should be set to this same radius so that we can normalize
1202 the PSF model to the correct instrumental flux within calibFluxRadius.
1205 self.log.info("Making %d fake star images", len(fakeCat))
1207 for (index, row)
in fakeCat.iterrows():
1213 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1214 starIm = psf.computeImage(xy)
1215 starIm /= correctedFlux
1217 except InvalidParameterError:
1218 self.log.
info(
"Star at %0.4f, %0.4f outside of image", row[
"x"], row[
"y"])
1222 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
1227 yield ((starIm.convertF(), xy))
1230 """Remove rows from the fakes catalog which have HLR = 0 for either the buldge or disk component,
1231 also remove galaxies that have Sersic index outside the galsim min and max
1232 allowed (0.3 <= n <= 6.2).
1236 fakeCat : `pandas.core.frame.DataFrame`
1237 The catalog of fake sources to be input
1238 starCheckVal : `str`, `bytes`
or `int`
1239 The value that
is set
in the sourceType column to specifiy an object
is a star.
1243 fakeCat : `pandas.core.frame.DataFrame`
1244 The input catalog of fake sources but
with the bad objects removed
1247 rowsToKeep = (((fakeCat['bulge_semimajor'] != 0.0) & (fakeCat[
'disk_semimajor'] != 0.0))
1248 | (fakeCat[self.config.sourceType] == starCheckVal))
1249 numRowsNotUsed = len(fakeCat) - len(np.where(rowsToKeep)[0])
1250 self.log.
info(
"Removing %d rows with HLR = 0 for either the bulge or disk", numRowsNotUsed)
1251 fakeCat = fakeCat[rowsToKeep]
1253 minN = galsim.Sersic._minimum_n
1254 maxN = galsim.Sersic._maximum_n
1255 rowsWithGoodSersic = (((fakeCat[
'bulge_n'] >= minN) & (fakeCat[
'bulge_n'] <= maxN)
1256 & (fakeCat[
'disk_n'] >= minN) & (fakeCat[
'disk_n'] <= maxN))
1257 | (fakeCat[self.config.sourceType] == starCheckVal))
1258 numRowsNotUsed = len(fakeCat) - len(np.where(rowsWithGoodSersic)[0])
1259 self.log.
info(
"Removing %d rows of galaxies with nBulge or nDisk outside of %0.2f <= n <= %0.2f",
1260 numRowsNotUsed, minN, maxN)
1261 fakeCat = fakeCat[rowsWithGoodSersic]
1263 if self.config.doSubSelectSources:
1264 numRowsNotUsed = len(fakeCat) - len(fakeCat[
'select'])
1265 self.log.
info(
"Removing %d rows which were not designated as template sources", numRowsNotUsed)
1266 fakeCat = fakeCat[fakeCat[
'select']]
1271 """Add the fake sources to the given image
1275 image : `lsst.afw.image.exposure.exposure.ExposureF`
1276 The image into which the fake sources should be added
1277 fakeImages : `typing.Iterator` [`tuple` ['lsst.afw.image.ImageF`, `lsst.geom.Point2d`]]
1278 An iterator of tuples that contains (or generates) images of fake sources,
1279 and the locations they are to be inserted at.
1281 The type (star/galaxy) of fake sources input
1285 image : `lsst.afw.image.exposure.exposure.ExposureF`
1289 Uses the x, y information
in the ``fakeCat`` to position an image of the fake interpolated onto the
1290 pixel grid of the image. Sets the ``FAKE`` mask plane
for the pixels added
with the fake source.
1293 imageBBox = image.getBBox()
1294 imageMI = image.maskedImage
1296 for (fakeImage, xy)
in fakeImages:
1297 X0 = xy.getX() - fakeImage.getWidth()/2 + 0.5
1298 Y0 = xy.getY() - fakeImage.getHeight()/2 + 0.5
1299 self.log.
debug(
"Adding fake source at %d, %d", xy.getX(), xy.getY())
1300 if sourceType ==
"galaxy":
1303 interpFakeImage = fakeImage
1305 interpFakeImBBox = interpFakeImage.getBBox()
1306 interpFakeImBBox.clip(imageBBox)
1308 if interpFakeImBBox.getArea() > 0:
1309 imageMIView = imageMI[interpFakeImBBox]
1310 clippedFakeImage = interpFakeImage[interpFakeImBBox]
1311 clippedFakeImageMI = afwImage.MaskedImageF(clippedFakeImage)
1312 clippedFakeImageMI.mask.set(self.bitmask)
1313 imageMIView += clippedFakeImageMI
1317 def _getMetadataName(self):
1318 """Disable metadata writing"""
A 2-dimensional celestial WCS that transform pixels to ICRS RA/Dec, using the LSST standard for pixel...
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, dataIds, **kwargs)
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)