23Insert fakes into deepCoadds
26from astropy.table
import Table
35from lsst.pipe.base import CmdLineTask, PipelineTask, PipelineTaskConfig, PipelineTaskConnections
36import lsst.pipe.base.connectionTypes
as cT
39from lsst.geom import SpherePoint, radians, Box2D, Point2D
41__all__ = [
"InsertFakesConfig",
"InsertFakesTask"]
44def _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 pixScale = wcs.getPixelScale(bbox.getCenter()).asArcseconds()
76 for spt, gsObj
in objects:
77 pt = wcs.skyToPixel(spt)
78 posd = galsim.PositionD(pt.x, pt.y)
79 posi = galsim.PositionI(pt.x//1, pt.y//1)
81 logger.debug(f
"Adding fake source at {pt}")
83 mat = wcs.linearizePixelToSky(spt, geom.arcseconds).getMatrix()
84 gsWCS = galsim.JacobianWCS(mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1])
89 gsPixScale = np.sqrt(gsWCS.pixelArea())
90 if gsPixScale < pixScale/2
or gsPixScale > pixScale*2:
94 psfArr = psf.computeKernelImage(pt).array
95 except InvalidParameterError:
97 contained_pt = Point2D(
98 np.clip(pt.x, bbox.minX, bbox.maxX),
99 np.clip(pt.y, bbox.minY, bbox.maxY)
101 if pt == contained_pt:
104 "Cannot compute Psf for object at {}; skipping",
110 psfArr = psf.computeKernelImage(contained_pt).array
111 except InvalidParameterError:
114 "Cannot compute Psf for object at {}; skipping",
119 apCorr = psf.computeApertureFlux(calibFluxRadius)
121 gsPSF = galsim.InterpolatedImage(galsim.Image(psfArr), wcs=gsWCS)
123 conv = galsim.Convolve(gsObj, gsPSF)
124 stampSize = conv.getGoodImageSize(gsWCS.minLinearScale())
125 subBounds = galsim.BoundsI(posi).withBorder(stampSize//2)
126 subBounds &= fullBounds
128 if subBounds.area() > 0:
129 subImg = gsImg[subBounds]
130 offset = posd - subBounds.true_center
147 exposure[subBox].mask.array |= bitmask
150def _isWCSGalsimDefault(wcs, hdr):
151 """Decide if wcs = galsim.PixelScale(1.0) is explicitly present in header,
152 or if it
's just the galsim default.
157 Potentially default WCS.
158 hdr : galsim.fits.FitsHeader
159 Header as read
in by galsim.
164 True if default,
False if explicitly set
in header.
166 if wcs != galsim.PixelScale(1.0):
168 if hdr.get(
'GS_WCS')
is not None:
170 if hdr.get(
'CTYPE1',
'LINEAR') ==
'LINEAR':
171 return not any(k
in hdr
for k
in [
'CD1_1',
'CDELT1'])
172 for wcs_type
in galsim.fitswcs.fits_wcs_types:
175 wcs_type._readHeader(hdr)
180 return not any(k
in hdr
for k
in [
'CD1_1',
'CDELT1'])
184 defaultTemplates={
"coaddName":
"deep",
185 "fakesType":
"fakes_"},
186 dimensions=(
"tract",
"patch",
"band",
"skymap")):
189 doc=
"Image into which fakes are to be added.",
190 name=
"{coaddName}Coadd",
191 storageClass=
"ExposureF",
192 dimensions=(
"tract",
"patch",
"band",
"skymap")
196 doc=
"Catalog of fake sources to draw inputs from.",
197 name=
"{fakesType}fakeSourceCat",
198 storageClass=
"DataFrame",
199 dimensions=(
"tract",
"skymap")
202 imageWithFakes = cT.Output(
203 doc=
"Image with fake sources added.",
204 name=
"{fakesType}{coaddName}Coadd",
205 storageClass=
"ExposureF",
206 dimensions=(
"tract",
"patch",
"band",
"skymap")
210class InsertFakesConfig(PipelineTaskConfig,
211 pipelineConnections=InsertFakesConnections):
212 """Config for inserting fake sources
217 doCleanCat = pexConfig.Field(
218 doc=
"If true removes bad sources from the catalog.",
223 fakeType = pexConfig.Field(
224 doc=
"What type of fake catalog to use, snapshot (includes variability in the magnitudes calculated "
225 "from the MJD of the image), static (no variability) or filename for a user defined fits"
231 calibFluxRadius = pexConfig.Field(
232 doc=
"Aperture radius (in pixels) that was used to define the calibration for this image+catalog. "
233 "This will be used to produce the correct instrumental fluxes within the radius. "
234 "This value should match that of the field defined in slot_CalibFlux_instFlux.",
239 coaddName = pexConfig.Field(
240 doc=
"The name of the type of coadd used",
245 doSubSelectSources = pexConfig.Field(
246 doc=
"Set to True if you wish to sub select sources to be input based on the value in the column"
247 "set in the sourceSelectionColName config option.",
252 insertImages = pexConfig.Field(
253 doc=
"Insert images directly? True or False.",
258 insertOnlyStars = pexConfig.Field(
259 doc=
"Insert only stars? True or False.",
264 doProcessAllDataIds = pexConfig.Field(
265 doc=
"If True, all input data IDs will be processed, even those containing no fake sources.",
270 trimBuffer = pexConfig.Field(
271 doc=
"Size of the pixel buffer surrounding the image. Only those fake sources with a centroid"
272 "falling within the image+buffer region will be considered for fake source injection.",
277 sourceType = pexConfig.Field(
278 doc=
"The column name for the source type used in the fake source catalog.",
280 default=
"sourceType",
283 fits_alignment = pexConfig.ChoiceField(
284 doc=
"How should injections from FITS files be aligned?",
288 "Input image will be transformed such that the local WCS in "
289 "the FITS header matches the local WCS in the target image. "
290 "I.e., North, East, and angular distances in the input image "
291 "will match North, East, and angular distances in the target "
295 "Input image will _not_ be transformed. Up, right, and pixel "
296 "distances in the input image will match up, right and pixel "
297 "distances in the target image."
305 ra_col = pexConfig.Field(
306 doc=
"Source catalog column name for RA (in radians).",
311 dec_col = pexConfig.Field(
312 doc=
"Source catalog column name for dec (in radians).",
317 bulge_semimajor_col = pexConfig.Field(
318 doc=
"Source catalog column name for the semimajor axis (in arcseconds) "
319 "of the bulge half-light ellipse.",
321 default=
"bulge_semimajor",
324 bulge_axis_ratio_col = pexConfig.Field(
325 doc=
"Source catalog column name for the axis ratio of the bulge "
326 "half-light ellipse.",
328 default=
"bulge_axis_ratio",
331 bulge_pa_col = pexConfig.Field(
332 doc=
"Source catalog column name for the position angle (measured from "
333 "North through East in degrees) of the semimajor axis of the bulge "
334 "half-light ellipse.",
339 bulge_n_col = pexConfig.Field(
340 doc=
"Source catalog column name for the Sersic index of the bulge.",
345 disk_semimajor_col = pexConfig.Field(
346 doc=
"Source catalog column name for the semimajor axis (in arcseconds) "
347 "of the disk half-light ellipse.",
349 default=
"disk_semimajor",
352 disk_axis_ratio_col = pexConfig.Field(
353 doc=
"Source catalog column name for the axis ratio of the disk "
354 "half-light ellipse.",
356 default=
"disk_axis_ratio",
359 disk_pa_col = pexConfig.Field(
360 doc=
"Source catalog column name for the position angle (measured from "
361 "North through East in degrees) of the semimajor axis of the disk "
362 "half-light ellipse.",
367 disk_n_col = pexConfig.Field(
368 doc=
"Source catalog column name for the Sersic index of the disk.",
373 bulge_disk_flux_ratio_col = pexConfig.Field(
374 doc=
"Source catalog column name for the bulge/disk flux ratio.",
376 default=
"bulge_disk_flux_ratio",
379 mag_col = pexConfig.Field(
380 doc=
"Source catalog column name template for magnitudes, in the format "
381 "``filter name``_mag_col. E.g., if this config variable is set to "
382 "``%s_mag``, then the i-band magnitude will be searched for in the "
383 "``i_mag`` column of the source catalog.",
388 select_col = pexConfig.Field(
389 doc=
"Source catalog column name to be used to select which sources to "
397 raColName = pexConfig.Field(
398 doc=
"RA column name used in the fake source catalog.",
401 deprecated=
"Use `ra_col` instead."
404 decColName = pexConfig.Field(
405 doc=
"Dec. column name used in the fake source catalog.",
408 deprecated=
"Use `dec_col` instead."
411 diskHLR = pexConfig.Field(
412 doc=
"Column name for the disk half light radius used in the fake source catalog.",
414 default=
"DiskHalfLightRadius",
416 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
417 " to specify disk half-light ellipse."
421 aDisk = pexConfig.Field(
422 doc=
"The column name for the semi major axis length of the disk component used in the fake source"
427 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
428 " to specify disk half-light ellipse."
432 bDisk = pexConfig.Field(
433 doc=
"The column name for the semi minor axis length of the disk component.",
437 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
438 " to specify disk half-light ellipse."
442 paDisk = pexConfig.Field(
443 doc=
"The column name for the PA of the disk component used in the fake source catalog.",
447 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
448 " to specify disk half-light ellipse."
452 nDisk = pexConfig.Field(
453 doc=
"The column name for the sersic index of the disk component used in the fake source catalog.",
456 deprecated=
"Use `disk_n_col` instead."
459 bulgeHLR = pexConfig.Field(
460 doc=
"Column name for the bulge half light radius used in the fake source catalog.",
462 default=
"BulgeHalfLightRadius",
464 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
465 "`bulge_pa_col` to specify disk half-light ellipse."
469 aBulge = pexConfig.Field(
470 doc=
"The column name for the semi major axis length of the bulge component.",
474 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
475 "`bulge_pa_col` to specify disk half-light ellipse."
479 bBulge = pexConfig.Field(
480 doc=
"The column name for the semi minor axis length of the bulge component used in the fake source "
485 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
486 "`bulge_pa_col` to specify disk half-light ellipse."
490 paBulge = pexConfig.Field(
491 doc=
"The column name for the PA of the bulge component used in the fake source catalog.",
495 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
496 "`bulge_pa_col` to specify disk half-light ellipse."
500 nBulge = pexConfig.Field(
501 doc=
"The column name for the sersic index of the bulge component used in the fake source catalog.",
504 deprecated=
"Use `bulge_n_col` instead."
507 magVar = pexConfig.Field(
508 doc=
"The column name for the magnitude calculated taking variability into account. In the format "
509 "``filter name``magVar, e.g. imagVar for the magnitude in the i band.",
512 deprecated=
"Use `mag_col` instead."
515 sourceSelectionColName = pexConfig.Field(
516 doc=
"The name of the column in the input fakes catalogue to be used to determine which sources to"
517 "add, default is none and when this is used all sources are added.",
519 default=
"templateSource",
520 deprecated=
"Use `select_col` instead."
524class InsertFakesTask(PipelineTask, CmdLineTask):
525 """Insert fake objects into images.
527 Add fake stars and galaxies to the given image, read
in through the dataRef. Galaxy parameters are read
in
528 from the specified file
and then modelled using galsim.
530 `InsertFakesTask` has five functions that make images of the fake sources
and then add them to the
534 Use the WCS information to add the pixel coordinates of each source.
535 `mkFakeGalsimGalaxies`
536 Use Galsim to make fake double sersic galaxies
for each set of galaxy parameters
in the input file.
538 Use the PSF information
from the image to make a fake star using the magnitude information
from the
541 Remove rows of the input fake catalog which have half light radius, of either the bulge
or the disk,
542 that are 0. Also removes rows that have Sersic index outside of galsim
's allowed paramters. If
543 the config option sourceSelectionColName is set then this function limits the catalog of input fakes
544 to only those which are
True in this column.
546 Add the fake sources to the image.
550 _DefaultName = "insertFakes"
551 ConfigClass = InsertFakesConfig
553 def runDataRef(self, dataRef):
554 """Read in/write out the required data products and add fake sources to the deepCoadd.
559 Data reference defining the image to have fakes added to it
560 Used to access the following data products:
564 self.log.info("Adding fakes to: tract: %d, patch: %s, filter: %s",
565 dataRef.dataId[
"tract"], dataRef.dataId[
"patch"], dataRef.dataId[
"filter"])
569 if self.config.fakeType ==
"static":
570 fakeCat = dataRef.get(
"deepCoadd_fakeSourceCat").toDataFrame()
573 self.fakeSourceCatType =
"deepCoadd_fakeSourceCat"
575 fakeCat = Table.read(self.config.fakeType).to_pandas()
577 coadd = dataRef.get(
"deepCoadd")
579 photoCalib = coadd.getPhotoCalib()
581 imageWithFakes = self.run(fakeCat, coadd, wcs, photoCalib)
583 dataRef.put(imageWithFakes.imageWithFakes,
"fakes_deepCoadd")
585 def runQuantum(self, butlerQC, inputRefs, outputRefs):
586 inputs = butlerQC.get(inputRefs)
587 inputs[
"wcs"] = inputs[
"image"].getWcs()
588 inputs[
"photoCalib"] = inputs[
"image"].getPhotoCalib()
590 outputs = self.run(**inputs)
591 butlerQC.put(outputs, outputRefs)
594 def _makeArgumentParser(cls):
595 parser = pipeBase.ArgumentParser(name=cls._DefaultName)
596 parser.add_id_argument(name=
"--id", datasetType=
"deepCoadd",
597 help=
"data IDs for the deepCoadd, e.g. --id tract=12345 patch=1,2 filter=r",
598 ContainerClass=ExistingCoaddDataIdContainer)
601 def run(self, fakeCat, image, wcs, photoCalib):
602 """Add fake sources to an image.
606 fakeCat : `pandas.core.frame.DataFrame`
607 The catalog of fake sources to be input
608 image : `lsst.afw.image.exposure.exposure.ExposureF`
609 The image into which the fake sources should be added
611 WCS to use to add fake sources
612 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
613 Photometric calibration to be used to calibrate the fake sources
617 resultStruct : `lsst.pipe.base.struct.Struct`
618 contains : image : `lsst.afw.image.exposure.exposure.ExposureF`
622 Adds pixel coordinates for each source to the fakeCat
and removes objects
with bulge
or disk half
623 light radius = 0 (
if ``config.doCleanCat =
True``).
625 Adds the ``Fake`` mask plane to the image which
is then set by `addFakeSources` to mark where fake
626 sources have been added. Uses the information
in the ``fakeCat`` to make fake galaxies (using galsim)
627 and fake stars, using the PSF models
from the PSF information
for the image. These are then added to
628 the image
and the image
with fakes included returned.
630 The galsim galaxies are made using a double sersic profile, one
for the bulge
and one
for the disk,
631 this
is then convolved
with the PSF at that point.
635 origWcs = image.getWcs()
636 origPhotoCalib = image.getPhotoCalib()
638 image.setPhotoCalib(photoCalib)
640 band = image.getFilterLabel().bandLabel
641 fakeCat = self._standardizeColumns(fakeCat, band)
643 fakeCat = self.addPixCoords(fakeCat, image)
644 fakeCat = self.trimFakeCat(fakeCat, image)
647 if not self.config.insertImages:
648 if isinstance(fakeCat[self.config.sourceType].iloc[0], str):
649 galCheckVal =
"galaxy"
650 starCheckVal =
"star"
651 elif isinstance(fakeCat[self.config.sourceType].iloc[0], bytes):
652 galCheckVal = b
"galaxy"
653 starCheckVal = b
"star"
654 elif isinstance(fakeCat[self.config.sourceType].iloc[0], (int, float)):
659 "sourceType column does not have required type, should be str, bytes or int"
661 if self.config.doCleanCat:
662 fakeCat = self.cleanCat(fakeCat, starCheckVal)
664 generator = self._generateGSObjectsFromCatalog(image, fakeCat, galCheckVal, starCheckVal)
666 generator = self._generateGSObjectsFromImages(image, fakeCat)
667 _add_fake_sources(image, generator, calibFluxRadius=self.config.calibFluxRadius, logger=self.log)
668 elif len(fakeCat) == 0
and self.config.doProcessAllDataIds:
669 self.log.warning(
"No fakes found for this dataRef; processing anyway.")
670 image.mask.addMaskPlane(
"FAKE")
672 raise RuntimeError(
"No fakes found for this dataRef.")
675 image.setWcs(origWcs)
676 image.setPhotoCalib(origPhotoCalib)
678 resultStruct = pipeBase.Struct(imageWithFakes=image)
682 def _standardizeColumns(self, fakeCat, band):
683 """Use config variables to 'standardize' the expected columns and column
684 names in the input catalog.
688 fakeCat : `pandas.core.frame.DataFrame`
689 The catalog of fake sources to be input
691 Label
for the current band being processed.
695 outCat : `pandas.core.frame.DataFrame`
696 The standardized catalog of fake sources
701 def add_to_replace_dict(new_name, depr_name, std_name):
702 if new_name
in fakeCat.columns:
703 replace_dict[new_name] = std_name
704 elif depr_name
in fakeCat.columns:
705 replace_dict[depr_name] = std_name
707 raise ValueError(f
"Could not determine column for {std_name}.")
711 for new_name, depr_name, std_name
in [
712 (cfg.ra_col, cfg.raColName,
'ra'),
713 (cfg.dec_col, cfg.decColName,
'dec'),
714 (cfg.mag_col%band, cfg.magVar%band,
'mag')
716 add_to_replace_dict(new_name, depr_name, std_name)
718 if not cfg.insertImages
and not cfg.insertOnlyStars:
719 for new_name, depr_name, std_name
in [
720 (cfg.bulge_n_col, cfg.nBulge,
'bulge_n'),
721 (cfg.bulge_pa_col, cfg.paBulge,
'bulge_pa'),
722 (cfg.disk_n_col, cfg.nDisk,
'disk_n'),
723 (cfg.disk_pa_col, cfg.paDisk,
'disk_pa'),
725 add_to_replace_dict(new_name, depr_name, std_name)
727 if cfg.doSubSelectSources:
730 cfg.sourceSelectionColName,
733 fakeCat = fakeCat.rename(columns=replace_dict, copy=
False)
738 if not cfg.insertImages
and not cfg.insertOnlyStars:
740 cfg.bulge_semimajor_col
in fakeCat.columns
741 and cfg.bulge_axis_ratio_col
in fakeCat.columns
743 fakeCat = fakeCat.rename(
745 cfg.bulge_semimajor_col:
'bulge_semimajor',
746 cfg.bulge_axis_ratio_col:
'bulge_axis_ratio',
747 cfg.disk_semimajor_col:
'disk_semimajor',
748 cfg.disk_axis_ratio_col:
'disk_axis_ratio',
753 cfg.bulgeHLR
in fakeCat.columns
754 and cfg.aBulge
in fakeCat.columns
755 and cfg.bBulge
in fakeCat.columns
757 fakeCat[
'bulge_axis_ratio'] = (
758 fakeCat[cfg.bBulge]/fakeCat[cfg.aBulge]
760 fakeCat[
'bulge_semimajor'] = (
761 fakeCat[cfg.bulgeHLR]/np.sqrt(fakeCat[
'bulge_axis_ratio'])
763 fakeCat[
'disk_axis_ratio'] = (
764 fakeCat[cfg.bDisk]/fakeCat[cfg.aDisk]
766 fakeCat[
'disk_semimajor'] = (
767 fakeCat[cfg.diskHLR]/np.sqrt(fakeCat[
'disk_axis_ratio'])
771 "Could not determine columns for half-light radius and "
776 if cfg.bulge_disk_flux_ratio_col
in fakeCat.columns:
777 fakeCat = fakeCat.rename(
779 cfg.bulge_disk_flux_ratio_col:
'bulge_disk_flux_ratio'
784 fakeCat[
'bulge_disk_flux_ratio'] = 1.0
788 def _generateGSObjectsFromCatalog(self, exposure, fakeCat, galCheckVal, starCheckVal):
789 """Process catalog to generate `galsim.GSObject` s.
793 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
794 The exposure into which the fake sources should be added
795 fakeCat : `pandas.core.frame.DataFrame`
796 The catalog of fake sources to be input
797 galCheckVal : `str`, `bytes` or `int`
798 The value that
is set
in the sourceType column to specifiy an object
is a galaxy.
799 starCheckVal : `str`, `bytes`
or `int`
800 The value that
is set
in the sourceType column to specifiy an object
is a star.
804 gsObjects : `generator`
807 wcs = exposure.getWcs()
808 photoCalib = exposure.getPhotoCalib()
810 self.log.info("Making %d objects for insertion", len(fakeCat))
812 for (index, row)
in fakeCat.iterrows():
816 xy = wcs.skyToPixel(skyCoord)
819 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
823 sourceType = row[self.config.sourceType]
824 if sourceType == galCheckVal:
826 bulge_gs_HLR = row[
'bulge_semimajor']*np.sqrt(row[
'bulge_axis_ratio'])
827 bulge = galsim.Sersic(n=row[
'bulge_n'], half_light_radius=bulge_gs_HLR)
828 bulge = bulge.shear(q=row[
'bulge_axis_ratio'], beta=((90 - row[
'bulge_pa'])*galsim.degrees))
830 disk_gs_HLR = row[
'disk_semimajor']*np.sqrt(row[
'disk_axis_ratio'])
831 disk = galsim.Sersic(n=row[
'disk_n'], half_light_radius=disk_gs_HLR)
832 disk = disk.shear(q=row[
'disk_axis_ratio'], beta=((90 - row[
'disk_pa'])*galsim.degrees))
834 gal = bulge*row[
'bulge_disk_flux_ratio'] + disk
835 gal = gal.withFlux(flux)
838 elif sourceType == starCheckVal:
839 star = galsim.DeltaFunction()
840 star = star.withFlux(flux)
843 raise TypeError(f
"Unknown sourceType {sourceType}")
845 def _generateGSObjectsFromImages(self, exposure, fakeCat):
846 """Process catalog to generate `galsim.GSObject` s.
850 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
851 The exposure into which the fake sources should be added
852 fakeCat : `pandas.core.frame.DataFrame`
853 The catalog of fake sources to be input
857 gsObjects : `generator`
860 band = exposure.getFilterLabel().bandLabel
861 wcs = exposure.getWcs()
862 photoCalib = exposure.getPhotoCalib()
864 self.log.info("Processing %d fake images", len(fakeCat))
866 for (index, row)
in fakeCat.iterrows():
870 xy = wcs.skyToPixel(skyCoord)
873 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
877 imFile = row[band+
"imFilename"]
879 imFile = imFile.decode(
"utf-8")
880 except AttributeError:
882 imFile = imFile.strip()
883 im = galsim.fits.read(imFile, read_header=
True)
885 if self.config.fits_alignment ==
"wcs":
892 if _isWCSGalsimDefault(im.wcs, im.header):
894 f
"Cannot find WCS in input FITS file {imFile}"
896 elif self.config.fits_alignment ==
"pixel":
899 linWcs = wcs.linearizePixelToSky(skyCoord, geom.arcseconds)
900 mat = linWcs.getMatrix()
901 im.wcs = galsim.JacobianWCS(
902 mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1]
906 f
"Unknown fits_alignment type {self.config.fits_alignment}"
909 obj = galsim.InterpolatedImage(im, calculate_stepk=
False)
910 obj = obj.withFlux(flux)
914 """Process images from files into the format needed for insertion.
918 fakeCat : `pandas.core.frame.DataFrame`
919 The catalog of fake sources to be input
920 wcs : `lsst.afw.geom.skyWcs.skyWcs.SkyWc`
921 WCS to use to add fake sources
922 psf : `lsst.meas.algorithms.coaddPsf.coaddPsf.CoaddPsf` or
923 `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
924 The PSF information to use to make the PSF images
925 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
926 Photometric calibration to be used to calibrate the fake sources
928 The filter band that the observation was taken
in.
930 The pixel scale of the image the sources are to be added to.
935 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF`
and
937 For sources labelled
as galaxy.
939 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF`
and
941 For sources labelled
as star.
945 The input fakes catalog needs to contain the absolute path to the image
in the
946 band that
is being used to add images to. It also needs to have the R.A.
and
947 declination of the fake source
in radians
and the sourceType of the object.
952 self.log.info("Processing %d fake images", len(fakeCat))
954 for (imFile, sourceType, mag, x, y)
in zip(fakeCat[band +
"imFilename"].array,
955 fakeCat[
"sourceType"].array,
956 fakeCat[
'mag'].array,
957 fakeCat[
"x"].array, fakeCat[
"y"].array):
959 im = afwImage.ImageF.readFits(imFile)
966 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
967 psfKernel = psf.computeKernelImage(xy).getArray()
968 psfKernel /= correctedFlux
970 except InvalidParameterError:
971 self.log.info(
"%s at %0.4f, %0.4f outside of image", sourceType, x, y)
974 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
975 galsimIm = galsim.InterpolatedImage(galsim.Image(im.array), scale=pixelScale)
976 convIm = galsim.Convolve([galsimIm, psfIm])
979 outIm = convIm.drawImage(scale=pixelScale, method=
"real_space").array
980 except (galsim.errors.GalSimFFTSizeError, MemoryError):
983 imSum = np.sum(outIm)
987 flux = photoCalib.magnitudeToInstFlux(mag, xy)
991 imWithFlux = flux*divIm
993 if sourceType == b
"galaxy":
994 galImages.append((afwImage.ImageF(imWithFlux), xy))
995 if sourceType == b
"star":
996 starImages.append((afwImage.ImageF(imWithFlux), xy))
998 return galImages, starImages
1002 """Add pixel coordinates to the catalog of fakes.
1006 fakeCat : `pandas.core.frame.DataFrame`
1007 The catalog of fake sources to be input
1008 image : `lsst.afw.image.exposure.exposure.ExposureF`
1009 The image into which the fake sources should be added
1013 fakeCat : `pandas.core.frame.DataFrame`
1015 wcs = image.getWcs()
1016 ras = fakeCat['ra'].values
1017 decs = fakeCat[
'dec'].values
1018 xs, ys = wcs.skyToPixelArray(ras, decs)
1025 """Trim the fake cat to about the size of the input image.
1027 `fakeCat` must be processed with addPixCoords before using this method.
1031 fakeCat : `pandas.core.frame.DataFrame`
1032 The catalog of fake sources to be input
1033 image : `lsst.afw.image.exposure.exposure.ExposureF`
1034 The image into which the fake sources should be added
1038 fakeCat : `pandas.core.frame.DataFrame`
1039 The original fakeCat trimmed to the area of the image
1042 bbox = Box2D(image.getBBox()).dilatedBy(self.config.trimBuffer)
1043 xs = fakeCat["x"].values
1044 ys = fakeCat[
"y"].values
1046 isContained = xs >= bbox.minX
1047 isContained &= xs <= bbox.maxX
1048 isContained &= ys >= bbox.minY
1049 isContained &= ys <= bbox.maxY
1051 return fakeCat[isContained]
1054 """Make images of fake galaxies using GalSim.
1059 pixelScale : `float`
1060 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
1061 The PSF information to use to make the PSF images
1062 fakeCat : `pandas.core.frame.DataFrame`
1063 The catalog of fake sources to be input
1064 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
1065 Photometric calibration to be used to calibrate the fake sources
1069 galImages : `generator`
1070 A generator of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
1076 Fake galaxies are made by combining two sersic profiles, one
for the bulge
and one
for the disk. Each
1077 component has an individual sersic index (n), a, b
and position angle (PA). The combined profile
is
1078 then convolved
with the PSF at the specified x, y position on the image.
1080 The names of the columns
in the ``fakeCat`` are configurable
and are the column names
from the
1081 University of Washington simulations database
as default. For more information see the doc strings
1082 attached to the config options.
1084 See mkFakeStars doc string
for an explanation of calibration to instrumental flux.
1087 self.log.info("Making %d fake galaxy images", len(fakeCat))
1089 for (index, row)
in fakeCat.iterrows():
1095 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1096 psfKernel = psf.computeKernelImage(xy).getArray()
1097 psfKernel /= correctedFlux
1099 except InvalidParameterError:
1100 self.log.info(
"Galaxy at %0.4f, %0.4f outside of image", row[
"x"], row[
"y"])
1104 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
1109 bulge_gs_HLR = row[
'bulge_semimajor']*np.sqrt(row[
'bulge_axis_ratio'])
1110 bulge = galsim.Sersic(n=row[
'bulge_n'], half_light_radius=bulge_gs_HLR)
1111 bulge = bulge.shear(q=row[
'bulge_axis_ratio'], beta=((90 - row[
'bulge_pa'])*galsim.degrees))
1113 disk_gs_HLR = row[
'disk_semimajor']*np.sqrt(row[
'disk_axis_ratio'])
1114 disk = galsim.Sersic(n=row[
'disk_n'], half_light_radius=disk_gs_HLR)
1115 disk = disk.shear(q=row[
'disk_axis_ratio'], beta=((90 - row[
'disk_pa'])*galsim.degrees))
1117 gal = bulge*row[
'bulge_disk_flux_ratio'] + disk
1118 gal = gal.withFlux(flux)
1120 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
1121 gal = galsim.Convolve([gal, psfIm])
1123 galIm = gal.drawImage(scale=pixelScale, method=
"real_space").array
1124 except (galsim.errors.GalSimFFTSizeError, MemoryError):
1127 yield (afwImage.ImageF(galIm), xy)
1131 """Make fake stars based off the properties in the fakeCat.
1136 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
1137 The PSF information to use to make the PSF images
1138 fakeCat : `pandas.core.frame.DataFrame`
1139 The catalog of fake sources to be input
1140 image : `lsst.afw.image.exposure.exposure.ExposureF`
1141 The image into which the fake sources should be added
1142 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
1143 Photometric calibration to be used to calibrate the fake sources
1147 starImages : `generator`
1148 A generator of tuples of `lsst.afw.image.ImageF` of fake stars and
1153 To take a given magnitude
and translate to the number of counts
in the image
1154 we use photoCalib.magnitudeToInstFlux, which returns the instrumental flux
for the
1155 given calibration radius used
in the photometric calibration step.
1156 Thus `calibFluxRadius` should be set to this same radius so that we can normalize
1157 the PSF model to the correct instrumental flux within calibFluxRadius.
1160 self.log.info("Making %d fake star images", len(fakeCat))
1162 for (index, row)
in fakeCat.iterrows():
1168 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1169 starIm = psf.computeImage(xy)
1170 starIm /= correctedFlux
1172 except InvalidParameterError:
1173 self.log.info(
"Star at %0.4f, %0.4f outside of image", row[
"x"], row[
"y"])
1177 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
1182 yield ((starIm.convertF(), xy))
1185 """Remove rows from the fakes catalog which have HLR = 0 for either the buldge or disk component,
1186 also remove galaxies that have Sersic index outside the galsim min and max
1187 allowed (0.3 <= n <= 6.2).
1191 fakeCat : `pandas.core.frame.DataFrame`
1192 The catalog of fake sources to be input
1193 starCheckVal : `str`, `bytes`
or `int`
1194 The value that
is set
in the sourceType column to specifiy an object
is a star.
1198 fakeCat : `pandas.core.frame.DataFrame`
1199 The input catalog of fake sources but
with the bad objects removed
1202 rowsToKeep = (((fakeCat['bulge_semimajor'] != 0.0) & (fakeCat[
'disk_semimajor'] != 0.0))
1203 | (fakeCat[self.config.sourceType] == starCheckVal))
1204 numRowsNotUsed = len(fakeCat) - len(np.where(rowsToKeep)[0])
1205 self.log.info(
"Removing %d rows with HLR = 0 for either the bulge or disk", numRowsNotUsed)
1206 fakeCat = fakeCat[rowsToKeep]
1208 minN = galsim.Sersic._minimum_n
1209 maxN = galsim.Sersic._maximum_n
1210 rowsWithGoodSersic = (((fakeCat[
'bulge_n'] >= minN) & (fakeCat[
'bulge_n'] <= maxN)
1211 & (fakeCat[
'disk_n'] >= minN) & (fakeCat[
'disk_n'] <= maxN))
1212 | (fakeCat[self.config.sourceType] == starCheckVal))
1213 numRowsNotUsed = len(fakeCat) - len(np.where(rowsWithGoodSersic)[0])
1214 self.log.info(
"Removing %d rows of galaxies with nBulge or nDisk outside of %0.2f <= n <= %0.2f",
1215 numRowsNotUsed, minN, maxN)
1216 fakeCat = fakeCat[rowsWithGoodSersic]
1218 if self.config.doSubSelectSources:
1219 numRowsNotUsed = len(fakeCat) - len(fakeCat[
'select'])
1220 self.log.info(
"Removing %d rows which were not designated as template sources", numRowsNotUsed)
1221 fakeCat = fakeCat[fakeCat[
'select']]
1226 """Add the fake sources to the given image
1230 image : `lsst.afw.image.exposure.exposure.ExposureF`
1231 The image into which the fake sources should be added
1232 fakeImages : `typing.Iterator` [`tuple` ['lsst.afw.image.ImageF`, `lsst.geom.Point2d`]]
1233 An iterator of tuples that contains (or generates) images of fake sources,
1234 and the locations they are to be inserted at.
1236 The type (star/galaxy) of fake sources input
1240 image : `lsst.afw.image.exposure.exposure.ExposureF`
1244 Uses the x, y information
in the ``fakeCat`` to position an image of the fake interpolated onto the
1245 pixel grid of the image. Sets the ``FAKE`` mask plane
for the pixels added
with the fake source.
1248 imageBBox = image.getBBox()
1249 imageMI = image.maskedImage
1251 for (fakeImage, xy)
in fakeImages:
1252 X0 = xy.getX() - fakeImage.getWidth()/2 + 0.5
1253 Y0 = xy.getY() - fakeImage.getHeight()/2 + 0.5
1254 self.log.debug(
"Adding fake source at %d, %d", xy.getX(), xy.getY())
1255 if sourceType ==
"galaxy":
1256 interpFakeImage = afwMath.offsetImage(fakeImage, X0, Y0,
"lanczos3")
1258 interpFakeImage = fakeImage
1260 interpFakeImBBox = interpFakeImage.getBBox()
1261 interpFakeImBBox.clip(imageBBox)
1263 if interpFakeImBBox.getArea() > 0:
1264 imageMIView = imageMI[interpFakeImBBox]
1265 clippedFakeImage = interpFakeImage[interpFakeImBBox]
1266 clippedFakeImageMI = afwImage.MaskedImageF(clippedFakeImage)
1267 clippedFakeImageMI.mask.set(self.bitmask)
1268 imageMIView += clippedFakeImageMI
1272 def _getMetadataName(self):
1273 """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)