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
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(
"Adding mask plane with bitmask %s", 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(
"Adding fake source at %s", 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 apCorr = psf.computeApertureFlux(calibFluxRadius, pt)
96 except InvalidParameterError:
98 contained_pt = Point2D(
99 np.clip(pt.x, bbox.minX, bbox.maxX),
100 np.clip(pt.y, bbox.minY, bbox.maxY)
102 if pt == contained_pt:
104 logger.info(
"Cannot compute Psf for object at %s; skipping", pt)
108 psfArr = psf.computeKernelImage(contained_pt).array
109 apCorr = psf.computeApertureFlux(calibFluxRadius, contained_pt)
110 except InvalidParameterError:
112 logger.info(
"Cannot compute Psf for object at %s; skipping", pt)
116 gsPSF = galsim.InterpolatedImage(galsim.Image(psfArr), wcs=gsWCS)
118 conv = galsim.Convolve(gsObj, gsPSF)
119 stampSize = conv.getGoodImageSize(gsWCS.minLinearScale())
120 subBounds = galsim.BoundsI(posi).withBorder(stampSize//2)
121 subBounds &= fullBounds
123 if subBounds.area() > 0:
124 subImg = gsImg[subBounds]
125 offset = posd - subBounds.true_center
142 exposure[subBox].mask.array |= bitmask
146 """Decide if wcs = galsim.PixelScale(1.0) is explicitly present in header,
147 or if it's just the galsim default.
152 Potentially default WCS.
153 hdr : galsim.fits.FitsHeader
154 Header as read in by galsim.
159 True if default, False if explicitly set in header.
161 if wcs != galsim.PixelScale(1.0):
163 if hdr.get(
'GS_WCS')
is not None:
165 if hdr.get(
'CTYPE1',
'LINEAR') ==
'LINEAR':
166 return not any(k
in hdr
for k
in [
'CD1_1',
'CDELT1'])
167 for wcs_type
in galsim.fitswcs.fits_wcs_types:
170 wcs_type._readHeader(hdr)
175 return not any(k
in hdr
for k
in [
'CD1_1',
'CDELT1'])
179 defaultTemplates={
"coaddName":
"deep",
180 "fakesType":
"fakes_"},
181 dimensions=(
"tract",
"patch",
"band",
"skymap")):
184 doc=
"Image into which fakes are to be added.",
185 name=
"{coaddName}Coadd",
186 storageClass=
"ExposureF",
187 dimensions=(
"tract",
"patch",
"band",
"skymap")
191 doc=
"Catalog of fake sources to draw inputs from.",
192 name=
"{fakesType}fakeSourceCat",
193 storageClass=
"DataFrame",
194 dimensions=(
"tract",
"skymap")
197 imageWithFakes = cT.Output(
198 doc=
"Image with fake sources added.",
199 name=
"{fakesType}{coaddName}Coadd",
200 storageClass=
"ExposureF",
201 dimensions=(
"tract",
"patch",
"band",
"skymap")
205class InsertFakesConfig(PipelineTaskConfig,
206 pipelineConnections=InsertFakesConnections):
207 """Config for inserting fake sources
212 doCleanCat = pexConfig.Field(
213 doc=
"If true removes bad sources from the catalog.",
218 fakeType = pexConfig.Field(
219 doc=
"What type of fake catalog to use, snapshot (includes variability in the magnitudes calculated "
220 "from the MJD of the image), static (no variability) or filename for a user defined fits"
226 calibFluxRadius = pexConfig.Field(
227 doc=
"Aperture radius (in pixels) that was used to define the calibration for this image+catalog. "
228 "This will be used to produce the correct instrumental fluxes within the radius. "
229 "This value should match that of the field defined in slot_CalibFlux_instFlux.",
234 coaddName = pexConfig.Field(
235 doc=
"The name of the type of coadd used",
240 doSubSelectSources = pexConfig.Field(
241 doc=
"Set to True if you wish to sub select sources to be input based on the value in the column"
242 "set in the sourceSelectionColName config option.",
247 insertImages = pexConfig.Field(
248 doc=
"Insert images directly? True or False.",
253 insertOnlyStars = pexConfig.Field(
254 doc=
"Insert only stars? True or False.",
259 doProcessAllDataIds = pexConfig.Field(
260 doc=
"If True, all input data IDs will be processed, even those containing no fake sources.",
265 trimBuffer = pexConfig.Field(
266 doc=
"Size of the pixel buffer surrounding the image. Only those fake sources with a centroid"
267 "falling within the image+buffer region will be considered for fake source injection.",
272 sourceType = pexConfig.Field(
273 doc=
"The column name for the source type used in the fake source catalog.",
275 default=
"sourceType",
278 fits_alignment = pexConfig.ChoiceField(
279 doc=
"How should injections from FITS files be aligned?",
283 "Input image will be transformed such that the local WCS in "
284 "the FITS header matches the local WCS in the target image. "
285 "I.e., North, East, and angular distances in the input image "
286 "will match North, East, and angular distances in the target "
290 "Input image will _not_ be transformed. Up, right, and pixel "
291 "distances in the input image will match up, right and pixel "
292 "distances in the target image."
300 ra_col = pexConfig.Field(
301 doc=
"Source catalog column name for RA (in radians).",
306 dec_col = pexConfig.Field(
307 doc=
"Source catalog column name for dec (in radians).",
312 bulge_semimajor_col = pexConfig.Field(
313 doc=
"Source catalog column name for the semimajor axis (in arcseconds) "
314 "of the bulge half-light ellipse.",
316 default=
"bulge_semimajor",
319 bulge_axis_ratio_col = pexConfig.Field(
320 doc=
"Source catalog column name for the axis ratio of the bulge "
321 "half-light ellipse.",
323 default=
"bulge_axis_ratio",
326 bulge_pa_col = pexConfig.Field(
327 doc=
"Source catalog column name for the position angle (measured from "
328 "North through East in degrees) of the semimajor axis of the bulge "
329 "half-light ellipse.",
334 bulge_n_col = pexConfig.Field(
335 doc=
"Source catalog column name for the Sersic index of the bulge.",
340 disk_semimajor_col = pexConfig.Field(
341 doc=
"Source catalog column name for the semimajor axis (in arcseconds) "
342 "of the disk half-light ellipse.",
344 default=
"disk_semimajor",
347 disk_axis_ratio_col = pexConfig.Field(
348 doc=
"Source catalog column name for the axis ratio of the disk "
349 "half-light ellipse.",
351 default=
"disk_axis_ratio",
354 disk_pa_col = pexConfig.Field(
355 doc=
"Source catalog column name for the position angle (measured from "
356 "North through East in degrees) of the semimajor axis of the disk "
357 "half-light ellipse.",
362 disk_n_col = pexConfig.Field(
363 doc=
"Source catalog column name for the Sersic index of the disk.",
368 bulge_disk_flux_ratio_col = pexConfig.Field(
369 doc=
"Source catalog column name for the bulge/disk flux ratio.",
371 default=
"bulge_disk_flux_ratio",
374 mag_col = pexConfig.Field(
375 doc=
"Source catalog column name template for magnitudes, in the format "
376 "``filter name``_mag_col. E.g., if this config variable is set to "
377 "``%s_mag``, then the i-band magnitude will be searched for in the "
378 "``i_mag`` column of the source catalog.",
383 select_col = pexConfig.Field(
384 doc=
"Source catalog column name to be used to select which sources to "
390 length_col = pexConfig.Field(
391 doc=
"Source catalog column name for trail length (in pixels).",
393 default=
"trail_length",
396 angle_col = pexConfig.Field(
397 doc=
"Source catalog column name for trail angle (in radians).",
399 default=
"trail_angle",
404 raColName = pexConfig.Field(
405 doc=
"RA column name used in the fake source catalog.",
408 deprecated=
"Use `ra_col` instead."
411 decColName = pexConfig.Field(
412 doc=
"Dec. column name used in the fake source catalog.",
415 deprecated=
"Use `dec_col` instead."
418 diskHLR = pexConfig.Field(
419 doc=
"Column name for the disk half light radius used in the fake source catalog.",
421 default=
"DiskHalfLightRadius",
423 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
424 " to specify disk half-light ellipse."
428 aDisk = pexConfig.Field(
429 doc=
"The column name for the semi major axis length of the disk component used in the fake source"
434 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
435 " to specify disk half-light ellipse."
439 bDisk = pexConfig.Field(
440 doc=
"The column name for the semi minor axis length of the disk component.",
444 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
445 " to specify disk half-light ellipse."
449 paDisk = pexConfig.Field(
450 doc=
"The column name for the PA of the disk component used in the fake source catalog.",
454 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
455 " to specify disk half-light ellipse."
459 nDisk = pexConfig.Field(
460 doc=
"The column name for the sersic index of the disk component used in the fake source catalog.",
463 deprecated=
"Use `disk_n_col` instead."
466 bulgeHLR = pexConfig.Field(
467 doc=
"Column name for the bulge half light radius used in the fake source catalog.",
469 default=
"BulgeHalfLightRadius",
471 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
472 "`bulge_pa_col` to specify disk half-light ellipse."
476 aBulge = pexConfig.Field(
477 doc=
"The column name for the semi major axis length of the bulge component.",
481 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
482 "`bulge_pa_col` to specify disk half-light ellipse."
486 bBulge = pexConfig.Field(
487 doc=
"The column name for the semi minor axis length of the bulge component used in the fake source "
492 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
493 "`bulge_pa_col` to specify disk half-light ellipse."
497 paBulge = pexConfig.Field(
498 doc=
"The column name for the PA of the bulge component used in the fake source catalog.",
502 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
503 "`bulge_pa_col` to specify disk half-light ellipse."
507 nBulge = pexConfig.Field(
508 doc=
"The column name for the sersic index of the bulge component used in the fake source catalog.",
511 deprecated=
"Use `bulge_n_col` instead."
514 magVar = pexConfig.Field(
515 doc=
"The column name for the magnitude calculated taking variability into account. In the format "
516 "``filter name``magVar, e.g. imagVar for the magnitude in the i band.",
519 deprecated=
"Use `mag_col` instead."
522 sourceSelectionColName = pexConfig.Field(
523 doc=
"The name of the column in the input fakes catalogue to be used to determine which sources to"
524 "add, default is none and when this is used all sources are added.",
526 default=
"templateSource",
527 deprecated=
"Use `select_col` instead."
531class InsertFakesTask(PipelineTask):
532 """Insert fake objects into images.
534 Add fake stars and galaxies to the given image, read in through the dataRef. Galaxy parameters are read in
535 from the specified file and then modelled using galsim.
537 `InsertFakesTask` has five functions that make images of the fake sources and then add them to the
541 Use the WCS information to add the pixel coordinates of each source.
542 `mkFakeGalsimGalaxies`
543 Use Galsim to make fake double sersic galaxies for each set of galaxy parameters in the input file.
545 Use the PSF information from the image to make a fake star using the magnitude information from the
548 Remove rows of the input fake catalog which have half light radius, of either the bulge or the disk,
549 that are 0. Also removes rows that have Sersic index outside of galsim's allowed paramters. If
550 the config option sourceSelectionColName is set then this function limits the catalog of input fakes
551 to only those which are True in this column.
553 Add the fake sources to the image.
557 _DefaultName =
"insertFakes"
558 ConfigClass = InsertFakesConfig
560 def runQuantum(self, butlerQC, inputRefs, outputRefs):
561 inputs = butlerQC.get(inputRefs)
562 inputs[
"wcs"] = inputs[
"image"].getWcs()
563 inputs[
"photoCalib"] = inputs[
"image"].getPhotoCalib()
565 outputs = self.run(**inputs)
566 butlerQC.put(outputs, outputRefs)
568 def run(self, fakeCat, image, wcs, photoCalib):
569 """Add fake sources to an image.
573 fakeCat : `pandas.core.frame.DataFrame`
574 The catalog of fake sources to be input
575 image : `lsst.afw.image.exposure.exposure.ExposureF`
576 The image into which the fake sources should be added
577 wcs : `lsst.afw.geom.SkyWcs`
578 WCS to use to add fake sources
579 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
580 Photometric calibration to be used to calibrate the fake sources
584 resultStruct : `lsst.pipe.base.struct.Struct`
585 contains : image : `lsst.afw.image.exposure.exposure.ExposureF`
589 Adds pixel coordinates for each source to the fakeCat and removes objects with bulge or disk half
590 light radius = 0 (if ``config.doCleanCat = True``).
592 Adds the ``Fake`` mask plane to the image which is then set by `addFakeSources` to mark where fake
593 sources have been added. Uses the information in the ``fakeCat`` to make fake galaxies (using galsim)
594 and fake stars, using the PSF models from the PSF information for the image. These are then added to
595 the image and the image with fakes included returned.
597 The galsim galaxies are made using a double sersic profile, one for the bulge and one for the disk,
598 this is then convolved with the PSF at that point.
602 origWcs = image.getWcs()
603 origPhotoCalib = image.getPhotoCalib()
605 image.setPhotoCalib(photoCalib)
607 band = image.getFilter().bandLabel
608 fakeCat = self._standardizeColumns(fakeCat, band)
610 fakeCat = self.addPixCoords(fakeCat, image)
611 fakeCat = self.trimFakeCat(fakeCat, image)
614 if not self.config.insertImages:
615 if isinstance(fakeCat[self.config.sourceType].iloc[0], str):
616 galCheckVal =
"galaxy"
617 starCheckVal =
"star"
618 trailCheckVal =
"trail"
619 elif isinstance(fakeCat[self.config.sourceType].iloc[0], bytes):
620 galCheckVal = b
"galaxy"
621 starCheckVal = b
"star"
622 trailCheckVal = b
"trail"
623 elif isinstance(fakeCat[self.config.sourceType].iloc[0], (int, float)):
629 "sourceType column does not have required type, should be str, bytes or int"
631 if self.config.doCleanCat:
632 fakeCat = self.cleanCat(fakeCat, starCheckVal)
634 generator = self._generateGSObjectsFromCatalog(image, fakeCat, galCheckVal, starCheckVal,
637 generator = self._generateGSObjectsFromImages(image, fakeCat)
638 _add_fake_sources(image, generator, calibFluxRadius=self.config.calibFluxRadius, logger=self.log)
639 elif len(fakeCat) == 0
and self.config.doProcessAllDataIds:
640 self.log.warning(
"No fakes found for this dataRef; processing anyway.")
641 image.mask.addMaskPlane(
"FAKE")
643 raise RuntimeError(
"No fakes found for this dataRef.")
646 image.setWcs(origWcs)
647 image.setPhotoCalib(origPhotoCalib)
649 resultStruct = pipeBase.Struct(imageWithFakes=image)
653 def _standardizeColumns(self, fakeCat, band):
654 """Use config variables to 'standardize' the expected columns and column
655 names in the input catalog.
659 fakeCat : `pandas.core.frame.DataFrame`
660 The catalog of fake sources to be input
662 Label for the current band being processed.
666 outCat : `pandas.core.frame.DataFrame`
667 The standardized catalog of fake sources
672 def add_to_replace_dict(new_name, depr_name, std_name):
673 if new_name
in fakeCat.columns:
674 replace_dict[new_name] = std_name
675 elif depr_name
in fakeCat.columns:
676 replace_dict[depr_name] = std_name
678 raise ValueError(f
"Could not determine column for {std_name}.")
682 for new_name, depr_name, std_name
in [
683 (cfg.ra_col, cfg.raColName,
'ra'),
684 (cfg.dec_col, cfg.decColName,
'dec'),
685 (cfg.mag_col%band, cfg.magVar%band,
'mag')
687 add_to_replace_dict(new_name, depr_name, std_name)
689 if not cfg.insertImages
and not cfg.insertOnlyStars:
690 for new_name, depr_name, std_name
in [
691 (cfg.bulge_n_col, cfg.nBulge,
'bulge_n'),
692 (cfg.bulge_pa_col, cfg.paBulge,
'bulge_pa'),
693 (cfg.disk_n_col, cfg.nDisk,
'disk_n'),
694 (cfg.disk_pa_col, cfg.paDisk,
'disk_pa'),
696 add_to_replace_dict(new_name, depr_name, std_name)
698 if cfg.doSubSelectSources:
701 cfg.sourceSelectionColName,
704 fakeCat = fakeCat.rename(columns=replace_dict, copy=
False)
709 if not cfg.insertImages
and not cfg.insertOnlyStars:
711 cfg.bulge_semimajor_col
in fakeCat.columns
712 and cfg.bulge_axis_ratio_col
in fakeCat.columns
714 fakeCat = fakeCat.rename(
716 cfg.bulge_semimajor_col:
'bulge_semimajor',
717 cfg.bulge_axis_ratio_col:
'bulge_axis_ratio',
718 cfg.disk_semimajor_col:
'disk_semimajor',
719 cfg.disk_axis_ratio_col:
'disk_axis_ratio',
724 cfg.bulgeHLR
in fakeCat.columns
725 and cfg.aBulge
in fakeCat.columns
726 and cfg.bBulge
in fakeCat.columns
728 fakeCat[
'bulge_axis_ratio'] = (
729 fakeCat[cfg.bBulge]/fakeCat[cfg.aBulge]
731 fakeCat[
'bulge_semimajor'] = (
732 fakeCat[cfg.bulgeHLR]/np.sqrt(fakeCat[
'bulge_axis_ratio'])
734 fakeCat[
'disk_axis_ratio'] = (
735 fakeCat[cfg.bDisk]/fakeCat[cfg.aDisk]
737 fakeCat[
'disk_semimajor'] = (
738 fakeCat[cfg.diskHLR]/np.sqrt(fakeCat[
'disk_axis_ratio'])
742 "Could not determine columns for half-light radius and "
747 if cfg.bulge_disk_flux_ratio_col
in fakeCat.columns:
748 fakeCat = fakeCat.rename(
750 cfg.bulge_disk_flux_ratio_col:
'bulge_disk_flux_ratio'
755 fakeCat[
'bulge_disk_flux_ratio'] = 1.0
759 def _generateGSObjectsFromCatalog(self, exposure, fakeCat, galCheckVal, starCheckVal, trailCheckVal):
760 """Process catalog to generate `galsim.GSObject` s.
764 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
765 The exposure into which the fake sources should be added
766 fakeCat : `pandas.core.frame.DataFrame`
767 The catalog of fake sources to be input
768 galCheckVal : `str`, `bytes` or `int`
769 The value that is set in the sourceType column to specify an object is a galaxy.
770 starCheckVal : `str`, `bytes` or `int`
771 The value that is set in the sourceType column to specify an object is a star.
772 trailCheckVal : `str`, `bytes` or `int`
773 The value that is set in the sourceType column to specify an object is a star
777 gsObjects : `generator`
778 A generator of tuples of `lsst.geom.SpherePoint` and `galsim.GSObject`.
780 wcs = exposure.getWcs()
781 photoCalib = exposure.getPhotoCalib()
783 self.log.info(
"Making %d objects for insertion", len(fakeCat))
785 for (index, row)
in fakeCat.iterrows():
789 xy = wcs.skyToPixel(skyCoord)
792 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
796 sourceType = row[self.config.sourceType]
797 if sourceType == galCheckVal:
799 bulge_gs_HLR = row[
'bulge_semimajor']*np.sqrt(row[
'bulge_axis_ratio'])
800 bulge = galsim.Sersic(n=row[
'bulge_n'], half_light_radius=bulge_gs_HLR)
801 bulge = bulge.shear(q=row[
'bulge_axis_ratio'], beta=((90 - row[
'bulge_pa'])*galsim.degrees))
803 disk_gs_HLR = row[
'disk_semimajor']*np.sqrt(row[
'disk_axis_ratio'])
804 disk = galsim.Sersic(n=row[
'disk_n'], half_light_radius=disk_gs_HLR)
805 disk = disk.shear(q=row[
'disk_axis_ratio'], beta=((90 - row[
'disk_pa'])*galsim.degrees))
807 gal = bulge*row[
'bulge_disk_flux_ratio'] + disk
808 gal = gal.withFlux(flux)
811 elif sourceType == starCheckVal:
812 star = galsim.DeltaFunction()
813 star = star.withFlux(flux)
815 elif sourceType == trailCheckVal:
816 length = row[
'trail_length']
817 angle = row[
'trail_angle']
821 theta = galsim.Angle(angle*galsim.radians)
822 trail = galsim.Box(length, thickness)
823 trail = trail.rotate(theta)
824 trail = trail.withFlux(flux*length)
829 mat = wcs.linearizePixelToSky(skyCoord, geom.arcseconds).getMatrix()
830 trail = trail.transform(mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1])
832 yield skyCoord, trail
834 raise TypeError(f
"Unknown sourceType {sourceType}")
836 def _generateGSObjectsFromImages(self, exposure, fakeCat):
837 """Process catalog to generate `galsim.GSObject` s.
841 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
842 The exposure into which the fake sources should be added
843 fakeCat : `pandas.core.frame.DataFrame`
844 The catalog of fake sources to be input
848 gsObjects : `generator`
849 A generator of tuples of `lsst.geom.SpherePoint` and `galsim.GSObject`.
851 band = exposure.getFilter().bandLabel
852 wcs = exposure.getWcs()
853 photoCalib = exposure.getPhotoCalib()
855 self.log.info(
"Processing %d fake images", len(fakeCat))
857 for (index, row)
in fakeCat.iterrows():
861 xy = wcs.skyToPixel(skyCoord)
864 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
868 imFile = row[band+
"imFilename"]
870 imFile = imFile.decode(
"utf-8")
871 except AttributeError:
873 imFile = imFile.strip()
874 im = galsim.fits.read(imFile, read_header=
True)
876 if self.config.fits_alignment ==
"wcs":
885 f
"Cannot find WCS in input FITS file {imFile}"
887 elif self.config.fits_alignment ==
"pixel":
890 linWcs = wcs.linearizePixelToSky(skyCoord, geom.arcseconds)
891 mat = linWcs.getMatrix()
892 im.wcs = galsim.JacobianWCS(
893 mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1]
897 f
"Unknown fits_alignment type {self.config.fits_alignment}"
900 obj = galsim.InterpolatedImage(im, calculate_stepk=
False)
901 obj = obj.withFlux(flux)
905 """Process images from files into the format needed for insertion.
909 fakeCat : `pandas.core.frame.DataFrame`
910 The catalog of fake sources to be input
911 wcs : `lsst.afw.geom.skyWcs.skyWcs.SkyWc`
912 WCS to use to add fake sources
913 psf : `lsst.meas.algorithms.coaddPsf.coaddPsf.CoaddPsf` or
914 `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
915 The PSF information to use to make the PSF images
916 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
917 Photometric calibration to be used to calibrate the fake sources
919 The filter band that the observation was taken in.
921 The pixel scale of the image the sources are to be added to.
926 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
927 `lsst.geom.Point2D` of their locations.
928 For sources labelled as galaxy.
930 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
931 `lsst.geom.Point2D` of their locations.
932 For sources labelled as star.
936 The input fakes catalog needs to contain the absolute path to the image in the
937 band that is being used to add images to. It also needs to have the R.A. and
938 declination of the fake source in radians and the sourceType of the object.
943 self.log.info(
"Processing %d fake images", len(fakeCat))
945 for (imFile, sourceType, mag, x, y)
in zip(fakeCat[band +
"imFilename"].array,
946 fakeCat[
"sourceType"].array,
947 fakeCat[
'mag'].array,
948 fakeCat[
"x"].array, fakeCat[
"y"].array):
950 im = afwImage.ImageF.readFits(imFile)
957 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
958 psfKernel = psf.computeKernelImage(xy).getArray()
959 psfKernel /= correctedFlux
961 except InvalidParameterError:
962 self.log.info(
"%s at %0.4f, %0.4f outside of image", sourceType, x, y)
965 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
966 galsimIm = galsim.InterpolatedImage(galsim.Image(im.array), scale=pixelScale)
967 convIm = galsim.Convolve([galsimIm, psfIm])
970 outIm = convIm.drawImage(scale=pixelScale, method=
"real_space").array
971 except (galsim.errors.GalSimFFTSizeError, MemoryError):
974 imSum = np.sum(outIm)
978 flux = photoCalib.magnitudeToInstFlux(mag, xy)
982 imWithFlux = flux*divIm
984 if sourceType == b
"galaxy":
985 galImages.append((afwImage.ImageF(imWithFlux), xy))
986 if sourceType == b
"star":
987 starImages.append((afwImage.ImageF(imWithFlux), xy))
989 return galImages, starImages
993 """Add pixel coordinates to the catalog of fakes.
997 fakeCat : `pandas.core.frame.DataFrame`
998 The catalog of fake sources to be input
999 image : `lsst.afw.image.exposure.exposure.ExposureF`
1000 The image into which the fake sources should be added
1004 fakeCat : `pandas.core.frame.DataFrame`
1006 wcs = image.getWcs()
1007 ras = fakeCat[
'ra'].values
1008 decs = fakeCat[
'dec'].values
1009 xs, ys = wcs.skyToPixelArray(ras, decs)
1016 """Trim the fake cat to the size of the input image plus trimBuffer padding.
1018 `fakeCat` must be processed with addPixCoords before using this method.
1022 fakeCat : `pandas.core.frame.DataFrame`
1023 The catalog of fake sources to be input
1024 image : `lsst.afw.image.exposure.exposure.ExposureF`
1025 The image into which the fake sources should be added
1029 fakeCat : `pandas.core.frame.DataFrame`
1030 The original fakeCat trimmed to the area of the image
1032 wideBbox =
Box2D(image.getBBox()).dilatedBy(self.config.trimBuffer)
1036 ras = fakeCat[self.config.ra_col].values * u.rad
1037 decs = fakeCat[self.config.dec_col].values * u.rad
1039 isContainedRaDec = image.containsSkyCoords(ras, decs, padding=self.config.trimBuffer)
1042 xs = fakeCat[
"x"].values
1043 ys = fakeCat[
"y"].values
1045 isContainedXy = xs >= wideBbox.minX
1046 isContainedXy &= xs <= wideBbox.maxX
1047 isContainedXy &= ys >= wideBbox.minY
1048 isContainedXy &= ys <= wideBbox.maxY
1050 return fakeCat[isContainedRaDec & isContainedXy]
1053 """Make images of fake galaxies using GalSim.
1058 pixelScale : `float`
1059 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
1060 The PSF information to use to make the PSF images
1061 fakeCat : `pandas.core.frame.DataFrame`
1062 The catalog of fake sources to be input
1063 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
1064 Photometric calibration to be used to calibrate the fake sources
1068 galImages : `generator`
1069 A generator of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
1070 `lsst.geom.Point2D` of their locations.
1075 Fake galaxies are made by combining two sersic profiles, one for the bulge and one for the disk. Each
1076 component has an individual sersic index (n), a, b and position angle (PA). The combined profile is
1077 then convolved with the PSF at the specified x, y position on the image.
1079 The names of the columns in the ``fakeCat`` are configurable and are the column names from the
1080 University of Washington simulations database as default. For more information see the doc strings
1081 attached to the config options.
1083 See mkFakeStars doc string for an explanation of calibration to instrumental flux.
1086 self.log.info(
"Making %d fake galaxy images", len(fakeCat))
1088 for (index, row)
in fakeCat.iterrows():
1094 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1095 psfKernel = psf.computeKernelImage(xy).getArray()
1096 psfKernel /= correctedFlux
1098 except InvalidParameterError:
1099 self.log.info(
"Galaxy at %0.4f, %0.4f outside of image", row[
"x"], row[
"y"])
1103 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
1108 bulge_gs_HLR = row[
'bulge_semimajor']*np.sqrt(row[
'bulge_axis_ratio'])
1109 bulge = galsim.Sersic(n=row[
'bulge_n'], half_light_radius=bulge_gs_HLR)
1110 bulge = bulge.shear(q=row[
'bulge_axis_ratio'], beta=((90 - row[
'bulge_pa'])*galsim.degrees))
1112 disk_gs_HLR = row[
'disk_semimajor']*np.sqrt(row[
'disk_axis_ratio'])
1113 disk = galsim.Sersic(n=row[
'disk_n'], half_light_radius=disk_gs_HLR)
1114 disk = disk.shear(q=row[
'disk_axis_ratio'], beta=((90 - row[
'disk_pa'])*galsim.degrees))
1116 gal = bulge*row[
'bulge_disk_flux_ratio'] + disk
1117 gal = gal.withFlux(flux)
1119 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
1120 gal = galsim.Convolve([gal, psfIm])
1122 galIm = gal.drawImage(scale=pixelScale, method=
"real_space").array
1123 except (galsim.errors.GalSimFFTSizeError, MemoryError):
1126 yield (afwImage.ImageF(galIm), xy)
1130 """Make fake stars based off the properties in the fakeCat.
1135 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
1136 The PSF information to use to make the PSF images
1137 fakeCat : `pandas.core.frame.DataFrame`
1138 The catalog of fake sources to be input
1139 image : `lsst.afw.image.exposure.exposure.ExposureF`
1140 The image into which the fake sources should be added
1141 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
1142 Photometric calibration to be used to calibrate the fake sources
1146 starImages : `generator`
1147 A generator of tuples of `lsst.afw.image.ImageF` of fake stars and
1148 `lsst.geom.Point2D` of their locations.
1152 To take a given magnitude and translate to the number of counts in the image
1153 we use photoCalib.magnitudeToInstFlux, which returns the instrumental flux for the
1154 given calibration radius used in the photometric calibration step.
1155 Thus `calibFluxRadius` should be set to this same radius so that we can normalize
1156 the PSF model to the correct instrumental flux within calibFluxRadius.
1159 self.log.info(
"Making %d fake star images", len(fakeCat))
1161 for (index, row)
in fakeCat.iterrows():
1167 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1168 starIm = psf.computeImage(xy)
1169 starIm /= correctedFlux
1171 except InvalidParameterError:
1172 self.log.info(
"Star at %0.4f, %0.4f outside of image", row[
"x"], row[
"y"])
1176 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
1181 yield ((starIm.convertF(), xy))
1184 """Remove rows from the fakes catalog which have HLR = 0 for either the buldge or disk component,
1185 also remove galaxies that have Sersic index outside the galsim min and max
1186 allowed (0.3 <= n <= 6.2).
1190 fakeCat : `pandas.core.frame.DataFrame`
1191 The catalog of fake sources to be input
1192 starCheckVal : `str`, `bytes` or `int`
1193 The value that is set in the sourceType column to specifiy an object is a star.
1197 fakeCat : `pandas.core.frame.DataFrame`
1198 The input catalog of fake sources but with the bad objects removed
1201 rowsToKeep = (((fakeCat[
'bulge_semimajor'] != 0.0) & (fakeCat[
'disk_semimajor'] != 0.0))
1202 | (fakeCat[self.config.sourceType] == starCheckVal))
1203 numRowsNotUsed = len(fakeCat) - len(np.where(rowsToKeep)[0])
1204 self.log.info(
"Removing %d rows with HLR = 0 for either the bulge or disk", numRowsNotUsed)
1205 fakeCat = fakeCat[rowsToKeep]
1207 minN = galsim.Sersic._minimum_n
1208 maxN = galsim.Sersic._maximum_n
1209 rowsWithGoodSersic = (((fakeCat[
'bulge_n'] >= minN) & (fakeCat[
'bulge_n'] <= maxN)
1210 & (fakeCat[
'disk_n'] >= minN) & (fakeCat[
'disk_n'] <= maxN))
1211 | (fakeCat[self.config.sourceType] == starCheckVal))
1212 numRowsNotUsed = len(fakeCat) - len(np.where(rowsWithGoodSersic)[0])
1213 self.log.info(
"Removing %d rows of galaxies with nBulge or nDisk outside of %0.2f <= n <= %0.2f",
1214 numRowsNotUsed, minN, maxN)
1215 fakeCat = fakeCat[rowsWithGoodSersic]
1217 if self.config.doSubSelectSources:
1218 numRowsNotUsed = len(fakeCat) - len(fakeCat[
'select'])
1219 self.log.info(
"Removing %d rows which were not designated as template sources", numRowsNotUsed)
1220 fakeCat = fakeCat[fakeCat[
'select']]
1225 """Add the fake sources to the given image
1229 image : `lsst.afw.image.exposure.exposure.ExposureF`
1230 The image into which the fake sources should be added
1231 fakeImages : `typing.Iterator` [`tuple` ['lsst.afw.image.ImageF`, `lsst.geom.Point2d`]]
1232 An iterator of tuples that contains (or generates) images of fake sources,
1233 and the locations they are to be inserted at.
1235 The type (star/galaxy) of fake sources input
1239 image : `lsst.afw.image.exposure.exposure.ExposureF`
1243 Uses the x, y information in the ``fakeCat`` to position an image of the fake interpolated onto the
1244 pixel grid of the image. Sets the ``FAKE`` mask plane for the pixels added with the fake source.
1247 imageBBox = image.getBBox()
1248 imageMI = image.maskedImage
1250 for (fakeImage, xy)
in fakeImages:
1251 X0 = xy.getX() - fakeImage.getWidth()/2 + 0.5
1252 Y0 = xy.getY() - fakeImage.getHeight()/2 + 0.5
1253 self.log.debug(
"Adding fake source at %d, %d", xy.getX(), xy.getY())
1254 if sourceType ==
"galaxy":
1257 interpFakeImage = fakeImage
1259 interpFakeImBBox = interpFakeImage.getBBox()
1260 interpFakeImBBox.clip(imageBBox)
1262 if interpFakeImBBox.getArea() > 0:
1263 imageMIView = imageMI[interpFakeImBBox]
1264 clippedFakeImage = interpFakeImage[interpFakeImBBox]
1265 clippedFakeImageMI = afwImage.MaskedImageF(clippedFakeImage)
1266 clippedFakeImageMI.mask.set(self.bitmask)
1267 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)