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",
266 fits_alignment = pexConfig.ChoiceField(
267 doc=
"How should injections from FITS files be aligned?",
271 "Input image will be transformed such that the local WCS in "
272 "the FITS header matches the local WCS in the target image. "
273 "I.e., North, East, and angular distances in the input image "
274 "will match North, East, and angular distances in the target "
278 "Input image will _not_ be transformed. Up, right, and pixel "
279 "distances in the input image will match up, right and pixel "
280 "distances in the target image."
288 ra_col = pexConfig.Field(
289 doc=
"Source catalog column name for RA (in radians).",
294 dec_col = pexConfig.Field(
295 doc=
"Source catalog column name for dec (in radians).",
300 bulge_semimajor_col = pexConfig.Field(
301 doc=
"Source catalog column name for the semimajor axis (in arcseconds) "
302 "of the bulge half-light ellipse.",
304 default=
"bulge_semimajor",
307 bulge_axis_ratio_col = pexConfig.Field(
308 doc=
"Source catalog column name for the axis ratio of the bulge "
309 "half-light ellipse.",
311 default=
"bulge_axis_ratio",
314 bulge_pa_col = pexConfig.Field(
315 doc=
"Source catalog column name for the position angle (measured from "
316 "North through East in degrees) of the semimajor axis of the bulge "
317 "half-light ellipse.",
322 bulge_n_col = pexConfig.Field(
323 doc=
"Source catalog column name for the Sersic index of the bulge.",
328 disk_semimajor_col = pexConfig.Field(
329 doc=
"Source catalog column name for the semimajor axis (in arcseconds) "
330 "of the disk half-light ellipse.",
332 default=
"disk_semimajor",
335 disk_axis_ratio_col = pexConfig.Field(
336 doc=
"Source catalog column name for the axis ratio of the disk "
337 "half-light ellipse.",
339 default=
"disk_axis_ratio",
342 disk_pa_col = pexConfig.Field(
343 doc=
"Source catalog column name for the position angle (measured from "
344 "North through East in degrees) of the semimajor axis of the disk "
345 "half-light ellipse.",
350 disk_n_col = pexConfig.Field(
351 doc=
"Source catalog column name for the Sersic index of the disk.",
356 bulge_disk_flux_ratio_col = pexConfig.Field(
357 doc=
"Source catalog column name for the bulge/disk flux ratio.",
359 default=
"bulge_disk_flux_ratio",
362 mag_col = pexConfig.Field(
363 doc=
"Source catalog column name template for magnitudes, in the format "
364 "``filter name``_mag_col. E.g., if this config variable is set to "
365 "``%s_mag``, then the i-band magnitude will be searched for in the "
366 "``i_mag`` column of the source catalog.",
371 select_col = pexConfig.Field(
372 doc=
"Source catalog column name to be used to select which sources to "
380 raColName = pexConfig.Field(
381 doc=
"RA column name used in the fake source catalog.",
384 deprecated=
"Use `ra_col` instead."
387 decColName = pexConfig.Field(
388 doc=
"Dec. column name used in the fake source catalog.",
391 deprecated=
"Use `dec_col` instead."
394 diskHLR = pexConfig.Field(
395 doc=
"Column name for the disk half light radius used in the fake source catalog.",
397 default=
"DiskHalfLightRadius",
399 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
400 " to specify disk half-light ellipse."
404 aDisk = pexConfig.Field(
405 doc=
"The column name for the semi major axis length of the disk component used in the fake source"
410 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
411 " to specify disk half-light ellipse."
415 bDisk = pexConfig.Field(
416 doc=
"The column name for the semi minor axis length of the disk component.",
420 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
421 " to specify disk half-light ellipse."
425 paDisk = pexConfig.Field(
426 doc=
"The column name for the PA of the disk component used in the fake source catalog.",
430 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
431 " to specify disk half-light ellipse."
435 nDisk = pexConfig.Field(
436 doc=
"The column name for the sersic index of the disk component used in the fake source catalog.",
439 deprecated=
"Use `disk_n` instead."
442 bulgeHLR = pexConfig.Field(
443 doc=
"Column name for the bulge half light radius used in the fake source catalog.",
445 default=
"BulgeHalfLightRadius",
447 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
448 "`bulge_pa_col` to specify disk half-light ellipse."
452 aBulge = pexConfig.Field(
453 doc=
"The column name for the semi major axis length of the bulge component.",
457 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
458 "`bulge_pa_col` to specify disk half-light ellipse."
462 bBulge = pexConfig.Field(
463 doc=
"The column name for the semi minor axis length of the bulge component used in the fake source "
468 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
469 "`bulge_pa_col` to specify disk half-light ellipse."
473 paBulge = pexConfig.Field(
474 doc=
"The column name for the PA of the bulge component used in the fake source catalog.",
478 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
479 "`bulge_pa_col` to specify disk half-light ellipse."
483 nBulge = pexConfig.Field(
484 doc=
"The column name for the sersic index of the bulge component used in the fake source catalog.",
487 deprecated=
"Use `bulge_n` instead."
490 magVar = pexConfig.Field(
491 doc=
"The column name for the magnitude calculated taking variability into account. In the format "
492 "``filter name``magVar, e.g. imagVar for the magnitude in the i band.",
495 deprecated=
"Use `mag_col` instead."
498 sourceSelectionColName = pexConfig.Field(
499 doc=
"The name of the column in the input fakes catalogue to be used to determine which sources to"
500 "add, default is none and when this is used all sources are added.",
502 default=
"templateSource",
503 deprecated=
"Use `select_col` instead."
507 class InsertFakesTask(PipelineTask, CmdLineTask):
508 """Insert fake objects into images.
510 Add fake stars and galaxies to the given image, read in through the dataRef. Galaxy parameters are read in
511 from the specified file and then modelled using galsim.
513 `InsertFakesTask` has five functions that make images of the fake sources and then add them to the
517 Use the WCS information to add the pixel coordinates of each source.
518 `mkFakeGalsimGalaxies`
519 Use Galsim to make fake double sersic galaxies for each set of galaxy parameters in the input file.
521 Use the PSF information from the image to make a fake star using the magnitude information from the
524 Remove rows of the input fake catalog which have half light radius, of either the bulge or the disk,
525 that are 0. Also removes rows that have Sersic index outside of galsim's allowed paramters. If
526 the config option sourceSelectionColName is set then this function limits the catalog of input fakes
527 to only those which are True in this column.
529 Add the fake sources to the image.
533 _DefaultName =
"insertFakes"
534 ConfigClass = InsertFakesConfig
536 def runDataRef(self, dataRef):
537 """Read in/write out the required data products and add fake sources to the deepCoadd.
541 dataRef : `lsst.daf.persistence.butlerSubset.ButlerDataRef`
542 Data reference defining the image to have fakes added to it
543 Used to access the following data products:
547 self.log.info(
"Adding fakes to: tract: %d, patch: %s, filter: %s",
548 dataRef.dataId[
"tract"], dataRef.dataId[
"patch"], dataRef.dataId[
"filter"])
552 if self.config.fakeType ==
"static":
553 fakeCat = dataRef.get(
"deepCoadd_fakeSourceCat").toDataFrame()
556 self.fakeSourceCatType =
"deepCoadd_fakeSourceCat"
558 fakeCat = Table.read(self.config.fakeType).to_pandas()
560 coadd = dataRef.get(
"deepCoadd")
562 photoCalib = coadd.getPhotoCalib()
564 imageWithFakes = self.run(fakeCat, coadd, wcs, photoCalib)
566 dataRef.put(imageWithFakes.imageWithFakes,
"fakes_deepCoadd")
568 def runQuantum(self, butlerQC, inputRefs, outputRefs):
569 inputs = butlerQC.get(inputRefs)
570 inputs[
"wcs"] = inputs[
"image"].getWcs()
571 inputs[
"photoCalib"] = inputs[
"image"].getPhotoCalib()
573 outputs = self.run(**inputs)
574 butlerQC.put(outputs, outputRefs)
577 def _makeArgumentParser(cls):
578 parser = pipeBase.ArgumentParser(name=cls._DefaultName)
579 parser.add_id_argument(name=
"--id", datasetType=
"deepCoadd",
580 help=
"data IDs for the deepCoadd, e.g. --id tract=12345 patch=1,2 filter=r",
581 ContainerClass=ExistingCoaddDataIdContainer)
584 def run(self, fakeCat, image, wcs, photoCalib):
585 """Add fake sources to an image.
589 fakeCat : `pandas.core.frame.DataFrame`
590 The catalog of fake sources to be input
591 image : `lsst.afw.image.exposure.exposure.ExposureF`
592 The image into which the fake sources should be added
593 wcs : `lsst.afw.geom.SkyWcs`
594 WCS to use to add fake sources
595 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
596 Photometric calibration to be used to calibrate the fake sources
600 resultStruct : `lsst.pipe.base.struct.Struct`
601 contains : image : `lsst.afw.image.exposure.exposure.ExposureF`
605 Adds pixel coordinates for each source to the fakeCat and removes objects with bulge or disk half
606 light radius = 0 (if ``config.doCleanCat = True``).
608 Adds the ``Fake`` mask plane to the image which is then set by `addFakeSources` to mark where fake
609 sources have been added. Uses the information in the ``fakeCat`` to make fake galaxies (using galsim)
610 and fake stars, using the PSF models from the PSF information for the image. These are then added to
611 the image and the image with fakes included returned.
613 The galsim galaxies are made using a double sersic profile, one for the bulge and one for the disk,
614 this is then convolved with the PSF at that point.
618 origWcs = image.getWcs()
619 origPhotoCalib = image.getPhotoCalib()
621 image.setPhotoCalib(photoCalib)
623 band = image.getFilterLabel().bandLabel
624 fakeCat = self._standardizeColumns(fakeCat, band)
626 fakeCat = self.addPixCoords(fakeCat, image)
627 fakeCat = self.trimFakeCat(fakeCat, image)
630 if not self.config.insertImages:
631 if isinstance(fakeCat[self.config.sourceType].iloc[0], str):
632 galCheckVal =
"galaxy"
633 starCheckVal =
"star"
634 elif isinstance(fakeCat[self.config.sourceType].iloc[0], bytes):
635 galCheckVal = b
"galaxy"
636 starCheckVal = b
"star"
637 elif isinstance(fakeCat[self.config.sourceType].iloc[0], (int, float)):
642 "sourceType column does not have required type, should be str, bytes or int"
644 if self.config.doCleanCat:
645 fakeCat = self.cleanCat(fakeCat, starCheckVal)
647 generator = self._generateGSObjectsFromCatalog(image, fakeCat, galCheckVal, starCheckVal)
649 generator = self._generateGSObjectsFromImages(image, fakeCat)
650 _add_fake_sources(image, generator, calibFluxRadius=self.config.calibFluxRadius, logger=self.log)
651 elif len(fakeCat) == 0
and self.config.doProcessAllDataIds:
652 self.log.warning(
"No fakes found for this dataRef; processing anyway.")
653 image.mask.addMaskPlane(
"FAKE")
655 raise RuntimeError(
"No fakes found for this dataRef.")
658 image.setWcs(origWcs)
659 image.setPhotoCalib(origPhotoCalib)
661 resultStruct = pipeBase.Struct(imageWithFakes=image)
665 def _standardizeColumns(self, fakeCat, band):
666 """Use config variables to 'standardize' the expected columns and column
667 names in the input catalog.
671 fakeCat : `pandas.core.frame.DataFrame`
672 The catalog of fake sources to be input
674 Label for the current band being processed.
678 outCat : `pandas.core.frame.DataFrame`
679 The standardized catalog of fake sources
684 def add_to_replace_dict(new_name, depr_name, std_name):
685 if new_name
in fakeCat.columns:
686 replace_dict[new_name] = std_name
687 elif depr_name
in fakeCat.columns:
688 replace_dict[depr_name] = std_name
690 raise ValueError(f
"Could not determine column for {std_name}.")
694 for new_name, depr_name, std_name
in [
695 (cfg.ra_col, cfg.raColName,
'ra'),
696 (cfg.dec_col, cfg.decColName,
'dec'),
697 (cfg.mag_col%band, cfg.magVar%band,
'mag')
699 add_to_replace_dict(new_name, depr_name, std_name)
701 if not cfg.insertImages:
702 for new_name, depr_name, std_name
in [
703 (cfg.bulge_n_col, cfg.nBulge,
'bulge_n'),
704 (cfg.bulge_pa_col, cfg.paBulge,
'bulge_pa'),
705 (cfg.disk_n_col, cfg.nDisk,
'disk_n'),
706 (cfg.disk_pa_col, cfg.paDisk,
'disk_pa'),
708 add_to_replace_dict(new_name, depr_name, std_name)
710 if cfg.doSubSelectSources:
713 cfg.sourceSelectionColName,
716 fakeCat = fakeCat.rename(columns=replace_dict, copy=
False)
721 if not cfg.insertImages:
723 cfg.bulge_semimajor_col
in fakeCat.columns
724 and cfg.bulge_axis_ratio_col
in fakeCat.columns
726 fakeCat = fakeCat.rename(
728 cfg.bulge_semimajor_col:
'bulge_semimajor',
729 cfg.bulge_axis_ratio_col:
'bulge_axis_ratio',
730 cfg.disk_semimajor_col:
'disk_semimajor',
731 cfg.disk_axis_ratio_col:
'disk_axis_ratio',
736 cfg.bulgeHLR
in fakeCat.columns
737 and cfg.aBulge
in fakeCat.columns
738 and cfg.bBulge
in fakeCat.columns
740 fakeCat[
'bulge_axis_ratio'] = (
741 fakeCat[cfg.bBulge]/fakeCat[cfg.aBulge]
743 fakeCat[
'bulge_semimajor'] = (
744 fakeCat[cfg.bulgeHLR]/np.sqrt(fakeCat[
'bulge_axis_ratio'])
746 fakeCat[
'disk_axis_ratio'] = (
747 fakeCat[cfg.bDisk]/fakeCat[cfg.aDisk]
749 fakeCat[
'disk_semimajor'] = (
750 fakeCat[cfg.diskHLR]/np.sqrt(fakeCat[
'disk_axis_ratio'])
754 "Could not determine columns for half-light radius and "
759 if cfg.bulge_disk_flux_ratio_col
in fakeCat.columns:
760 fakeCat = fakeCat.rename(
762 cfg.bulge_disk_flux_ratio_col:
'bulge_disk_flux_ratio'
767 fakeCat[
'bulge_disk_flux_ratio'] = 1.0
771 def _generateGSObjectsFromCatalog(self, exposure, fakeCat, galCheckVal, starCheckVal):
772 """Process catalog to generate `galsim.GSObject` s.
776 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
777 The exposure into which the fake sources should be added
778 fakeCat : `pandas.core.frame.DataFrame`
779 The catalog of fake sources to be input
780 galCheckVal : `str`, `bytes` or `int`
781 The value that is set in the sourceType column to specifiy an object is a galaxy.
782 starCheckVal : `str`, `bytes` or `int`
783 The value that is set in the sourceType column to specifiy an object is a star.
787 gsObjects : `generator`
788 A generator of tuples of `lsst.geom.SpherePoint` and `galsim.GSObject`.
790 wcs = exposure.getWcs()
791 photoCalib = exposure.getPhotoCalib()
793 self.log.info(
"Making %d objects for insertion", len(fakeCat))
795 for (index, row)
in fakeCat.iterrows():
799 xy = wcs.skyToPixel(skyCoord)
802 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
806 sourceType = row[self.config.sourceType]
807 if sourceType == galCheckVal:
809 bulge_gs_HLR = row[
'bulge_semimajor']*np.sqrt(row[
'bulge_axis_ratio'])
810 bulge = galsim.Sersic(n=row[
'bulge_n'], half_light_radius=bulge_gs_HLR)
811 bulge = bulge.shear(q=row[
'bulge_axis_ratio'], beta=((90 - row[
'bulge_pa'])*galsim.degrees))
813 disk_gs_HLR = row[
'disk_semimajor']*np.sqrt(row[
'disk_axis_ratio'])
814 disk = galsim.Sersic(n=row[
'disk_n'], half_light_radius=disk_gs_HLR)
815 disk = disk.shear(q=row[
'disk_axis_ratio'], beta=((90 - row[
'disk_pa'])*galsim.degrees))
817 gal = bulge*row[
'bulge_disk_flux_ratio'] + disk
818 gal = gal.withFlux(flux)
821 elif sourceType == starCheckVal:
822 star = galsim.DeltaFunction()
823 star = star.withFlux(flux)
826 raise TypeError(f
"Unknown sourceType {sourceType}")
828 def _generateGSObjectsFromImages(self, exposure, fakeCat):
829 """Process catalog to generate `galsim.GSObject` s.
833 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
834 The exposure into which the fake sources should be added
835 fakeCat : `pandas.core.frame.DataFrame`
836 The catalog of fake sources to be input
840 gsObjects : `generator`
841 A generator of tuples of `lsst.geom.SpherePoint` and `galsim.GSObject`.
843 band = exposure.getFilterLabel().bandLabel
844 wcs = exposure.getWcs()
845 photoCalib = exposure.getPhotoCalib()
847 self.log.info(
"Processing %d fake images", len(fakeCat))
849 for (index, row)
in fakeCat.iterrows():
853 xy = wcs.skyToPixel(skyCoord)
856 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
860 imFile = row[band+
"imFilename"]
862 imFile = imFile.decode(
"utf-8")
863 except AttributeError:
865 imFile = imFile.strip()
866 im = galsim.fits.read(imFile, read_header=
True)
868 if self.config.fits_alignment ==
"wcs":
875 if _isWCSGalsimDefault(im.wcs, im.header):
877 f
"Cannot find WCS in input FITS file {imFile}"
879 elif self.config.fits_alignment ==
"pixel":
882 linWcs = wcs.linearizePixelToSky(skyCoord, geom.arcseconds)
883 mat = linWcs.getMatrix()
884 im.wcs = galsim.JacobianWCS(
885 mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1]
889 f
"Unknown fits_alignment type {self.config.fits_alignment}"
892 obj = galsim.InterpolatedImage(im, calculate_stepk=
False)
893 obj = obj.withFlux(flux)
897 """Process images from files into the format needed for insertion.
901 fakeCat : `pandas.core.frame.DataFrame`
902 The catalog of fake sources to be input
903 wcs : `lsst.afw.geom.skyWcs.skyWcs.SkyWc`
904 WCS to use to add fake sources
905 psf : `lsst.meas.algorithms.coaddPsf.coaddPsf.CoaddPsf` or
906 `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
907 The PSF information to use to make the PSF images
908 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
909 Photometric calibration to be used to calibrate the fake sources
911 The filter band that the observation was taken in.
913 The pixel scale of the image the sources are to be added to.
918 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
919 `lsst.geom.Point2D` of their locations.
920 For sources labelled as galaxy.
922 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
923 `lsst.geom.Point2D` of their locations.
924 For sources labelled as star.
928 The input fakes catalog needs to contain the absolute path to the image in the
929 band that is being used to add images to. It also needs to have the R.A. and
930 declination of the fake source in radians and the sourceType of the object.
935 self.log.info(
"Processing %d fake images", len(fakeCat))
937 for (imFile, sourceType, mag, x, y)
in zip(fakeCat[band +
"imFilename"].array,
938 fakeCat[
"sourceType"].array,
939 fakeCat[
'mag'].array,
940 fakeCat[
"x"].array, fakeCat[
"y"].array):
942 im = afwImage.ImageF.readFits(imFile)
949 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
950 psfKernel = psf.computeKernelImage(xy).getArray()
951 psfKernel /= correctedFlux
953 except InvalidParameterError:
954 self.log.info(
"%s at %0.4f, %0.4f outside of image", sourceType, x, y)
957 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
958 galsimIm = galsim.InterpolatedImage(galsim.Image(im.array), scale=pixelScale)
959 convIm = galsim.Convolve([galsimIm, psfIm])
962 outIm = convIm.drawImage(scale=pixelScale, method=
"real_space").array
963 except (galsim.errors.GalSimFFTSizeError, MemoryError):
966 imSum = np.sum(outIm)
970 flux = photoCalib.magnitudeToInstFlux(mag, xy)
974 imWithFlux = flux*divIm
976 if sourceType == b
"galaxy":
977 galImages.append((afwImage.ImageF(imWithFlux), xy))
978 if sourceType == b
"star":
979 starImages.append((afwImage.ImageF(imWithFlux), xy))
981 return galImages, starImages
985 """Add pixel coordinates to the catalog of fakes.
989 fakeCat : `pandas.core.frame.DataFrame`
990 The catalog of fake sources to be input
991 image : `lsst.afw.image.exposure.exposure.ExposureF`
992 The image into which the fake sources should be added
996 fakeCat : `pandas.core.frame.DataFrame`
999 ras = fakeCat[
'ra'].values
1000 decs = fakeCat[
'dec'].values
1001 xs, ys = wcs.skyToPixelArray(ras, decs)
1008 """Trim the fake cat to about the size of the input image.
1010 `fakeCat` must be processed with addPixCoords before using this method.
1014 fakeCat : `pandas.core.frame.DataFrame`
1015 The catalog of fake sources to be input
1016 image : `lsst.afw.image.exposure.exposure.ExposureF`
1017 The image into which the fake sources should be added
1021 fakeCat : `pandas.core.frame.DataFrame`
1022 The original fakeCat trimmed to the area of the image
1025 bbox =
Box2D(image.getBBox()).dilatedBy(self.config.trimBuffer)
1026 xs = fakeCat[
"x"].values
1027 ys = fakeCat[
"y"].values
1029 isContained = xs >= bbox.minX
1030 isContained &= xs <= bbox.maxX
1031 isContained &= ys >= bbox.minY
1032 isContained &= ys <= bbox.maxY
1034 return fakeCat[isContained]
1037 """Make images of fake galaxies using GalSim.
1042 pixelScale : `float`
1043 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
1044 The PSF information to use to make the PSF images
1045 fakeCat : `pandas.core.frame.DataFrame`
1046 The catalog of fake sources to be input
1047 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
1048 Photometric calibration to be used to calibrate the fake sources
1052 galImages : `generator`
1053 A generator of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
1054 `lsst.geom.Point2D` of their locations.
1059 Fake galaxies are made by combining two sersic profiles, one for the bulge and one for the disk. Each
1060 component has an individual sersic index (n), a, b and position angle (PA). The combined profile is
1061 then convolved with the PSF at the specified x, y position on the image.
1063 The names of the columns in the ``fakeCat`` are configurable and are the column names from the
1064 University of Washington simulations database as default. For more information see the doc strings
1065 attached to the config options.
1067 See mkFakeStars doc string for an explanation of calibration to instrumental flux.
1070 self.log.info(
"Making %d fake galaxy images", len(fakeCat))
1072 for (index, row)
in fakeCat.iterrows():
1078 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1079 psfKernel = psf.computeKernelImage(xy).getArray()
1080 psfKernel /= correctedFlux
1082 except InvalidParameterError:
1083 self.log.info(
"Galaxy at %0.4f, %0.4f outside of image", row[
"x"], row[
"y"])
1087 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
1092 bulge_gs_HLR = row[
'bulge_semimajor']*np.sqrt(row[
'bulge_axis_ratio'])
1093 bulge = galsim.Sersic(n=row[
'bulge_n'], half_light_radius=bulge_gs_HLR)
1094 bulge = bulge.shear(q=row[
'bulge_axis_ratio'], beta=((90 - row[
'bulge_pa'])*galsim.degrees))
1096 disk_gs_HLR = row[
'disk_semimajor']*np.sqrt(row[
'disk_axis_ratio'])
1097 disk = galsim.Sersic(n=row[
'disk_n'], half_light_radius=disk_gs_HLR)
1098 disk = disk.shear(q=row[
'disk_axis_ratio'], beta=((90 - row[
'disk_pa'])*galsim.degrees))
1100 gal = bulge*row[
'bulge_disk_flux_ratio'] + disk
1101 gal = gal.withFlux(flux)
1103 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
1104 gal = galsim.Convolve([gal, psfIm])
1106 galIm = gal.drawImage(scale=pixelScale, method=
"real_space").array
1107 except (galsim.errors.GalSimFFTSizeError, MemoryError):
1110 yield (afwImage.ImageF(galIm), xy)
1114 """Make fake stars based off the properties in the fakeCat.
1119 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
1120 The PSF information to use to make the PSF images
1121 fakeCat : `pandas.core.frame.DataFrame`
1122 The catalog of fake sources to be input
1123 image : `lsst.afw.image.exposure.exposure.ExposureF`
1124 The image into which the fake sources should be added
1125 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
1126 Photometric calibration to be used to calibrate the fake sources
1130 starImages : `generator`
1131 A generator of tuples of `lsst.afw.image.ImageF` of fake stars and
1132 `lsst.geom.Point2D` of their locations.
1136 To take a given magnitude and translate to the number of counts in the image
1137 we use photoCalib.magnitudeToInstFlux, which returns the instrumental flux for the
1138 given calibration radius used in the photometric calibration step.
1139 Thus `calibFluxRadius` should be set to this same radius so that we can normalize
1140 the PSF model to the correct instrumental flux within calibFluxRadius.
1143 self.log.info(
"Making %d fake star images", len(fakeCat))
1145 for (index, row)
in fakeCat.iterrows():
1151 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1152 starIm = psf.computeImage(xy)
1153 starIm /= correctedFlux
1155 except InvalidParameterError:
1156 self.log.info(
"Star at %0.4f, %0.4f outside of image", row[
"x"], row[
"y"])
1160 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
1165 yield ((starIm.convertF(), xy))
1168 """Remove rows from the fakes catalog which have HLR = 0 for either the buldge or disk component,
1169 also remove galaxies that have Sersic index outside the galsim min and max
1170 allowed (0.3 <= n <= 6.2).
1174 fakeCat : `pandas.core.frame.DataFrame`
1175 The catalog of fake sources to be input
1176 starCheckVal : `str`, `bytes` or `int`
1177 The value that is set in the sourceType column to specifiy an object is a star.
1181 fakeCat : `pandas.core.frame.DataFrame`
1182 The input catalog of fake sources but with the bad objects removed
1185 rowsToKeep = (((fakeCat[
'bulge_semimajor'] != 0.0) & (fakeCat[
'disk_semimajor'] != 0.0))
1186 | (fakeCat[self.config.sourceType] == starCheckVal))
1187 numRowsNotUsed = len(fakeCat) - len(np.where(rowsToKeep)[0])
1188 self.log.info(
"Removing %d rows with HLR = 0 for either the bulge or disk", numRowsNotUsed)
1189 fakeCat = fakeCat[rowsToKeep]
1191 minN = galsim.Sersic._minimum_n
1192 maxN = galsim.Sersic._maximum_n
1193 rowsWithGoodSersic = (((fakeCat[
'bulge_n'] >= minN) & (fakeCat[
'bulge_n'] <= maxN)
1194 & (fakeCat[
'disk_n'] >= minN) & (fakeCat[
'disk_n'] <= maxN))
1195 | (fakeCat[self.config.sourceType] == starCheckVal))
1196 numRowsNotUsed = len(fakeCat) - len(np.where(rowsWithGoodSersic)[0])
1197 self.log.info(
"Removing %d rows of galaxies with nBulge or nDisk outside of %0.2f <= n <= %0.2f",
1198 numRowsNotUsed, minN, maxN)
1199 fakeCat = fakeCat[rowsWithGoodSersic]
1201 if self.config.doSubSelectSources:
1202 numRowsNotUsed = len(fakeCat) - len(fakeCat[
'select'])
1203 self.log.info(
"Removing %d rows which were not designated as template sources", numRowsNotUsed)
1204 fakeCat = fakeCat[fakeCat[
'select']]
1209 """Add the fake sources to the given image
1213 image : `lsst.afw.image.exposure.exposure.ExposureF`
1214 The image into which the fake sources should be added
1215 fakeImages : `typing.Iterator` [`tuple` ['lsst.afw.image.ImageF`, `lsst.geom.Point2d`]]
1216 An iterator of tuples that contains (or generates) images of fake sources,
1217 and the locations they are to be inserted at.
1219 The type (star/galaxy) of fake sources input
1223 image : `lsst.afw.image.exposure.exposure.ExposureF`
1227 Uses the x, y information in the ``fakeCat`` to position an image of the fake interpolated onto the
1228 pixel grid of the image. Sets the ``FAKE`` mask plane for the pixels added with the fake source.
1231 imageBBox = image.getBBox()
1232 imageMI = image.maskedImage
1234 for (fakeImage, xy)
in fakeImages:
1235 X0 = xy.getX() - fakeImage.getWidth()/2 + 0.5
1236 Y0 = xy.getY() - fakeImage.getHeight()/2 + 0.5
1237 self.log.debug(
"Adding fake source at %d, %d", xy.getX(), xy.getY())
1238 if sourceType ==
"galaxy":
1239 interpFakeImage = afwMath.offsetImage(fakeImage, X0, Y0,
"lanczos3")
1241 interpFakeImage = fakeImage
1243 interpFakeImBBox = interpFakeImage.getBBox()
1244 interpFakeImBBox.clip(imageBBox)
1246 if interpFakeImBBox.getArea() > 0:
1247 imageMIView = imageMI[interpFakeImBBox]
1248 clippedFakeImage = interpFakeImage[interpFakeImBBox]
1249 clippedFakeImageMI = afwImage.MaskedImageF(clippedFakeImage)
1250 clippedFakeImageMI.mask.set(self.bitmask)
1251 imageMIView += clippedFakeImageMI
1255 def _getMetadataName(self):
1256 """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)