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
43from deprecated.sphinx
import deprecated
47 """Add fake sources to the given exposure
51 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
52 The exposure into which the fake sources should be added
53 objects : `typing.Iterator` [`tuple` ['lsst.geom.SpherePoint`, `galsim.GSObject`]]
54 An iterator of tuples that contains (or generates) locations and object
55 surface brightness profiles to inject.
56 calibFluxRadius : `float`, optional
57 Aperture radius (in pixels) used to define the calibration for this
58 exposure+catalog. This is used to produce the correct instrumental fluxes
59 within the radius. The value should match that of the field defined in
60 slot_CalibFlux_instFlux.
61 logger : `logging.Logger`, optional
64 exposure.mask.addMaskPlane(
"FAKE")
65 bitmask = exposure.mask.getPlaneBitMask(
"FAKE")
67 logger.info(
"Adding mask plane with bitmask %s", bitmask)
69 wcs = exposure.getWcs()
70 psf = exposure.getPsf()
72 bbox = exposure.getBBox()
73 fullBounds = galsim.BoundsI(bbox.minX, bbox.maxX, bbox.minY, bbox.maxY)
74 gsImg = galsim.Image(exposure.image.array, bounds=fullBounds)
76 pixScale = wcs.getPixelScale(bbox.getCenter()).asArcseconds()
78 for spt, gsObj
in objects:
79 pt = wcs.skyToPixel(spt)
80 posd = galsim.PositionD(pt.x, pt.y)
81 posi = galsim.PositionI(pt.x//1, pt.y//1)
83 logger.debug(
"Adding fake source at %s", pt)
85 mat = wcs.linearizePixelToSky(spt, geom.arcseconds).getMatrix()
86 gsWCS = galsim.JacobianWCS(mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1])
91 gsPixScale = np.sqrt(gsWCS.pixelArea())
92 if gsPixScale < pixScale/2
or gsPixScale > pixScale*2:
96 psfArr = psf.computeKernelImage(pt).array
97 apCorr = psf.computeApertureFlux(calibFluxRadius, pt)
98 except InvalidParameterError:
100 contained_pt = Point2D(
101 np.clip(pt.x, bbox.minX, bbox.maxX),
102 np.clip(pt.y, bbox.minY, bbox.maxY)
104 if pt == contained_pt:
106 logger.info(
"Cannot compute Psf for object at %s; skipping", pt)
110 psfArr = psf.computeKernelImage(contained_pt).array
111 apCorr = psf.computeApertureFlux(calibFluxRadius, contained_pt)
112 except InvalidParameterError:
114 logger.info(
"Cannot compute Psf for object at %s; skipping", pt)
118 gsPSF = galsim.InterpolatedImage(galsim.Image(psfArr), wcs=gsWCS)
120 conv = galsim.Convolve(gsObj, gsPSF)
121 stampSize = conv.getGoodImageSize(gsWCS.minLinearScale())
122 subBounds = galsim.BoundsI(posi).withBorder(stampSize//2)
123 subBounds &= fullBounds
125 if subBounds.area() > 0:
126 subImg = gsImg[subBounds]
127 offset = posd - subBounds.true_center
144 exposure[subBox].mask.array |= bitmask
148 """Decide if wcs = galsim.PixelScale(1.0) is explicitly present in header,
149 or if it's just the galsim default.
154 Potentially default WCS.
155 hdr : galsim.fits.FitsHeader
156 Header as read in by galsim.
161 True if default, False if explicitly set in header.
163 if wcs != galsim.PixelScale(1.0):
165 if hdr.get(
'GS_WCS')
is not None:
167 if hdr.get(
'CTYPE1',
'LINEAR') ==
'LINEAR':
168 return not any(k
in hdr
for k
in [
'CD1_1',
'CDELT1'])
169 for wcs_type
in galsim.fitswcs.fits_wcs_types:
172 wcs_type._readHeader(hdr)
177 return not any(k
in hdr
for k
in [
'CD1_1',
'CDELT1'])
181 defaultTemplates={
"coaddName":
"deep",
182 "fakesType":
"fakes_"},
183 dimensions=(
"tract",
"patch",
"band",
"skymap")):
186 doc=
"Image into which fakes are to be added.",
187 name=
"{coaddName}Coadd",
188 storageClass=
"ExposureF",
189 dimensions=(
"tract",
"patch",
"band",
"skymap")
193 doc=
"Catalog of fake sources to draw inputs from.",
194 name=
"{fakesType}fakeSourceCat",
195 storageClass=
"DataFrame",
196 dimensions=(
"tract",
"skymap")
199 imageWithFakes = cT.Output(
200 doc=
"Image with fake sources added.",
201 name=
"{fakesType}{coaddName}Coadd",
202 storageClass=
"ExposureF",
203 dimensions=(
"tract",
"patch",
"band",
"skymap")
208 reason=
"This task will be removed in v28.0 as it is replaced by `source_injection` tasks.",
210 category=FutureWarning,
212class InsertFakesConfig(PipelineTaskConfig,
213 pipelineConnections=InsertFakesConnections):
214 """Config for inserting fake sources
219 doCleanCat = pexConfig.Field(
220 doc=
"If true removes bad sources from the catalog.",
225 fakeType = pexConfig.Field(
226 doc=
"What type of fake catalog to use, snapshot (includes variability in the magnitudes calculated "
227 "from the MJD of the image), static (no variability) or filename for a user defined fits"
233 calibFluxRadius = pexConfig.Field(
234 doc=
"Aperture radius (in pixels) that was used to define the calibration for this image+catalog. "
235 "This will be used to produce the correct instrumental fluxes within the radius. "
236 "This value should match that of the field defined in slot_CalibFlux_instFlux.",
241 coaddName = pexConfig.Field(
242 doc=
"The name of the type of coadd used",
247 doSubSelectSources = pexConfig.Field(
248 doc=
"Set to True if you wish to sub select sources to be input based on the value in the column"
249 "set in the sourceSelectionColName config option.",
254 insertImages = pexConfig.Field(
255 doc=
"Insert images directly? True or False.",
260 insertOnlyStars = pexConfig.Field(
261 doc=
"Insert only stars? True or False.",
266 doProcessAllDataIds = pexConfig.Field(
267 doc=
"If True, all input data IDs will be processed, even those containing no fake sources.",
272 trimBuffer = pexConfig.Field(
273 doc=
"Size of the pixel buffer surrounding the image. Only those fake sources with a centroid"
274 "falling within the image+buffer region will be considered for fake source injection.",
279 sourceType = pexConfig.Field(
280 doc=
"The column name for the source type used in the fake source catalog.",
282 default=
"sourceType",
285 fits_alignment = pexConfig.ChoiceField(
286 doc=
"How should injections from FITS files be aligned?",
290 "Input image will be transformed such that the local WCS in "
291 "the FITS header matches the local WCS in the target image. "
292 "I.e., North, East, and angular distances in the input image "
293 "will match North, East, and angular distances in the target "
297 "Input image will _not_ be transformed. Up, right, and pixel "
298 "distances in the input image will match up, right and pixel "
299 "distances in the target image."
307 ra_col = pexConfig.Field(
308 doc=
"Source catalog column name for RA (in radians).",
313 dec_col = pexConfig.Field(
314 doc=
"Source catalog column name for dec (in radians).",
319 bulge_semimajor_col = pexConfig.Field(
320 doc=
"Source catalog column name for the semimajor axis (in arcseconds) "
321 "of the bulge half-light ellipse.",
323 default=
"bulge_semimajor",
326 bulge_axis_ratio_col = pexConfig.Field(
327 doc=
"Source catalog column name for the axis ratio of the bulge "
328 "half-light ellipse.",
330 default=
"bulge_axis_ratio",
333 bulge_pa_col = pexConfig.Field(
334 doc=
"Source catalog column name for the position angle (measured from "
335 "North through East in degrees) of the semimajor axis of the bulge "
336 "half-light ellipse.",
341 bulge_n_col = pexConfig.Field(
342 doc=
"Source catalog column name for the Sersic index of the bulge.",
347 disk_semimajor_col = pexConfig.Field(
348 doc=
"Source catalog column name for the semimajor axis (in arcseconds) "
349 "of the disk half-light ellipse.",
351 default=
"disk_semimajor",
354 disk_axis_ratio_col = pexConfig.Field(
355 doc=
"Source catalog column name for the axis ratio of the disk "
356 "half-light ellipse.",
358 default=
"disk_axis_ratio",
361 disk_pa_col = pexConfig.Field(
362 doc=
"Source catalog column name for the position angle (measured from "
363 "North through East in degrees) of the semimajor axis of the disk "
364 "half-light ellipse.",
369 disk_n_col = pexConfig.Field(
370 doc=
"Source catalog column name for the Sersic index of the disk.",
375 bulge_disk_flux_ratio_col = pexConfig.Field(
376 doc=
"Source catalog column name for the bulge/disk flux ratio.",
378 default=
"bulge_disk_flux_ratio",
381 mag_col = pexConfig.Field(
382 doc=
"Source catalog column name template for magnitudes, in the format "
383 "``filter name``_mag_col. E.g., if this config variable is set to "
384 "``%s_mag``, then the i-band magnitude will be searched for in the "
385 "``i_mag`` column of the source catalog.",
390 select_col = pexConfig.Field(
391 doc=
"Source catalog column name to be used to select which sources to "
397 length_col = pexConfig.Field(
398 doc=
"Source catalog column name for trail length (in pixels).",
400 default=
"trail_length",
403 angle_col = pexConfig.Field(
404 doc=
"Source catalog column name for trail angle (in radians).",
406 default=
"trail_angle",
411 raColName = pexConfig.Field(
412 doc=
"RA column name used in the fake source catalog.",
415 deprecated=
"Use `ra_col` instead."
418 decColName = pexConfig.Field(
419 doc=
"Dec. column name used in the fake source catalog.",
422 deprecated=
"Use `dec_col` instead."
425 diskHLR = pexConfig.Field(
426 doc=
"Column name for the disk half light radius used in the fake source catalog.",
428 default=
"DiskHalfLightRadius",
430 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
431 " to specify disk half-light ellipse."
435 aDisk = pexConfig.Field(
436 doc=
"The column name for the semi major axis length of the disk component used in the fake source"
441 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
442 " to specify disk half-light ellipse."
446 bDisk = pexConfig.Field(
447 doc=
"The column name for the semi minor axis length of the disk component.",
451 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
452 " to specify disk half-light ellipse."
456 paDisk = pexConfig.Field(
457 doc=
"The column name for the PA of the disk component used in the fake source catalog.",
461 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
462 " to specify disk half-light ellipse."
466 nDisk = pexConfig.Field(
467 doc=
"The column name for the sersic index of the disk component used in the fake source catalog.",
470 deprecated=
"Use `disk_n_col` instead."
473 bulgeHLR = pexConfig.Field(
474 doc=
"Column name for the bulge half light radius used in the fake source catalog.",
476 default=
"BulgeHalfLightRadius",
478 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
479 "`bulge_pa_col` to specify disk half-light ellipse."
483 aBulge = pexConfig.Field(
484 doc=
"The column name for the semi major axis length of the bulge component.",
488 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
489 "`bulge_pa_col` to specify disk half-light ellipse."
493 bBulge = pexConfig.Field(
494 doc=
"The column name for the semi minor axis length of the bulge component used in the fake source "
499 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
500 "`bulge_pa_col` to specify disk half-light ellipse."
504 paBulge = pexConfig.Field(
505 doc=
"The column name for the PA of the bulge component used in the fake source catalog.",
509 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
510 "`bulge_pa_col` to specify disk half-light ellipse."
514 nBulge = pexConfig.Field(
515 doc=
"The column name for the sersic index of the bulge component used in the fake source catalog.",
518 deprecated=
"Use `bulge_n_col` instead."
521 magVar = pexConfig.Field(
522 doc=
"The column name for the magnitude calculated taking variability into account. In the format "
523 "``filter name``magVar, e.g. imagVar for the magnitude in the i band.",
526 deprecated=
"Use `mag_col` instead."
529 sourceSelectionColName = pexConfig.Field(
530 doc=
"The name of the column in the input fakes catalogue to be used to determine which sources to"
531 "add, default is none and when this is used all sources are added.",
533 default=
"templateSource",
534 deprecated=
"Use `select_col` instead."
539 reason=
"This task will be removed in v28.0 as it is replaced by `source_injection` tasks.",
541 category=FutureWarning,
543class InsertFakesTask(PipelineTask):
544 """Insert fake objects into images.
546 Add fake stars and galaxies to the given image, read in through the dataRef. Galaxy parameters are read in
547 from the specified file and then modelled using galsim.
549 `InsertFakesTask` has five functions that make images of the fake sources and then add them to the
553 Use the WCS information to add the pixel coordinates of each source.
554 `mkFakeGalsimGalaxies`
555 Use Galsim to make fake double sersic galaxies for each set of galaxy parameters in the input file.
557 Use the PSF information from the image to make a fake star using the magnitude information from the
560 Remove rows of the input fake catalog which have half light radius, of either the bulge or the disk,
561 that are 0. Also removes rows that have Sersic index outside of galsim's allowed paramters. If
562 the config option sourceSelectionColName is set then this function limits the catalog of input fakes
563 to only those which are True in this column.
565 Add the fake sources to the image.
569 _DefaultName =
"insertFakes"
570 ConfigClass = InsertFakesConfig
572 def runQuantum(self, butlerQC, inputRefs, outputRefs):
573 inputs = butlerQC.get(inputRefs)
574 inputs[
"wcs"] = inputs[
"image"].getWcs()
575 inputs[
"photoCalib"] = inputs[
"image"].getPhotoCalib()
577 outputs = self.run(**inputs)
578 butlerQC.put(outputs, outputRefs)
580 def run(self, fakeCat, image, wcs, photoCalib):
581 """Add fake sources to an image.
585 fakeCat : `pandas.core.frame.DataFrame`
586 The catalog of fake sources to be input
587 image : `lsst.afw.image.exposure.exposure.ExposureF`
588 The image into which the fake sources should be added
589 wcs : `lsst.afw.geom.SkyWcs`
590 WCS to use to add fake sources
591 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
592 Photometric calibration to be used to calibrate the fake sources
596 resultStruct : `lsst.pipe.base.struct.Struct`
597 contains : image : `lsst.afw.image.exposure.exposure.ExposureF`
601 Adds pixel coordinates for each source to the fakeCat and removes objects with bulge or disk half
602 light radius = 0 (if ``config.doCleanCat = True``).
604 Adds the ``Fake`` mask plane to the image which is then set by `addFakeSources` to mark where fake
605 sources have been added. Uses the information in the ``fakeCat`` to make fake galaxies (using galsim)
606 and fake stars, using the PSF models from the PSF information for the image. These are then added to
607 the image and the image with fakes included returned.
609 The galsim galaxies are made using a double sersic profile, one for the bulge and one for the disk,
610 this is then convolved with the PSF at that point.
614 origWcs = image.getWcs()
615 origPhotoCalib = image.getPhotoCalib()
617 image.setPhotoCalib(photoCalib)
619 band = image.getFilter().bandLabel
620 fakeCat = self._standardizeColumns(fakeCat, band)
622 fakeCat = self.addPixCoords(fakeCat, image)
623 fakeCat = self.trimFakeCat(fakeCat, image)
626 if not self.config.insertImages:
627 if isinstance(fakeCat[self.config.sourceType].iloc[0], str):
628 galCheckVal =
"galaxy"
629 starCheckVal =
"star"
630 trailCheckVal =
"trail"
631 elif isinstance(fakeCat[self.config.sourceType].iloc[0], bytes):
632 galCheckVal = b
"galaxy"
633 starCheckVal = b
"star"
634 trailCheckVal = b
"trail"
635 elif isinstance(fakeCat[self.config.sourceType].iloc[0], (int, float)):
641 "sourceType column does not have required type, should be str, bytes or int"
643 if self.config.doCleanCat:
644 fakeCat = self.cleanCat(fakeCat, starCheckVal)
646 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
and not cfg.insertOnlyStars:
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
and not cfg.insertOnlyStars:
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, trailCheckVal):
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 specify an object is a galaxy.
782 starCheckVal : `str`, `bytes` or `int`
783 The value that is set in the sourceType column to specify an object is a star.
784 trailCheckVal : `str`, `bytes` or `int`
785 The value that is set in the sourceType column to specify an object is a star
789 gsObjects : `generator`
790 A generator of tuples of `lsst.geom.SpherePoint` and `galsim.GSObject`.
792 wcs = exposure.getWcs()
793 photoCalib = exposure.getPhotoCalib()
795 self.log.info(
"Making %d objects for insertion", len(fakeCat))
797 for (index, row)
in fakeCat.iterrows():
801 xy = wcs.skyToPixel(skyCoord)
804 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
808 sourceType = row[self.config.sourceType]
809 if sourceType == galCheckVal:
811 bulge_gs_HLR = row[
'bulge_semimajor']*np.sqrt(row[
'bulge_axis_ratio'])
812 bulge = galsim.Sersic(n=row[
'bulge_n'], half_light_radius=bulge_gs_HLR)
813 bulge = bulge.shear(q=row[
'bulge_axis_ratio'], beta=((90 - row[
'bulge_pa'])*galsim.degrees))
815 disk_gs_HLR = row[
'disk_semimajor']*np.sqrt(row[
'disk_axis_ratio'])
816 disk = galsim.Sersic(n=row[
'disk_n'], half_light_radius=disk_gs_HLR)
817 disk = disk.shear(q=row[
'disk_axis_ratio'], beta=((90 - row[
'disk_pa'])*galsim.degrees))
819 gal = bulge*row[
'bulge_disk_flux_ratio'] + disk
820 gal = gal.withFlux(flux)
823 elif sourceType == starCheckVal:
824 star = galsim.DeltaFunction()
825 star = star.withFlux(flux)
827 elif sourceType == trailCheckVal:
828 length = row[
'trail_length']
829 angle = row[
'trail_angle']
833 theta = galsim.Angle(angle*galsim.radians)
834 trail = galsim.Box(length, thickness)
835 trail = trail.rotate(theta)
836 trail = trail.withFlux(flux*length)
841 mat = wcs.linearizePixelToSky(skyCoord, geom.arcseconds).getMatrix()
842 trail = trail.transform(mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1])
844 yield skyCoord, trail
846 raise TypeError(f
"Unknown sourceType {sourceType}")
848 def _generateGSObjectsFromImages(self, exposure, fakeCat):
849 """Process catalog to generate `galsim.GSObject` s.
853 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
854 The exposure into which the fake sources should be added
855 fakeCat : `pandas.core.frame.DataFrame`
856 The catalog of fake sources to be input
860 gsObjects : `generator`
861 A generator of tuples of `lsst.geom.SpherePoint` and `galsim.GSObject`.
863 band = exposure.getFilter().bandLabel
864 wcs = exposure.getWcs()
865 photoCalib = exposure.getPhotoCalib()
867 self.log.info(
"Processing %d fake images", len(fakeCat))
869 for (index, row)
in fakeCat.iterrows():
873 xy = wcs.skyToPixel(skyCoord)
876 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
880 imFile = row[band+
"imFilename"]
882 imFile = imFile.decode(
"utf-8")
883 except AttributeError:
885 imFile = imFile.strip()
886 im = galsim.fits.read(imFile, read_header=
True)
888 if self.config.fits_alignment ==
"wcs":
897 f
"Cannot find WCS in input FITS file {imFile}"
899 elif self.config.fits_alignment ==
"pixel":
902 linWcs = wcs.linearizePixelToSky(skyCoord, geom.arcseconds)
903 mat = linWcs.getMatrix()
904 im.wcs = galsim.JacobianWCS(
905 mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1]
909 f
"Unknown fits_alignment type {self.config.fits_alignment}"
912 obj = galsim.InterpolatedImage(im, calculate_stepk=
False)
913 obj = obj.withFlux(flux)
917 """Process images from files into the format needed for insertion.
921 fakeCat : `pandas.core.frame.DataFrame`
922 The catalog of fake sources to be input
923 wcs : `lsst.afw.geom.skyWcs.skyWcs.SkyWc`
924 WCS to use to add fake sources
925 psf : `lsst.meas.algorithms.coaddPsf.coaddPsf.CoaddPsf` or
926 `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
927 The PSF information to use to make the PSF images
928 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
929 Photometric calibration to be used to calibrate the fake sources
931 The filter band that the observation was taken in.
933 The pixel scale of the image the sources are to be added to.
938 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
939 `lsst.geom.Point2D` of their locations.
940 For sources labelled as galaxy.
942 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
943 `lsst.geom.Point2D` of their locations.
944 For sources labelled as star.
948 The input fakes catalog needs to contain the absolute path to the image in the
949 band that is being used to add images to. It also needs to have the R.A. and
950 declination of the fake source in radians and the sourceType of the object.
955 self.log.info(
"Processing %d fake images", len(fakeCat))
957 for (imFile, sourceType, mag, x, y)
in zip(fakeCat[band +
"imFilename"].array,
958 fakeCat[
"sourceType"].array,
959 fakeCat[
'mag'].array,
960 fakeCat[
"x"].array, fakeCat[
"y"].array):
962 im = afwImage.ImageF.readFits(imFile)
969 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
970 psfKernel = psf.computeKernelImage(xy).getArray()
971 psfKernel /= correctedFlux
973 except InvalidParameterError:
974 self.log.info(
"%s at %0.4f, %0.4f outside of image", sourceType, x, y)
977 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
978 galsimIm = galsim.InterpolatedImage(galsim.Image(im.array), scale=pixelScale)
979 convIm = galsim.Convolve([galsimIm, psfIm])
982 outIm = convIm.drawImage(scale=pixelScale, method=
"real_space").array
983 except (galsim.errors.GalSimFFTSizeError, MemoryError):
986 imSum = np.sum(outIm)
990 flux = photoCalib.magnitudeToInstFlux(mag, xy)
994 imWithFlux = flux*divIm
996 if sourceType == b
"galaxy":
997 galImages.append((afwImage.ImageF(imWithFlux), xy))
998 if sourceType == b
"star":
999 starImages.append((afwImage.ImageF(imWithFlux), xy))
1001 return galImages, starImages
1005 """Add pixel coordinates to the catalog of fakes.
1009 fakeCat : `pandas.core.frame.DataFrame`
1010 The catalog of fake sources to be input
1011 image : `lsst.afw.image.exposure.exposure.ExposureF`
1012 The image into which the fake sources should be added
1016 fakeCat : `pandas.core.frame.DataFrame`
1018 wcs = image.getWcs()
1019 ras = fakeCat[
'ra'].values
1020 decs = fakeCat[
'dec'].values
1021 xs, ys = wcs.skyToPixelArray(ras, decs)
1028 """Trim the fake cat to the size of the input image plus trimBuffer padding.
1030 `fakeCat` must be processed with addPixCoords before using this method.
1034 fakeCat : `pandas.core.frame.DataFrame`
1035 The catalog of fake sources to be input
1036 image : `lsst.afw.image.exposure.exposure.ExposureF`
1037 The image into which the fake sources should be added
1041 fakeCat : `pandas.core.frame.DataFrame`
1042 The original fakeCat trimmed to the area of the image
1044 wideBbox =
Box2D(image.getBBox()).dilatedBy(self.config.trimBuffer)
1048 ras = fakeCat[self.config.ra_col].values * u.rad
1049 decs = fakeCat[self.config.dec_col].values * u.rad
1051 isContainedRaDec = image.containsSkyCoords(ras, decs, padding=self.config.trimBuffer)
1054 xs = fakeCat[
"x"].values
1055 ys = fakeCat[
"y"].values
1057 isContainedXy = xs >= wideBbox.minX
1058 isContainedXy &= xs <= wideBbox.maxX
1059 isContainedXy &= ys >= wideBbox.minY
1060 isContainedXy &= ys <= wideBbox.maxY
1062 return fakeCat[isContainedRaDec & isContainedXy]
1065 """Make images of fake galaxies using GalSim.
1070 pixelScale : `float`
1071 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
1072 The PSF information to use to make the PSF images
1073 fakeCat : `pandas.core.frame.DataFrame`
1074 The catalog of fake sources to be input
1075 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
1076 Photometric calibration to be used to calibrate the fake sources
1080 galImages : `generator`
1081 A generator of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
1082 `lsst.geom.Point2D` of their locations.
1087 Fake galaxies are made by combining two sersic profiles, one for the bulge and one for the disk. Each
1088 component has an individual sersic index (n), a, b and position angle (PA). The combined profile is
1089 then convolved with the PSF at the specified x, y position on the image.
1091 The names of the columns in the ``fakeCat`` are configurable and are the column names from the
1092 University of Washington simulations database as default. For more information see the doc strings
1093 attached to the config options.
1095 See mkFakeStars doc string for an explanation of calibration to instrumental flux.
1098 self.log.info(
"Making %d fake galaxy images", len(fakeCat))
1100 for (index, row)
in fakeCat.iterrows():
1106 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1107 psfKernel = psf.computeKernelImage(xy).getArray()
1108 psfKernel /= correctedFlux
1110 except InvalidParameterError:
1111 self.log.info(
"Galaxy at %0.4f, %0.4f outside of image", row[
"x"], row[
"y"])
1115 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
1120 bulge_gs_HLR = row[
'bulge_semimajor']*np.sqrt(row[
'bulge_axis_ratio'])
1121 bulge = galsim.Sersic(n=row[
'bulge_n'], half_light_radius=bulge_gs_HLR)
1122 bulge = bulge.shear(q=row[
'bulge_axis_ratio'], beta=((90 - row[
'bulge_pa'])*galsim.degrees))
1124 disk_gs_HLR = row[
'disk_semimajor']*np.sqrt(row[
'disk_axis_ratio'])
1125 disk = galsim.Sersic(n=row[
'disk_n'], half_light_radius=disk_gs_HLR)
1126 disk = disk.shear(q=row[
'disk_axis_ratio'], beta=((90 - row[
'disk_pa'])*galsim.degrees))
1128 gal = bulge*row[
'bulge_disk_flux_ratio'] + disk
1129 gal = gal.withFlux(flux)
1131 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
1132 gal = galsim.Convolve([gal, psfIm])
1134 galIm = gal.drawImage(scale=pixelScale, method=
"real_space").array
1135 except (galsim.errors.GalSimFFTSizeError, MemoryError):
1138 yield (afwImage.ImageF(galIm), xy)
1142 """Make fake stars based off the properties in the fakeCat.
1147 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
1148 The PSF information to use to make the PSF images
1149 fakeCat : `pandas.core.frame.DataFrame`
1150 The catalog of fake sources to be input
1151 image : `lsst.afw.image.exposure.exposure.ExposureF`
1152 The image into which the fake sources should be added
1153 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
1154 Photometric calibration to be used to calibrate the fake sources
1158 starImages : `generator`
1159 A generator of tuples of `lsst.afw.image.ImageF` of fake stars and
1160 `lsst.geom.Point2D` of their locations.
1164 To take a given magnitude and translate to the number of counts in the image
1165 we use photoCalib.magnitudeToInstFlux, which returns the instrumental flux for the
1166 given calibration radius used in the photometric calibration step.
1167 Thus `calibFluxRadius` should be set to this same radius so that we can normalize
1168 the PSF model to the correct instrumental flux within calibFluxRadius.
1171 self.log.info(
"Making %d fake star images", len(fakeCat))
1173 for (index, row)
in fakeCat.iterrows():
1179 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1180 starIm = psf.computeImage(xy)
1181 starIm /= correctedFlux
1183 except InvalidParameterError:
1184 self.log.info(
"Star at %0.4f, %0.4f outside of image", row[
"x"], row[
"y"])
1188 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
1193 yield ((starIm.convertF(), xy))
1196 """Remove rows from the fakes catalog which have HLR = 0 for either the buldge or disk component,
1197 also remove galaxies that have Sersic index outside the galsim min and max
1198 allowed (0.3 <= n <= 6.2).
1202 fakeCat : `pandas.core.frame.DataFrame`
1203 The catalog of fake sources to be input
1204 starCheckVal : `str`, `bytes` or `int`
1205 The value that is set in the sourceType column to specifiy an object is a star.
1209 fakeCat : `pandas.core.frame.DataFrame`
1210 The input catalog of fake sources but with the bad objects removed
1213 rowsToKeep = (((fakeCat[
'bulge_semimajor'] != 0.0) & (fakeCat[
'disk_semimajor'] != 0.0))
1214 | (fakeCat[self.config.sourceType] == starCheckVal))
1215 numRowsNotUsed = len(fakeCat) - len(np.where(rowsToKeep)[0])
1216 self.log.info(
"Removing %d rows with HLR = 0 for either the bulge or disk", numRowsNotUsed)
1217 fakeCat = fakeCat[rowsToKeep]
1219 minN = galsim.Sersic._minimum_n
1220 maxN = galsim.Sersic._maximum_n
1221 rowsWithGoodSersic = (((fakeCat[
'bulge_n'] >= minN) & (fakeCat[
'bulge_n'] <= maxN)
1222 & (fakeCat[
'disk_n'] >= minN) & (fakeCat[
'disk_n'] <= maxN))
1223 | (fakeCat[self.config.sourceType] == starCheckVal))
1224 numRowsNotUsed = len(fakeCat) - len(np.where(rowsWithGoodSersic)[0])
1225 self.log.info(
"Removing %d rows of galaxies with nBulge or nDisk outside of %0.2f <= n <= %0.2f",
1226 numRowsNotUsed, minN, maxN)
1227 fakeCat = fakeCat[rowsWithGoodSersic]
1229 if self.config.doSubSelectSources:
1230 numRowsNotUsed = len(fakeCat) - len(fakeCat[
'select'])
1231 self.log.info(
"Removing %d rows which were not designated as template sources", numRowsNotUsed)
1232 fakeCat = fakeCat[fakeCat[
'select']]
1237 """Add the fake sources to the given image
1241 image : `lsst.afw.image.exposure.exposure.ExposureF`
1242 The image into which the fake sources should be added
1243 fakeImages : `typing.Iterator` [`tuple` ['lsst.afw.image.ImageF`, `lsst.geom.Point2d`]]
1244 An iterator of tuples that contains (or generates) images of fake sources,
1245 and the locations they are to be inserted at.
1247 The type (star/galaxy) of fake sources input
1251 image : `lsst.afw.image.exposure.exposure.ExposureF`
1255 Uses the x, y information in the ``fakeCat`` to position an image of the fake interpolated onto the
1256 pixel grid of the image. Sets the ``FAKE`` mask plane for the pixels added with the fake source.
1259 imageBBox = image.getBBox()
1260 imageMI = image.maskedImage
1262 for (fakeImage, xy)
in fakeImages:
1263 X0 = xy.getX() - fakeImage.getWidth()/2 + 0.5
1264 Y0 = xy.getY() - fakeImage.getHeight()/2 + 0.5
1265 self.log.debug(
"Adding fake source at %d, %d", xy.getX(), xy.getY())
1266 if sourceType ==
"galaxy":
1269 interpFakeImage = fakeImage
1271 interpFakeImBBox = interpFakeImage.getBBox()
1272 interpFakeImBBox.clip(imageBBox)
1274 if interpFakeImBBox.getArea() > 0:
1275 imageMIView = imageMI[interpFakeImBBox]
1276 clippedFakeImage = interpFakeImage[interpFakeImBBox]
1277 clippedFakeImageMI = afwImage.MaskedImageF(clippedFakeImage)
1278 clippedFakeImageMI.mask.set(self.bitmask)
1279 imageMIView += clippedFakeImageMI
A floating-point coordinate rectangle geometry.
An integer coordinate rectangle.
Point in an unspecified spherical coordinate system.
std::shared_ptr< ImageT > offsetImage(ImageT const &image, float dx, float dy, std::string const &algorithmName="lanczos5", unsigned int buffer=0)
Return an image offset by (dx, dy) using the specified algorithm.
addPixCoords(self, fakeCat, image)
_isWCSGalsimDefault(wcs, hdr)
mkFakeStars(self, fakeCat, band, photoCalib, psf, image)
_add_fake_sources(exposure, objects, calibFluxRadius=12.0, logger=None)
addFakeSources(self, image, fakeImages, sourceType)
cleanCat(self, fakeCat, starCheckVal)
trimFakeCat(self, fakeCat, image)
mkFakeGalsimGalaxies(self, fakeCat, band, photoCalib, pixelScale, psf, image)
processImagesForInsertion(self, fakeCat, wcs, psf, photoCalib, band, pixelScale)