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:
98 contained_pt = Point2D(
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 "
398 raColName = pexConfig.Field(
399 doc=
"RA column name used in the fake source catalog.",
402 deprecated=
"Use `ra_col` instead."
405 decColName = pexConfig.Field(
406 doc=
"Dec. column name used in the fake source catalog.",
409 deprecated=
"Use `dec_col` instead."
412 diskHLR = pexConfig.Field(
413 doc=
"Column name for the disk half light radius used in the fake source catalog.",
415 default=
"DiskHalfLightRadius",
417 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
418 " to specify disk half-light ellipse."
422 aDisk = pexConfig.Field(
423 doc=
"The column name for the semi major axis length of the disk component used in the fake source"
428 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
429 " to specify disk half-light ellipse."
433 bDisk = pexConfig.Field(
434 doc=
"The column name for the semi minor axis length of the disk component.",
438 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
439 " to specify disk half-light ellipse."
443 paDisk = pexConfig.Field(
444 doc=
"The column name for the PA of the disk component used in the fake source catalog.",
448 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
449 " to specify disk half-light ellipse."
453 nDisk = pexConfig.Field(
454 doc=
"The column name for the sersic index of the disk component used in the fake source catalog.",
457 deprecated=
"Use `disk_n_col` instead."
460 bulgeHLR = pexConfig.Field(
461 doc=
"Column name for the bulge half light radius used in the fake source catalog.",
463 default=
"BulgeHalfLightRadius",
465 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
466 "`bulge_pa_col` to specify disk half-light ellipse."
470 aBulge = pexConfig.Field(
471 doc=
"The column name for the semi major axis length of the bulge component.",
475 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
476 "`bulge_pa_col` to specify disk half-light ellipse."
480 bBulge = pexConfig.Field(
481 doc=
"The column name for the semi minor axis length of the bulge component used in the fake source "
486 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
487 "`bulge_pa_col` to specify disk half-light ellipse."
491 paBulge = pexConfig.Field(
492 doc=
"The column name for the PA of the bulge component used in the fake source catalog.",
496 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
497 "`bulge_pa_col` to specify disk half-light ellipse."
501 nBulge = pexConfig.Field(
502 doc=
"The column name for the sersic index of the bulge component used in the fake source catalog.",
505 deprecated=
"Use `bulge_n_col` instead."
508 magVar = pexConfig.Field(
509 doc=
"The column name for the magnitude calculated taking variability into account. In the format "
510 "``filter name``magVar, e.g. imagVar for the magnitude in the i band.",
513 deprecated=
"Use `mag_col` instead."
516 sourceSelectionColName = pexConfig.Field(
517 doc=
"The name of the column in the input fakes catalogue to be used to determine which sources to"
518 "add, default is none and when this is used all sources are added.",
520 default=
"templateSource",
521 deprecated=
"Use `select_col` instead."
525class InsertFakesTask(PipelineTask, CmdLineTask):
526 """Insert fake objects into images.
528 Add fake stars and galaxies to the given image, read
in through the dataRef. Galaxy parameters are read
in
529 from the specified file
and then modelled using galsim.
531 `InsertFakesTask` has five functions that make images of the fake sources
and then add them to the
535 Use the WCS information to add the pixel coordinates of each source.
536 `mkFakeGalsimGalaxies`
537 Use Galsim to make fake double sersic galaxies
for each set of galaxy parameters
in the input file.
539 Use the PSF information
from the image to make a fake star using the magnitude information
from the
542 Remove rows of the input fake catalog which have half light radius, of either the bulge
or the disk,
543 that are 0. Also removes rows that have Sersic index outside of galsim
's allowed paramters. If
544 the config option sourceSelectionColName is set then this function limits the catalog of input fakes
545 to only those which are
True in this column.
547 Add the fake sources to the image.
551 _DefaultName = "insertFakes"
552 ConfigClass = InsertFakesConfig
554 def runDataRef(self, dataRef):
555 """Read in/write out the required data products and add fake sources to the deepCoadd.
560 Data reference defining the image to have fakes added to it
561 Used to access the following data products:
565 self.log.info("Adding fakes to: tract: %d, patch: %s, filter: %s",
566 dataRef.dataId[
"tract"], dataRef.dataId[
"patch"], dataRef.dataId[
"filter"])
570 if self.config.fakeType ==
"static":
571 fakeCat = dataRef.get(
"deepCoadd_fakeSourceCat").toDataFrame()
574 self.fakeSourceCatType =
"deepCoadd_fakeSourceCat"
576 fakeCat = Table.read(self.config.fakeType).to_pandas()
578 coadd = dataRef.get(
"deepCoadd")
580 photoCalib = coadd.getPhotoCalib()
582 imageWithFakes = self.run(fakeCat, coadd, wcs, photoCalib)
584 dataRef.put(imageWithFakes.imageWithFakes,
"fakes_deepCoadd")
586 def runQuantum(self, butlerQC, inputRefs, outputRefs):
587 inputs = butlerQC.get(inputRefs)
588 inputs[
"wcs"] = inputs[
"image"].getWcs()
589 inputs[
"photoCalib"] = inputs[
"image"].getPhotoCalib()
591 outputs = self.run(**inputs)
592 butlerQC.put(outputs, outputRefs)
595 def _makeArgumentParser(cls):
596 parser = pipeBase.ArgumentParser(name=cls._DefaultName)
597 parser.add_id_argument(name=
"--id", datasetType=
"deepCoadd",
598 help=
"data IDs for the deepCoadd, e.g. --id tract=12345 patch=1,2 filter=r",
599 ContainerClass=ExistingCoaddDataIdContainer)
602 def run(self, fakeCat, image, wcs, photoCalib):
603 """Add fake sources to an image.
607 fakeCat : `pandas.core.frame.DataFrame`
608 The catalog of fake sources to be input
609 image : `lsst.afw.image.exposure.exposure.ExposureF`
610 The image into which the fake sources should be added
612 WCS to use to add fake sources
613 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
614 Photometric calibration to be used to calibrate the fake sources
618 resultStruct : `lsst.pipe.base.struct.Struct`
619 contains : image : `lsst.afw.image.exposure.exposure.ExposureF`
623 Adds pixel coordinates for each source to the fakeCat
and removes objects
with bulge
or disk half
624 light radius = 0 (
if ``config.doCleanCat =
True``).
626 Adds the ``Fake`` mask plane to the image which
is then set by `addFakeSources` to mark where fake
627 sources have been added. Uses the information
in the ``fakeCat`` to make fake galaxies (using galsim)
628 and fake stars, using the PSF models
from the PSF information
for the image. These are then added to
629 the image
and the image
with fakes included returned.
631 The galsim galaxies are made using a double sersic profile, one
for the bulge
and one
for the disk,
632 this
is then convolved
with the PSF at that point.
636 origWcs = image.getWcs()
637 origPhotoCalib = image.getPhotoCalib()
639 image.setPhotoCalib(photoCalib)
641 band = image.getFilterLabel().bandLabel
642 fakeCat = self._standardizeColumns(fakeCat, band)
644 fakeCat = self.addPixCoords(fakeCat, image)
645 fakeCat = self.trimFakeCat(fakeCat, image)
648 if not self.config.insertImages:
649 if isinstance(fakeCat[self.config.sourceType].iloc[0], str):
650 galCheckVal =
"galaxy"
651 starCheckVal =
"star"
652 elif isinstance(fakeCat[self.config.sourceType].iloc[0], bytes):
653 galCheckVal = b
"galaxy"
654 starCheckVal = b
"star"
655 elif isinstance(fakeCat[self.config.sourceType].iloc[0], (int, float)):
660 "sourceType column does not have required type, should be str, bytes or int"
662 if self.config.doCleanCat:
663 fakeCat = self.cleanCat(fakeCat, starCheckVal)
665 generator = self._generateGSObjectsFromCatalog(image, fakeCat, galCheckVal, starCheckVal)
667 generator = self._generateGSObjectsFromImages(image, fakeCat)
668 _add_fake_sources(image, generator, calibFluxRadius=self.config.calibFluxRadius, logger=self.log)
669 elif len(fakeCat) == 0
and self.config.doProcessAllDataIds:
670 self.log.warning(
"No fakes found for this dataRef; processing anyway.")
671 image.mask.addMaskPlane(
"FAKE")
673 raise RuntimeError(
"No fakes found for this dataRef.")
676 image.setWcs(origWcs)
677 image.setPhotoCalib(origPhotoCalib)
679 resultStruct = pipeBase.Struct(imageWithFakes=image)
683 def _standardizeColumns(self, fakeCat, band):
684 """Use config variables to 'standardize' the expected columns and column
685 names in the input catalog.
689 fakeCat : `pandas.core.frame.DataFrame`
690 The catalog of fake sources to be input
692 Label
for the current band being processed.
696 outCat : `pandas.core.frame.DataFrame`
697 The standardized catalog of fake sources
702 def add_to_replace_dict(new_name, depr_name, std_name):
703 if new_name
in fakeCat.columns:
704 replace_dict[new_name] = std_name
705 elif depr_name
in fakeCat.columns:
706 replace_dict[depr_name] = std_name
708 raise ValueError(f
"Could not determine column for {std_name}.")
712 for new_name, depr_name, std_name
in [
713 (cfg.ra_col, cfg.raColName,
'ra'),
714 (cfg.dec_col, cfg.decColName,
'dec'),
715 (cfg.mag_col%band, cfg.magVar%band,
'mag')
717 add_to_replace_dict(new_name, depr_name, std_name)
719 if not cfg.insertImages
and not cfg.insertOnlyStars:
720 for new_name, depr_name, std_name
in [
721 (cfg.bulge_n_col, cfg.nBulge,
'bulge_n'),
722 (cfg.bulge_pa_col, cfg.paBulge,
'bulge_pa'),
723 (cfg.disk_n_col, cfg.nDisk,
'disk_n'),
724 (cfg.disk_pa_col, cfg.paDisk,
'disk_pa'),
726 add_to_replace_dict(new_name, depr_name, std_name)
728 if cfg.doSubSelectSources:
731 cfg.sourceSelectionColName,
734 fakeCat = fakeCat.rename(columns=replace_dict, copy=
False)
739 if not cfg.insertImages
and not cfg.insertOnlyStars:
741 cfg.bulge_semimajor_col
in fakeCat.columns
742 and cfg.bulge_axis_ratio_col
in fakeCat.columns
744 fakeCat = fakeCat.rename(
746 cfg.bulge_semimajor_col:
'bulge_semimajor',
747 cfg.bulge_axis_ratio_col:
'bulge_axis_ratio',
748 cfg.disk_semimajor_col:
'disk_semimajor',
749 cfg.disk_axis_ratio_col:
'disk_axis_ratio',
754 cfg.bulgeHLR
in fakeCat.columns
755 and cfg.aBulge
in fakeCat.columns
756 and cfg.bBulge
in fakeCat.columns
758 fakeCat[
'bulge_axis_ratio'] = (
759 fakeCat[cfg.bBulge]/fakeCat[cfg.aBulge]
761 fakeCat[
'bulge_semimajor'] = (
762 fakeCat[cfg.bulgeHLR]/np.sqrt(fakeCat[
'bulge_axis_ratio'])
764 fakeCat[
'disk_axis_ratio'] = (
765 fakeCat[cfg.bDisk]/fakeCat[cfg.aDisk]
767 fakeCat[
'disk_semimajor'] = (
768 fakeCat[cfg.diskHLR]/np.sqrt(fakeCat[
'disk_axis_ratio'])
772 "Could not determine columns for half-light radius and "
777 if cfg.bulge_disk_flux_ratio_col
in fakeCat.columns:
778 fakeCat = fakeCat.rename(
780 cfg.bulge_disk_flux_ratio_col:
'bulge_disk_flux_ratio'
785 fakeCat[
'bulge_disk_flux_ratio'] = 1.0
789 def _generateGSObjectsFromCatalog(self, exposure, fakeCat, galCheckVal, starCheckVal):
790 """Process catalog to generate `galsim.GSObject` s.
794 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
795 The exposure into which the fake sources should be added
796 fakeCat : `pandas.core.frame.DataFrame`
797 The catalog of fake sources to be input
798 galCheckVal : `str`, `bytes` or `int`
799 The value that
is set
in the sourceType column to specifiy an object
is a galaxy.
800 starCheckVal : `str`, `bytes`
or `int`
801 The value that
is set
in the sourceType column to specifiy an object
is a star.
805 gsObjects : `generator`
808 wcs = exposure.getWcs()
809 photoCalib = exposure.getPhotoCalib()
811 self.log.info("Making %d objects for insertion", len(fakeCat))
813 for (index, row)
in fakeCat.iterrows():
817 xy = wcs.skyToPixel(skyCoord)
820 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
824 sourceType = row[self.config.sourceType]
825 if sourceType == galCheckVal:
827 bulge_gs_HLR = row[
'bulge_semimajor']*np.sqrt(row[
'bulge_axis_ratio'])
828 bulge = galsim.Sersic(n=row[
'bulge_n'], half_light_radius=bulge_gs_HLR)
829 bulge = bulge.shear(q=row[
'bulge_axis_ratio'], beta=((90 - row[
'bulge_pa'])*galsim.degrees))
831 disk_gs_HLR = row[
'disk_semimajor']*np.sqrt(row[
'disk_axis_ratio'])
832 disk = galsim.Sersic(n=row[
'disk_n'], half_light_radius=disk_gs_HLR)
833 disk = disk.shear(q=row[
'disk_axis_ratio'], beta=((90 - row[
'disk_pa'])*galsim.degrees))
835 gal = bulge*row[
'bulge_disk_flux_ratio'] + disk
836 gal = gal.withFlux(flux)
839 elif sourceType == starCheckVal:
840 star = galsim.DeltaFunction()
841 star = star.withFlux(flux)
844 raise TypeError(f
"Unknown sourceType {sourceType}")
846 def _generateGSObjectsFromImages(self, exposure, fakeCat):
847 """Process catalog to generate `galsim.GSObject` s.
851 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
852 The exposure into which the fake sources should be added
853 fakeCat : `pandas.core.frame.DataFrame`
854 The catalog of fake sources to be input
858 gsObjects : `generator`
861 band = exposure.getFilterLabel().bandLabel
862 wcs = exposure.getWcs()
863 photoCalib = exposure.getPhotoCalib()
865 self.log.info("Processing %d fake images", len(fakeCat))
867 for (index, row)
in fakeCat.iterrows():
871 xy = wcs.skyToPixel(skyCoord)
874 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
878 imFile = row[band+
"imFilename"]
880 imFile = imFile.decode(
"utf-8")
881 except AttributeError:
883 imFile = imFile.strip()
884 im = galsim.fits.read(imFile, read_header=
True)
886 if self.config.fits_alignment ==
"wcs":
893 if _isWCSGalsimDefault(im.wcs, im.header):
895 f
"Cannot find WCS in input FITS file {imFile}"
897 elif self.config.fits_alignment ==
"pixel":
900 linWcs = wcs.linearizePixelToSky(skyCoord, geom.arcseconds)
901 mat = linWcs.getMatrix()
902 im.wcs = galsim.JacobianWCS(
903 mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1]
907 f
"Unknown fits_alignment type {self.config.fits_alignment}"
910 obj = galsim.InterpolatedImage(im, calculate_stepk=
False)
911 obj = obj.withFlux(flux)
915 """Process images from files into the format needed for insertion.
919 fakeCat : `pandas.core.frame.DataFrame`
920 The catalog of fake sources to be input
921 wcs : `lsst.afw.geom.skyWcs.skyWcs.SkyWc`
922 WCS to use to add fake sources
923 psf : `lsst.meas.algorithms.coaddPsf.coaddPsf.CoaddPsf` or
924 `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
925 The PSF information to use to make the PSF images
926 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
927 Photometric calibration to be used to calibrate the fake sources
929 The filter band that the observation was taken
in.
931 The pixel scale of the image the sources are to be added to.
936 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF`
and
938 For sources labelled
as galaxy.
940 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF`
and
942 For sources labelled
as star.
946 The input fakes catalog needs to contain the absolute path to the image
in the
947 band that
is being used to add images to. It also needs to have the R.A.
and
948 declination of the fake source
in radians
and the sourceType of the object.
953 self.log.info("Processing %d fake images", len(fakeCat))
955 for (imFile, sourceType, mag, x, y)
in zip(fakeCat[band +
"imFilename"].array,
956 fakeCat[
"sourceType"].array,
957 fakeCat[
'mag'].array,
958 fakeCat[
"x"].array, fakeCat[
"y"].array):
960 im = afwImage.ImageF.readFits(imFile)
967 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
968 psfKernel = psf.computeKernelImage(xy).getArray()
969 psfKernel /= correctedFlux
971 except InvalidParameterError:
972 self.log.info(
"%s at %0.4f, %0.4f outside of image", sourceType, x, y)
975 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
976 galsimIm = galsim.InterpolatedImage(galsim.Image(im.array), scale=pixelScale)
977 convIm = galsim.Convolve([galsimIm, psfIm])
980 outIm = convIm.drawImage(scale=pixelScale, method=
"real_space").array
981 except (galsim.errors.GalSimFFTSizeError, MemoryError):
984 imSum = np.sum(outIm)
988 flux = photoCalib.magnitudeToInstFlux(mag, xy)
992 imWithFlux = flux*divIm
994 if sourceType == b
"galaxy":
995 galImages.append((afwImage.ImageF(imWithFlux), xy))
996 if sourceType == b
"star":
997 starImages.append((afwImage.ImageF(imWithFlux), xy))
999 return galImages, starImages
1003 """Add pixel coordinates to the catalog of fakes.
1007 fakeCat : `pandas.core.frame.DataFrame`
1008 The catalog of fake sources to be input
1009 image : `lsst.afw.image.exposure.exposure.ExposureF`
1010 The image into which the fake sources should be added
1014 fakeCat : `pandas.core.frame.DataFrame`
1016 wcs = image.getWcs()
1017 ras = fakeCat['ra'].values
1018 decs = fakeCat[
'dec'].values
1019 xs, ys = wcs.skyToPixelArray(ras, decs)
1026 """Trim the fake cat to the size of the input image plus trimBuffer padding.
1028 `fakeCat` must be processed with addPixCoords before using this method.
1032 fakeCat : `pandas.core.frame.DataFrame`
1033 The catalog of fake sources to be input
1034 image : `lsst.afw.image.exposure.exposure.ExposureF`
1035 The image into which the fake sources should be added
1039 fakeCat : `pandas.core.frame.DataFrame`
1040 The original fakeCat trimmed to the area of the image
1042 wideBbox = Box2D(image.getBBox()).dilatedBy(self.config.trimBuffer)
1046 ras = fakeCat[self.config.ra_col].values * u.rad
1047 decs = fakeCat[self.config.dec_col].values * u.rad
1049 isContainedRaDec = image.containsSkyCoords(ras, decs, padding=self.config.trimBuffer)
1052 xs = fakeCat[
"x"].values
1053 ys = fakeCat[
"y"].values
1055 isContainedXy = xs >= wideBbox.minX
1056 isContainedXy &= xs <= wideBbox.maxX
1057 isContainedXy &= ys >= wideBbox.minY
1058 isContainedXy &= ys <= wideBbox.maxY
1060 return fakeCat[isContainedRaDec & isContainedXy]
1063 """Make images of fake galaxies using GalSim.
1068 pixelScale : `float`
1069 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
1070 The PSF information to use to make the PSF images
1071 fakeCat : `pandas.core.frame.DataFrame`
1072 The catalog of fake sources to be input
1073 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
1074 Photometric calibration to be used to calibrate the fake sources
1078 galImages : `generator`
1079 A generator of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
1085 Fake galaxies are made by combining two sersic profiles, one
for the bulge
and one
for the disk. Each
1086 component has an individual sersic index (n), a, b
and position angle (PA). The combined profile
is
1087 then convolved
with the PSF at the specified x, y position on the image.
1089 The names of the columns
in the ``fakeCat`` are configurable
and are the column names
from the
1090 University of Washington simulations database
as default. For more information see the doc strings
1091 attached to the config options.
1093 See mkFakeStars doc string
for an explanation of calibration to instrumental flux.
1096 self.log.info("Making %d fake galaxy images", len(fakeCat))
1098 for (index, row)
in fakeCat.iterrows():
1104 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1105 psfKernel = psf.computeKernelImage(xy).getArray()
1106 psfKernel /= correctedFlux
1108 except InvalidParameterError:
1109 self.log.info(
"Galaxy at %0.4f, %0.4f outside of image", row[
"x"], row[
"y"])
1113 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
1118 bulge_gs_HLR = row[
'bulge_semimajor']*np.sqrt(row[
'bulge_axis_ratio'])
1119 bulge = galsim.Sersic(n=row[
'bulge_n'], half_light_radius=bulge_gs_HLR)
1120 bulge = bulge.shear(q=row[
'bulge_axis_ratio'], beta=((90 - row[
'bulge_pa'])*galsim.degrees))
1122 disk_gs_HLR = row[
'disk_semimajor']*np.sqrt(row[
'disk_axis_ratio'])
1123 disk = galsim.Sersic(n=row[
'disk_n'], half_light_radius=disk_gs_HLR)
1124 disk = disk.shear(q=row[
'disk_axis_ratio'], beta=((90 - row[
'disk_pa'])*galsim.degrees))
1126 gal = bulge*row[
'bulge_disk_flux_ratio'] + disk
1127 gal = gal.withFlux(flux)
1129 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
1130 gal = galsim.Convolve([gal, psfIm])
1132 galIm = gal.drawImage(scale=pixelScale, method=
"real_space").array
1133 except (galsim.errors.GalSimFFTSizeError, MemoryError):
1136 yield (afwImage.ImageF(galIm), xy)
1140 """Make fake stars based off the properties in the fakeCat.
1145 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
1146 The PSF information to use to make the PSF images
1147 fakeCat : `pandas.core.frame.DataFrame`
1148 The catalog of fake sources to be input
1149 image : `lsst.afw.image.exposure.exposure.ExposureF`
1150 The image into which the fake sources should be added
1151 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
1152 Photometric calibration to be used to calibrate the fake sources
1156 starImages : `generator`
1157 A generator of tuples of `lsst.afw.image.ImageF` of fake stars and
1162 To take a given magnitude
and translate to the number of counts
in the image
1163 we use photoCalib.magnitudeToInstFlux, which returns the instrumental flux
for the
1164 given calibration radius used
in the photometric calibration step.
1165 Thus `calibFluxRadius` should be set to this same radius so that we can normalize
1166 the PSF model to the correct instrumental flux within calibFluxRadius.
1169 self.log.info("Making %d fake star images", len(fakeCat))
1171 for (index, row)
in fakeCat.iterrows():
1177 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1178 starIm = psf.computeImage(xy)
1179 starIm /= correctedFlux
1181 except InvalidParameterError:
1182 self.log.info(
"Star at %0.4f, %0.4f outside of image", row[
"x"], row[
"y"])
1186 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
1191 yield ((starIm.convertF(), xy))
1194 """Remove rows from the fakes catalog which have HLR = 0 for either the buldge or disk component,
1195 also remove galaxies that have Sersic index outside the galsim min and max
1196 allowed (0.3 <= n <= 6.2).
1200 fakeCat : `pandas.core.frame.DataFrame`
1201 The catalog of fake sources to be input
1202 starCheckVal : `str`, `bytes`
or `int`
1203 The value that
is set
in the sourceType column to specifiy an object
is a star.
1207 fakeCat : `pandas.core.frame.DataFrame`
1208 The input catalog of fake sources but
with the bad objects removed
1211 rowsToKeep = (((fakeCat['bulge_semimajor'] != 0.0) & (fakeCat[
'disk_semimajor'] != 0.0))
1212 | (fakeCat[self.config.sourceType] == starCheckVal))
1213 numRowsNotUsed = len(fakeCat) - len(np.where(rowsToKeep)[0])
1214 self.log.info(
"Removing %d rows with HLR = 0 for either the bulge or disk", numRowsNotUsed)
1215 fakeCat = fakeCat[rowsToKeep]
1217 minN = galsim.Sersic._minimum_n
1218 maxN = galsim.Sersic._maximum_n
1219 rowsWithGoodSersic = (((fakeCat[
'bulge_n'] >= minN) & (fakeCat[
'bulge_n'] <= maxN)
1220 & (fakeCat[
'disk_n'] >= minN) & (fakeCat[
'disk_n'] <= maxN))
1221 | (fakeCat[self.config.sourceType] == starCheckVal))
1222 numRowsNotUsed = len(fakeCat) - len(np.where(rowsWithGoodSersic)[0])
1223 self.log.info(
"Removing %d rows of galaxies with nBulge or nDisk outside of %0.2f <= n <= %0.2f",
1224 numRowsNotUsed, minN, maxN)
1225 fakeCat = fakeCat[rowsWithGoodSersic]
1227 if self.config.doSubSelectSources:
1228 numRowsNotUsed = len(fakeCat) - len(fakeCat[
'select'])
1229 self.log.info(
"Removing %d rows which were not designated as template sources", numRowsNotUsed)
1230 fakeCat = fakeCat[fakeCat[
'select']]
1235 """Add the fake sources to the given image
1239 image : `lsst.afw.image.exposure.exposure.ExposureF`
1240 The image into which the fake sources should be added
1241 fakeImages : `typing.Iterator` [`tuple` ['lsst.afw.image.ImageF`, `lsst.geom.Point2d`]]
1242 An iterator of tuples that contains (or generates) images of fake sources,
1243 and the locations they are to be inserted at.
1245 The type (star/galaxy) of fake sources input
1249 image : `lsst.afw.image.exposure.exposure.ExposureF`
1253 Uses the x, y information
in the ``fakeCat`` to position an image of the fake interpolated onto the
1254 pixel grid of the image. Sets the ``FAKE`` mask plane
for the pixels added
with the fake source.
1257 imageBBox = image.getBBox()
1258 imageMI = image.maskedImage
1260 for (fakeImage, xy)
in fakeImages:
1261 X0 = xy.getX() - fakeImage.getWidth()/2 + 0.5
1262 Y0 = xy.getY() - fakeImage.getHeight()/2 + 0.5
1263 self.log.debug(
"Adding fake source at %d, %d", xy.getX(), xy.getY())
1264 if sourceType ==
"galaxy":
1265 interpFakeImage = afwMath.offsetImage(fakeImage, X0, Y0,
"lanczos3")
1267 interpFakeImage = fakeImage
1269 interpFakeImBBox = interpFakeImage.getBBox()
1270 interpFakeImBBox.clip(imageBBox)
1272 if interpFakeImBBox.getArea() > 0:
1273 imageMIView = imageMI[interpFakeImBBox]
1274 clippedFakeImage = interpFakeImage[interpFakeImBBox]
1275 clippedFakeImageMI = afwImage.MaskedImageF(clippedFakeImage)
1276 clippedFakeImageMI.mask.set(self.bitmask)
1277 imageMIView += clippedFakeImageMI
1281 def _getMetadataName(self):
1282 """Disable metadata writing"""
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)