23Insert fakes into deepCoadds
26__all__ = [
"InsertFakesConfig",
"InsertFakesTask"]
30from astropy
import units
as u
38from lsst.pipe.base import PipelineTask, PipelineTaskConfig, PipelineTaskConnections
39import lsst.pipe.base.connectionTypes
as cT
41from lsst.geom import SpherePoint, radians, Box2D, Point2D
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 "
395 length_col = pexConfig.Field(
396 doc=
"Source catalog column name for trail length (in pixels).",
398 default=
"trail_length",
401 angle_col = pexConfig.Field(
402 doc=
"Source catalog column name for trail angle (in radians).",
404 default=
"trail_angle",
409 raColName = pexConfig.Field(
410 doc=
"RA column name used in the fake source catalog.",
413 deprecated=
"Use `ra_col` instead."
416 decColName = pexConfig.Field(
417 doc=
"Dec. column name used in the fake source catalog.",
420 deprecated=
"Use `dec_col` instead."
423 diskHLR = pexConfig.Field(
424 doc=
"Column name for the disk half light radius used in the fake source catalog.",
426 default=
"DiskHalfLightRadius",
428 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
429 " to specify disk half-light ellipse."
433 aDisk = pexConfig.Field(
434 doc=
"The column name for the semi major axis length of the disk component used in the fake source"
439 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
440 " to specify disk half-light ellipse."
444 bDisk = pexConfig.Field(
445 doc=
"The column name for the semi minor axis length of the disk component.",
449 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
450 " to specify disk half-light ellipse."
454 paDisk = pexConfig.Field(
455 doc=
"The column name for the PA of the disk component used in the fake source catalog.",
459 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
460 " to specify disk half-light ellipse."
464 nDisk = pexConfig.Field(
465 doc=
"The column name for the sersic index of the disk component used in the fake source catalog.",
468 deprecated=
"Use `disk_n_col` instead."
471 bulgeHLR = pexConfig.Field(
472 doc=
"Column name for the bulge half light radius used in the fake source catalog.",
474 default=
"BulgeHalfLightRadius",
476 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
477 "`bulge_pa_col` to specify disk half-light ellipse."
481 aBulge = pexConfig.Field(
482 doc=
"The column name for the semi major axis length of the bulge component.",
486 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
487 "`bulge_pa_col` to specify disk half-light ellipse."
491 bBulge = pexConfig.Field(
492 doc=
"The column name for the semi minor axis length of the bulge component used in the fake source "
497 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
498 "`bulge_pa_col` to specify disk half-light ellipse."
502 paBulge = pexConfig.Field(
503 doc=
"The column name for the PA of the bulge component used in the fake source catalog.",
507 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
508 "`bulge_pa_col` to specify disk half-light ellipse."
512 nBulge = pexConfig.Field(
513 doc=
"The column name for the sersic index of the bulge component used in the fake source catalog.",
516 deprecated=
"Use `bulge_n_col` instead."
519 magVar = pexConfig.Field(
520 doc=
"The column name for the magnitude calculated taking variability into account. In the format "
521 "``filter name``magVar, e.g. imagVar for the magnitude in the i band.",
524 deprecated=
"Use `mag_col` instead."
527 sourceSelectionColName = pexConfig.Field(
528 doc=
"The name of the column in the input fakes catalogue to be used to determine which sources to"
529 "add, default is none and when this is used all sources are added.",
531 default=
"templateSource",
532 deprecated=
"Use `select_col` instead."
536class InsertFakesTask(PipelineTask):
537 """Insert fake objects into images.
539 Add fake stars and galaxies to the given image, read
in through the dataRef. Galaxy parameters are read
in
540 from the specified file
and then modelled using galsim.
542 `InsertFakesTask` has five functions that make images of the fake sources
and then add them to the
546 Use the WCS information to add the pixel coordinates of each source.
547 `mkFakeGalsimGalaxies`
548 Use Galsim to make fake double sersic galaxies
for each set of galaxy parameters
in the input file.
550 Use the PSF information
from the image to make a fake star using the magnitude information
from the
553 Remove rows of the input fake catalog which have half light radius, of either the bulge
or the disk,
554 that are 0. Also removes rows that have Sersic index outside of galsim
's allowed paramters. If
555 the config option sourceSelectionColName is set then this function limits the catalog of input fakes
556 to only those which are
True in this column.
558 Add the fake sources to the image.
562 _DefaultName = "insertFakes"
563 ConfigClass = InsertFakesConfig
565 def runQuantum(self, butlerQC, inputRefs, outputRefs):
566 inputs = butlerQC.get(inputRefs)
567 inputs[
"wcs"] = inputs[
"image"].getWcs()
568 inputs[
"photoCalib"] = inputs[
"image"].getPhotoCalib()
570 outputs = self.run(**inputs)
571 butlerQC.put(outputs, outputRefs)
573 def run(self, fakeCat, image, wcs, photoCalib):
574 """Add fake sources to an image.
578 fakeCat : `pandas.core.frame.DataFrame`
579 The catalog of fake sources to be input
580 image : `lsst.afw.image.exposure.exposure.ExposureF`
581 The image into which the fake sources should be added
583 WCS to use to add fake sources
584 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
585 Photometric calibration to be used to calibrate the fake sources
589 resultStruct : `lsst.pipe.base.struct.Struct`
590 contains : image : `lsst.afw.image.exposure.exposure.ExposureF`
594 Adds pixel coordinates for each source to the fakeCat
and removes objects
with bulge
or disk half
595 light radius = 0 (
if ``config.doCleanCat =
True``).
597 Adds the ``Fake`` mask plane to the image which
is then set by `addFakeSources` to mark where fake
598 sources have been added. Uses the information
in the ``fakeCat`` to make fake galaxies (using galsim)
599 and fake stars, using the PSF models
from the PSF information
for the image. These are then added to
600 the image
and the image
with fakes included returned.
602 The galsim galaxies are made using a double sersic profile, one
for the bulge
and one
for the disk,
603 this
is then convolved
with the PSF at that point.
607 origWcs = image.getWcs()
608 origPhotoCalib = image.getPhotoCalib()
610 image.setPhotoCalib(photoCalib)
612 band = image.getFilter().bandLabel
613 fakeCat = self._standardizeColumns(fakeCat, band)
615 fakeCat = self.addPixCoords(fakeCat, image)
616 fakeCat = self.trimFakeCat(fakeCat, image)
619 if not self.config.insertImages:
620 if isinstance(fakeCat[self.config.sourceType].iloc[0], str):
621 galCheckVal =
"galaxy"
622 starCheckVal =
"star"
623 trailCheckVal =
"trail"
624 elif isinstance(fakeCat[self.config.sourceType].iloc[0], bytes):
625 galCheckVal = b
"galaxy"
626 starCheckVal = b
"star"
627 trailCheckVal = b
"trail"
628 elif isinstance(fakeCat[self.config.sourceType].iloc[0], (int, float)):
634 "sourceType column does not have required type, should be str, bytes or int"
636 if self.config.doCleanCat:
637 fakeCat = self.cleanCat(fakeCat, starCheckVal)
639 generator = self._generateGSObjectsFromCatalog(image, fakeCat, galCheckVal, starCheckVal,
642 generator = self._generateGSObjectsFromImages(image, fakeCat)
643 _add_fake_sources(image, generator, calibFluxRadius=self.config.calibFluxRadius, logger=self.log)
644 elif len(fakeCat) == 0
and self.config.doProcessAllDataIds:
645 self.log.warning(
"No fakes found for this dataRef; processing anyway.")
646 image.mask.addMaskPlane(
"FAKE")
648 raise RuntimeError(
"No fakes found for this dataRef.")
651 image.setWcs(origWcs)
652 image.setPhotoCalib(origPhotoCalib)
654 resultStruct = pipeBase.Struct(imageWithFakes=image)
658 def _standardizeColumns(self, fakeCat, band):
659 """Use config variables to 'standardize' the expected columns and column
660 names in the input catalog.
664 fakeCat : `pandas.core.frame.DataFrame`
665 The catalog of fake sources to be input
667 Label
for the current band being processed.
671 outCat : `pandas.core.frame.DataFrame`
672 The standardized catalog of fake sources
677 def add_to_replace_dict(new_name, depr_name, std_name):
678 if new_name
in fakeCat.columns:
679 replace_dict[new_name] = std_name
680 elif depr_name
in fakeCat.columns:
681 replace_dict[depr_name] = std_name
683 raise ValueError(f
"Could not determine column for {std_name}.")
687 for new_name, depr_name, std_name
in [
688 (cfg.ra_col, cfg.raColName,
'ra'),
689 (cfg.dec_col, cfg.decColName,
'dec'),
690 (cfg.mag_col%band, cfg.magVar%band,
'mag')
692 add_to_replace_dict(new_name, depr_name, std_name)
694 if not cfg.insertImages
and not cfg.insertOnlyStars:
695 for new_name, depr_name, std_name
in [
696 (cfg.bulge_n_col, cfg.nBulge,
'bulge_n'),
697 (cfg.bulge_pa_col, cfg.paBulge,
'bulge_pa'),
698 (cfg.disk_n_col, cfg.nDisk,
'disk_n'),
699 (cfg.disk_pa_col, cfg.paDisk,
'disk_pa'),
701 add_to_replace_dict(new_name, depr_name, std_name)
703 if cfg.doSubSelectSources:
706 cfg.sourceSelectionColName,
709 fakeCat = fakeCat.rename(columns=replace_dict, copy=
False)
714 if not cfg.insertImages
and not cfg.insertOnlyStars:
716 cfg.bulge_semimajor_col
in fakeCat.columns
717 and cfg.bulge_axis_ratio_col
in fakeCat.columns
719 fakeCat = fakeCat.rename(
721 cfg.bulge_semimajor_col:
'bulge_semimajor',
722 cfg.bulge_axis_ratio_col:
'bulge_axis_ratio',
723 cfg.disk_semimajor_col:
'disk_semimajor',
724 cfg.disk_axis_ratio_col:
'disk_axis_ratio',
729 cfg.bulgeHLR
in fakeCat.columns
730 and cfg.aBulge
in fakeCat.columns
731 and cfg.bBulge
in fakeCat.columns
733 fakeCat[
'bulge_axis_ratio'] = (
734 fakeCat[cfg.bBulge]/fakeCat[cfg.aBulge]
736 fakeCat[
'bulge_semimajor'] = (
737 fakeCat[cfg.bulgeHLR]/np.sqrt(fakeCat[
'bulge_axis_ratio'])
739 fakeCat[
'disk_axis_ratio'] = (
740 fakeCat[cfg.bDisk]/fakeCat[cfg.aDisk]
742 fakeCat[
'disk_semimajor'] = (
743 fakeCat[cfg.diskHLR]/np.sqrt(fakeCat[
'disk_axis_ratio'])
747 "Could not determine columns for half-light radius and "
752 if cfg.bulge_disk_flux_ratio_col
in fakeCat.columns:
753 fakeCat = fakeCat.rename(
755 cfg.bulge_disk_flux_ratio_col:
'bulge_disk_flux_ratio'
760 fakeCat[
'bulge_disk_flux_ratio'] = 1.0
764 def _generateGSObjectsFromCatalog(self, exposure, fakeCat, galCheckVal, starCheckVal, trailCheckVal):
765 """Process catalog to generate `galsim.GSObject` s.
769 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
770 The exposure into which the fake sources should be added
771 fakeCat : `pandas.core.frame.DataFrame`
772 The catalog of fake sources to be input
773 galCheckVal : `str`, `bytes` or `int`
774 The value that
is set
in the sourceType column to specify an object
is a galaxy.
775 starCheckVal : `str`, `bytes`
or `int`
776 The value that
is set
in the sourceType column to specify an object
is a star.
777 trailCheckVal : `str`, `bytes`
or `int`
778 The value that
is set
in the sourceType column to specify an object
is a star
782 gsObjects : `generator`
785 wcs = exposure.getWcs()
786 photoCalib = exposure.getPhotoCalib()
788 self.log.info("Making %d objects for insertion", len(fakeCat))
790 for (index, row)
in fakeCat.iterrows():
794 xy = wcs.skyToPixel(skyCoord)
797 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
801 sourceType = row[self.config.sourceType]
802 if sourceType == galCheckVal:
804 bulge_gs_HLR = row[
'bulge_semimajor']*np.sqrt(row[
'bulge_axis_ratio'])
805 bulge = galsim.Sersic(n=row[
'bulge_n'], half_light_radius=bulge_gs_HLR)
806 bulge = bulge.shear(q=row[
'bulge_axis_ratio'], beta=((90 - row[
'bulge_pa'])*galsim.degrees))
808 disk_gs_HLR = row[
'disk_semimajor']*np.sqrt(row[
'disk_axis_ratio'])
809 disk = galsim.Sersic(n=row[
'disk_n'], half_light_radius=disk_gs_HLR)
810 disk = disk.shear(q=row[
'disk_axis_ratio'], beta=((90 - row[
'disk_pa'])*galsim.degrees))
812 gal = bulge*row[
'bulge_disk_flux_ratio'] + disk
813 gal = gal.withFlux(flux)
816 elif sourceType == starCheckVal:
817 star = galsim.DeltaFunction()
818 star = star.withFlux(flux)
820 elif sourceType == trailCheckVal:
821 length = row[
'trail_length']
822 angle = row[
'trail_angle']
826 theta = galsim.Angle(angle*galsim.radians)
827 trail = galsim.Box(length, thickness)
828 trail = trail.rotate(theta)
829 trail = trail.withFlux(flux*length)
834 mat = wcs.linearizePixelToSky(skyCoord, geom.arcseconds).getMatrix()
835 trail = trail.transform(mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1])
837 yield skyCoord, trail
839 raise TypeError(f
"Unknown sourceType {sourceType}")
841 def _generateGSObjectsFromImages(self, exposure, fakeCat):
842 """Process catalog to generate `galsim.GSObject` s.
846 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
847 The exposure into which the fake sources should be added
848 fakeCat : `pandas.core.frame.DataFrame`
849 The catalog of fake sources to be input
853 gsObjects : `generator`
856 band = exposure.getFilter().bandLabel
857 wcs = exposure.getWcs()
858 photoCalib = exposure.getPhotoCalib()
860 self.log.info("Processing %d fake images", len(fakeCat))
862 for (index, row)
in fakeCat.iterrows():
866 xy = wcs.skyToPixel(skyCoord)
869 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
873 imFile = row[band+
"imFilename"]
875 imFile = imFile.decode(
"utf-8")
876 except AttributeError:
878 imFile = imFile.strip()
879 im = galsim.fits.read(imFile, read_header=
True)
881 if self.config.fits_alignment ==
"wcs":
888 if _isWCSGalsimDefault(im.wcs, im.header):
890 f
"Cannot find WCS in input FITS file {imFile}"
892 elif self.config.fits_alignment ==
"pixel":
895 linWcs = wcs.linearizePixelToSky(skyCoord, geom.arcseconds)
896 mat = linWcs.getMatrix()
897 im.wcs = galsim.JacobianWCS(
898 mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1]
902 f
"Unknown fits_alignment type {self.config.fits_alignment}"
905 obj = galsim.InterpolatedImage(im, calculate_stepk=
False)
906 obj = obj.withFlux(flux)
910 """Process images from files into the format needed for insertion.
914 fakeCat : `pandas.core.frame.DataFrame`
915 The catalog of fake sources to be input
916 wcs : `lsst.afw.geom.skyWcs.skyWcs.SkyWc`
917 WCS to use to add fake sources
918 psf : `lsst.meas.algorithms.coaddPsf.coaddPsf.CoaddPsf` or
919 `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
920 The PSF information to use to make the PSF images
921 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
922 Photometric calibration to be used to calibrate the fake sources
924 The filter band that the observation was taken
in.
926 The pixel scale of the image the sources are to be added to.
931 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF`
and
933 For sources labelled
as galaxy.
935 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF`
and
937 For sources labelled
as star.
941 The input fakes catalog needs to contain the absolute path to the image
in the
942 band that
is being used to add images to. It also needs to have the R.A.
and
943 declination of the fake source
in radians
and the sourceType of the object.
948 self.log.info("Processing %d fake images", len(fakeCat))
950 for (imFile, sourceType, mag, x, y)
in zip(fakeCat[band +
"imFilename"].array,
951 fakeCat[
"sourceType"].array,
952 fakeCat[
'mag'].array,
953 fakeCat[
"x"].array, fakeCat[
"y"].array):
955 im = afwImage.ImageF.readFits(imFile)
962 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
963 psfKernel = psf.computeKernelImage(xy).getArray()
964 psfKernel /= correctedFlux
966 except InvalidParameterError:
967 self.log.info(
"%s at %0.4f, %0.4f outside of image", sourceType, x, y)
970 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
971 galsimIm = galsim.InterpolatedImage(galsim.Image(im.array), scale=pixelScale)
972 convIm = galsim.Convolve([galsimIm, psfIm])
975 outIm = convIm.drawImage(scale=pixelScale, method=
"real_space").array
976 except (galsim.errors.GalSimFFTSizeError, MemoryError):
979 imSum = np.sum(outIm)
983 flux = photoCalib.magnitudeToInstFlux(mag, xy)
987 imWithFlux = flux*divIm
989 if sourceType == b
"galaxy":
990 galImages.append((afwImage.ImageF(imWithFlux), xy))
991 if sourceType == b
"star":
992 starImages.append((afwImage.ImageF(imWithFlux), xy))
994 return galImages, starImages
998 """Add pixel coordinates to the catalog of fakes.
1002 fakeCat : `pandas.core.frame.DataFrame`
1003 The catalog of fake sources to be input
1004 image : `lsst.afw.image.exposure.exposure.ExposureF`
1005 The image into which the fake sources should be added
1009 fakeCat : `pandas.core.frame.DataFrame`
1011 wcs = image.getWcs()
1012 ras = fakeCat['ra'].values
1013 decs = fakeCat[
'dec'].values
1014 xs, ys = wcs.skyToPixelArray(ras, decs)
1021 """Trim the fake cat to the size of the input image plus trimBuffer padding.
1023 `fakeCat` must be processed with addPixCoords before using this method.
1027 fakeCat : `pandas.core.frame.DataFrame`
1028 The catalog of fake sources to be input
1029 image : `lsst.afw.image.exposure.exposure.ExposureF`
1030 The image into which the fake sources should be added
1034 fakeCat : `pandas.core.frame.DataFrame`
1035 The original fakeCat trimmed to the area of the image
1037 wideBbox = Box2D(image.getBBox()).dilatedBy(self.config.trimBuffer)
1041 ras = fakeCat[self.config.ra_col].values * u.rad
1042 decs = fakeCat[self.config.dec_col].values * u.rad
1044 isContainedRaDec = image.containsSkyCoords(ras, decs, padding=self.config.trimBuffer)
1047 xs = fakeCat[
"x"].values
1048 ys = fakeCat[
"y"].values
1050 isContainedXy = xs >= wideBbox.minX
1051 isContainedXy &= xs <= wideBbox.maxX
1052 isContainedXy &= ys >= wideBbox.minY
1053 isContainedXy &= ys <= wideBbox.maxY
1055 return fakeCat[isContainedRaDec & isContainedXy]
1058 """Make images of fake galaxies using GalSim.
1063 pixelScale : `float`
1064 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
1065 The PSF information to use to make the PSF images
1066 fakeCat : `pandas.core.frame.DataFrame`
1067 The catalog of fake sources to be input
1068 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
1069 Photometric calibration to be used to calibrate the fake sources
1073 galImages : `generator`
1074 A generator of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
1080 Fake galaxies are made by combining two sersic profiles, one
for the bulge
and one
for the disk. Each
1081 component has an individual sersic index (n), a, b
and position angle (PA). The combined profile
is
1082 then convolved
with the PSF at the specified x, y position on the image.
1084 The names of the columns
in the ``fakeCat`` are configurable
and are the column names
from the
1085 University of Washington simulations database
as default. For more information see the doc strings
1086 attached to the config options.
1088 See mkFakeStars doc string
for an explanation of calibration to instrumental flux.
1091 self.log.info("Making %d fake galaxy images", len(fakeCat))
1093 for (index, row)
in fakeCat.iterrows():
1099 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1100 psfKernel = psf.computeKernelImage(xy).getArray()
1101 psfKernel /= correctedFlux
1103 except InvalidParameterError:
1104 self.log.info(
"Galaxy at %0.4f, %0.4f outside of image", row[
"x"], row[
"y"])
1108 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
1113 bulge_gs_HLR = row[
'bulge_semimajor']*np.sqrt(row[
'bulge_axis_ratio'])
1114 bulge = galsim.Sersic(n=row[
'bulge_n'], half_light_radius=bulge_gs_HLR)
1115 bulge = bulge.shear(q=row[
'bulge_axis_ratio'], beta=((90 - row[
'bulge_pa'])*galsim.degrees))
1117 disk_gs_HLR = row[
'disk_semimajor']*np.sqrt(row[
'disk_axis_ratio'])
1118 disk = galsim.Sersic(n=row[
'disk_n'], half_light_radius=disk_gs_HLR)
1119 disk = disk.shear(q=row[
'disk_axis_ratio'], beta=((90 - row[
'disk_pa'])*galsim.degrees))
1121 gal = bulge*row[
'bulge_disk_flux_ratio'] + disk
1122 gal = gal.withFlux(flux)
1124 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
1125 gal = galsim.Convolve([gal, psfIm])
1127 galIm = gal.drawImage(scale=pixelScale, method=
"real_space").array
1128 except (galsim.errors.GalSimFFTSizeError, MemoryError):
1131 yield (afwImage.ImageF(galIm), xy)
1135 """Make fake stars based off the properties in the fakeCat.
1140 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
1141 The PSF information to use to make the PSF images
1142 fakeCat : `pandas.core.frame.DataFrame`
1143 The catalog of fake sources to be input
1144 image : `lsst.afw.image.exposure.exposure.ExposureF`
1145 The image into which the fake sources should be added
1146 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
1147 Photometric calibration to be used to calibrate the fake sources
1151 starImages : `generator`
1152 A generator of tuples of `lsst.afw.image.ImageF` of fake stars and
1157 To take a given magnitude
and translate to the number of counts
in the image
1158 we use photoCalib.magnitudeToInstFlux, which returns the instrumental flux
for the
1159 given calibration radius used
in the photometric calibration step.
1160 Thus `calibFluxRadius` should be set to this same radius so that we can normalize
1161 the PSF model to the correct instrumental flux within calibFluxRadius.
1164 self.log.info("Making %d fake star images", len(fakeCat))
1166 for (index, row)
in fakeCat.iterrows():
1172 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1173 starIm = psf.computeImage(xy)
1174 starIm /= correctedFlux
1176 except InvalidParameterError:
1177 self.log.info(
"Star at %0.4f, %0.4f outside of image", row[
"x"], row[
"y"])
1181 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
1186 yield ((starIm.convertF(), xy))
1189 """Remove rows from the fakes catalog which have HLR = 0 for either the buldge or disk component,
1190 also remove galaxies that have Sersic index outside the galsim min and max
1191 allowed (0.3 <= n <= 6.2).
1195 fakeCat : `pandas.core.frame.DataFrame`
1196 The catalog of fake sources to be input
1197 starCheckVal : `str`, `bytes`
or `int`
1198 The value that
is set
in the sourceType column to specifiy an object
is a star.
1202 fakeCat : `pandas.core.frame.DataFrame`
1203 The input catalog of fake sources but
with the bad objects removed
1206 rowsToKeep = (((fakeCat['bulge_semimajor'] != 0.0) & (fakeCat[
'disk_semimajor'] != 0.0))
1207 | (fakeCat[self.config.sourceType] == starCheckVal))
1208 numRowsNotUsed = len(fakeCat) - len(np.where(rowsToKeep)[0])
1209 self.log.info(
"Removing %d rows with HLR = 0 for either the bulge or disk", numRowsNotUsed)
1210 fakeCat = fakeCat[rowsToKeep]
1212 minN = galsim.Sersic._minimum_n
1213 maxN = galsim.Sersic._maximum_n
1214 rowsWithGoodSersic = (((fakeCat[
'bulge_n'] >= minN) & (fakeCat[
'bulge_n'] <= maxN)
1215 & (fakeCat[
'disk_n'] >= minN) & (fakeCat[
'disk_n'] <= maxN))
1216 | (fakeCat[self.config.sourceType] == starCheckVal))
1217 numRowsNotUsed = len(fakeCat) - len(np.where(rowsWithGoodSersic)[0])
1218 self.log.info(
"Removing %d rows of galaxies with nBulge or nDisk outside of %0.2f <= n <= %0.2f",
1219 numRowsNotUsed, minN, maxN)
1220 fakeCat = fakeCat[rowsWithGoodSersic]
1222 if self.config.doSubSelectSources:
1223 numRowsNotUsed = len(fakeCat) - len(fakeCat[
'select'])
1224 self.log.info(
"Removing %d rows which were not designated as template sources", numRowsNotUsed)
1225 fakeCat = fakeCat[fakeCat[
'select']]
1230 """Add the fake sources to the given image
1234 image : `lsst.afw.image.exposure.exposure.ExposureF`
1235 The image into which the fake sources should be added
1236 fakeImages : `typing.Iterator` [`tuple` ['lsst.afw.image.ImageF`, `lsst.geom.Point2d`]]
1237 An iterator of tuples that contains (or generates) images of fake sources,
1238 and the locations they are to be inserted at.
1240 The type (star/galaxy) of fake sources input
1244 image : `lsst.afw.image.exposure.exposure.ExposureF`
1248 Uses the x, y information
in the ``fakeCat`` to position an image of the fake interpolated onto the
1249 pixel grid of the image. Sets the ``FAKE`` mask plane
for the pixels added
with the fake source.
1252 imageBBox = image.getBBox()
1253 imageMI = image.maskedImage
1255 for (fakeImage, xy)
in fakeImages:
1256 X0 = xy.getX() - fakeImage.getWidth()/2 + 0.5
1257 Y0 = xy.getY() - fakeImage.getHeight()/2 + 0.5
1258 self.log.debug(
"Adding fake source at %d, %d", xy.getX(), xy.getY())
1259 if sourceType ==
"galaxy":
1260 interpFakeImage = afwMath.offsetImage(fakeImage, X0, Y0,
"lanczos3")
1262 interpFakeImage = fakeImage
1264 interpFakeImBBox = interpFakeImage.getBBox()
1265 interpFakeImBBox.clip(imageBBox)
1267 if interpFakeImBBox.getArea() > 0:
1268 imageMIView = imageMI[interpFakeImBBox]
1269 clippedFakeImage = interpFakeImage[interpFakeImBBox]
1270 clippedFakeImageMI = afwImage.MaskedImageF(clippedFakeImage)
1271 clippedFakeImageMI.mask.set(self.bitmask)
1272 imageMIView += clippedFakeImageMI
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)