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 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:
105 "Cannot compute Psf for object at {}; skipping",
111 psfArr = psf.computeKernelImage(contained_pt).array
112 apCorr = psf.computeApertureFlux(calibFluxRadius, contained_pt)
113 except InvalidParameterError:
116 "Cannot compute Psf for object at {}; skipping",
122 gsPSF = galsim.InterpolatedImage(galsim.Image(psfArr), wcs=gsWCS)
124 conv = galsim.Convolve(gsObj, gsPSF)
125 stampSize = conv.getGoodImageSize(gsWCS.minLinearScale())
126 subBounds = galsim.BoundsI(posi).withBorder(stampSize//2)
127 subBounds &= fullBounds
129 if subBounds.area() > 0:
130 subImg = gsImg[subBounds]
131 offset = posd - subBounds.true_center
148 exposure[subBox].mask.array |= bitmask
151def _isWCSGalsimDefault(wcs, hdr):
152 """Decide if wcs = galsim.PixelScale(1.0) is explicitly present in header,
153 or if it
's just the galsim default.
158 Potentially default WCS.
159 hdr : galsim.fits.FitsHeader
160 Header as read
in by galsim.
165 True if default,
False if explicitly set
in header.
167 if wcs != galsim.PixelScale(1.0):
169 if hdr.get(
'GS_WCS')
is not None:
171 if hdr.get(
'CTYPE1',
'LINEAR') ==
'LINEAR':
172 return not any(k
in hdr
for k
in [
'CD1_1',
'CDELT1'])
173 for wcs_type
in galsim.fitswcs.fits_wcs_types:
176 wcs_type._readHeader(hdr)
181 return not any(k
in hdr
for k
in [
'CD1_1',
'CDELT1'])
185 defaultTemplates={
"coaddName":
"deep",
186 "fakesType":
"fakes_"},
187 dimensions=(
"tract",
"patch",
"band",
"skymap")):
190 doc=
"Image into which fakes are to be added.",
191 name=
"{coaddName}Coadd",
192 storageClass=
"ExposureF",
193 dimensions=(
"tract",
"patch",
"band",
"skymap")
197 doc=
"Catalog of fake sources to draw inputs from.",
198 name=
"{fakesType}fakeSourceCat",
199 storageClass=
"DataFrame",
200 dimensions=(
"tract",
"skymap")
203 imageWithFakes = cT.Output(
204 doc=
"Image with fake sources added.",
205 name=
"{fakesType}{coaddName}Coadd",
206 storageClass=
"ExposureF",
207 dimensions=(
"tract",
"patch",
"band",
"skymap")
211class InsertFakesConfig(PipelineTaskConfig,
212 pipelineConnections=InsertFakesConnections):
213 """Config for inserting fake sources
218 doCleanCat = pexConfig.Field(
219 doc=
"If true removes bad sources from the catalog.",
224 fakeType = pexConfig.Field(
225 doc=
"What type of fake catalog to use, snapshot (includes variability in the magnitudes calculated "
226 "from the MJD of the image), static (no variability) or filename for a user defined fits"
232 calibFluxRadius = pexConfig.Field(
233 doc=
"Aperture radius (in pixels) that was used to define the calibration for this image+catalog. "
234 "This will be used to produce the correct instrumental fluxes within the radius. "
235 "This value should match that of the field defined in slot_CalibFlux_instFlux.",
240 coaddName = pexConfig.Field(
241 doc=
"The name of the type of coadd used",
246 doSubSelectSources = pexConfig.Field(
247 doc=
"Set to True if you wish to sub select sources to be input based on the value in the column"
248 "set in the sourceSelectionColName config option.",
253 insertImages = pexConfig.Field(
254 doc=
"Insert images directly? True or False.",
259 insertOnlyStars = pexConfig.Field(
260 doc=
"Insert only stars? True or False.",
265 doProcessAllDataIds = pexConfig.Field(
266 doc=
"If True, all input data IDs will be processed, even those containing no fake sources.",
271 trimBuffer = pexConfig.Field(
272 doc=
"Size of the pixel buffer surrounding the image. Only those fake sources with a centroid"
273 "falling within the image+buffer region will be considered for fake source injection.",
278 sourceType = pexConfig.Field(
279 doc=
"The column name for the source type used in the fake source catalog.",
281 default=
"sourceType",
284 fits_alignment = pexConfig.ChoiceField(
285 doc=
"How should injections from FITS files be aligned?",
289 "Input image will be transformed such that the local WCS in "
290 "the FITS header matches the local WCS in the target image. "
291 "I.e., North, East, and angular distances in the input image "
292 "will match North, East, and angular distances in the target "
296 "Input image will _not_ be transformed. Up, right, and pixel "
297 "distances in the input image will match up, right and pixel "
298 "distances in the target image."
306 ra_col = pexConfig.Field(
307 doc=
"Source catalog column name for RA (in radians).",
312 dec_col = pexConfig.Field(
313 doc=
"Source catalog column name for dec (in radians).",
318 bulge_semimajor_col = pexConfig.Field(
319 doc=
"Source catalog column name for the semimajor axis (in arcseconds) "
320 "of the bulge half-light ellipse.",
322 default=
"bulge_semimajor",
325 bulge_axis_ratio_col = pexConfig.Field(
326 doc=
"Source catalog column name for the axis ratio of the bulge "
327 "half-light ellipse.",
329 default=
"bulge_axis_ratio",
332 bulge_pa_col = pexConfig.Field(
333 doc=
"Source catalog column name for the position angle (measured from "
334 "North through East in degrees) of the semimajor axis of the bulge "
335 "half-light ellipse.",
340 bulge_n_col = pexConfig.Field(
341 doc=
"Source catalog column name for the Sersic index of the bulge.",
346 disk_semimajor_col = pexConfig.Field(
347 doc=
"Source catalog column name for the semimajor axis (in arcseconds) "
348 "of the disk half-light ellipse.",
350 default=
"disk_semimajor",
353 disk_axis_ratio_col = pexConfig.Field(
354 doc=
"Source catalog column name for the axis ratio of the disk "
355 "half-light ellipse.",
357 default=
"disk_axis_ratio",
360 disk_pa_col = pexConfig.Field(
361 doc=
"Source catalog column name for the position angle (measured from "
362 "North through East in degrees) of the semimajor axis of the disk "
363 "half-light ellipse.",
368 disk_n_col = pexConfig.Field(
369 doc=
"Source catalog column name for the Sersic index of the disk.",
374 bulge_disk_flux_ratio_col = pexConfig.Field(
375 doc=
"Source catalog column name for the bulge/disk flux ratio.",
377 default=
"bulge_disk_flux_ratio",
380 mag_col = pexConfig.Field(
381 doc=
"Source catalog column name template for magnitudes, in the format "
382 "``filter name``_mag_col. E.g., if this config variable is set to "
383 "``%s_mag``, then the i-band magnitude will be searched for in the "
384 "``i_mag`` column of the source catalog.",
389 select_col = pexConfig.Field(
390 doc=
"Source catalog column name to be used to select which sources to "
396 length_col = pexConfig.Field(
397 doc=
"Source catalog column name for trail length (in pixels).",
399 default=
"trail_length",
402 angle_col = pexConfig.Field(
403 doc=
"Source catalog column name for trail angle (in radians).",
405 default=
"trail_angle",
410 raColName = pexConfig.Field(
411 doc=
"RA column name used in the fake source catalog.",
414 deprecated=
"Use `ra_col` instead."
417 decColName = pexConfig.Field(
418 doc=
"Dec. column name used in the fake source catalog.",
421 deprecated=
"Use `dec_col` instead."
424 diskHLR = pexConfig.Field(
425 doc=
"Column name for the disk half light radius used in the fake source catalog.",
427 default=
"DiskHalfLightRadius",
429 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
430 " to specify disk half-light ellipse."
434 aDisk = pexConfig.Field(
435 doc=
"The column name for the semi major axis length of the disk component used in the fake source"
440 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
441 " to specify disk half-light ellipse."
445 bDisk = pexConfig.Field(
446 doc=
"The column name for the semi minor axis length of the disk component.",
450 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
451 " to specify disk half-light ellipse."
455 paDisk = pexConfig.Field(
456 doc=
"The column name for the PA of the disk component used in the fake source catalog.",
460 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
461 " to specify disk half-light ellipse."
465 nDisk = pexConfig.Field(
466 doc=
"The column name for the sersic index of the disk component used in the fake source catalog.",
469 deprecated=
"Use `disk_n_col` instead."
472 bulgeHLR = pexConfig.Field(
473 doc=
"Column name for the bulge half light radius used in the fake source catalog.",
475 default=
"BulgeHalfLightRadius",
477 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
478 "`bulge_pa_col` to specify disk half-light ellipse."
482 aBulge = pexConfig.Field(
483 doc=
"The column name for the semi major axis length of the bulge component.",
487 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
488 "`bulge_pa_col` to specify disk half-light ellipse."
492 bBulge = pexConfig.Field(
493 doc=
"The column name for the semi minor axis length of the bulge component used in the fake source "
498 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
499 "`bulge_pa_col` to specify disk half-light ellipse."
503 paBulge = pexConfig.Field(
504 doc=
"The column name for the PA of the bulge component used in the fake source catalog.",
508 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
509 "`bulge_pa_col` to specify disk half-light ellipse."
513 nBulge = pexConfig.Field(
514 doc=
"The column name for the sersic index of the bulge component used in the fake source catalog.",
517 deprecated=
"Use `bulge_n_col` instead."
520 magVar = pexConfig.Field(
521 doc=
"The column name for the magnitude calculated taking variability into account. In the format "
522 "``filter name``magVar, e.g. imagVar for the magnitude in the i band.",
525 deprecated=
"Use `mag_col` instead."
528 sourceSelectionColName = pexConfig.Field(
529 doc=
"The name of the column in the input fakes catalogue to be used to determine which sources to"
530 "add, default is none and when this is used all sources are added.",
532 default=
"templateSource",
533 deprecated=
"Use `select_col` instead."
537class InsertFakesTask(PipelineTask):
538 """Insert fake objects into images.
540 Add fake stars and galaxies to the given image, read
in through the dataRef. Galaxy parameters are read
in
541 from the specified file
and then modelled using galsim.
543 `InsertFakesTask` has five functions that make images of the fake sources
and then add them to the
547 Use the WCS information to add the pixel coordinates of each source.
548 `mkFakeGalsimGalaxies`
549 Use Galsim to make fake double sersic galaxies
for each set of galaxy parameters
in the input file.
551 Use the PSF information
from the image to make a fake star using the magnitude information
from the
554 Remove rows of the input fake catalog which have half light radius, of either the bulge
or the disk,
555 that are 0. Also removes rows that have Sersic index outside of galsim
's allowed paramters. If
556 the config option sourceSelectionColName is set then this function limits the catalog of input fakes
557 to only those which are
True in this column.
559 Add the fake sources to the image.
563 _DefaultName = "insertFakes"
564 ConfigClass = InsertFakesConfig
566 def runQuantum(self, butlerQC, inputRefs, outputRefs):
567 inputs = butlerQC.get(inputRefs)
568 inputs[
"wcs"] = inputs[
"image"].getWcs()
569 inputs[
"photoCalib"] = inputs[
"image"].getPhotoCalib()
571 outputs = self.run(**inputs)
572 butlerQC.put(outputs, outputRefs)
574 def run(self, fakeCat, image, wcs, photoCalib):
575 """Add fake sources to an image.
579 fakeCat : `pandas.core.frame.DataFrame`
580 The catalog of fake sources to be input
581 image : `lsst.afw.image.exposure.exposure.ExposureF`
582 The image into which the fake sources should be added
584 WCS to use to add fake sources
585 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
586 Photometric calibration to be used to calibrate the fake sources
590 resultStruct : `lsst.pipe.base.struct.Struct`
591 contains : image : `lsst.afw.image.exposure.exposure.ExposureF`
595 Adds pixel coordinates for each source to the fakeCat
and removes objects
with bulge
or disk half
596 light radius = 0 (
if ``config.doCleanCat =
True``).
598 Adds the ``Fake`` mask plane to the image which
is then set by `addFakeSources` to mark where fake
599 sources have been added. Uses the information
in the ``fakeCat`` to make fake galaxies (using galsim)
600 and fake stars, using the PSF models
from the PSF information
for the image. These are then added to
601 the image
and the image
with fakes included returned.
603 The galsim galaxies are made using a double sersic profile, one
for the bulge
and one
for the disk,
604 this
is then convolved
with the PSF at that point.
608 origWcs = image.getWcs()
609 origPhotoCalib = image.getPhotoCalib()
611 image.setPhotoCalib(photoCalib)
613 band = image.getFilter().bandLabel
614 fakeCat = self._standardizeColumns(fakeCat, band)
616 fakeCat = self.addPixCoords(fakeCat, image)
617 fakeCat = self.trimFakeCat(fakeCat, image)
620 if not self.config.insertImages:
621 if isinstance(fakeCat[self.config.sourceType].iloc[0], str):
622 galCheckVal =
"galaxy"
623 starCheckVal =
"star"
624 trailCheckVal =
"trail"
625 elif isinstance(fakeCat[self.config.sourceType].iloc[0], bytes):
626 galCheckVal = b
"galaxy"
627 starCheckVal = b
"star"
628 trailCheckVal = b
"trail"
629 elif isinstance(fakeCat[self.config.sourceType].iloc[0], (int, float)):
635 "sourceType column does not have required type, should be str, bytes or int"
637 if self.config.doCleanCat:
638 fakeCat = self.cleanCat(fakeCat, starCheckVal)
640 generator = self._generateGSObjectsFromCatalog(image, fakeCat, galCheckVal, starCheckVal,
643 generator = self._generateGSObjectsFromImages(image, fakeCat)
644 _add_fake_sources(image, generator, calibFluxRadius=self.config.calibFluxRadius, logger=self.log)
645 elif len(fakeCat) == 0
and self.config.doProcessAllDataIds:
646 self.log.warning(
"No fakes found for this dataRef; processing anyway.")
647 image.mask.addMaskPlane(
"FAKE")
649 raise RuntimeError(
"No fakes found for this dataRef.")
652 image.setWcs(origWcs)
653 image.setPhotoCalib(origPhotoCalib)
655 resultStruct = pipeBase.Struct(imageWithFakes=image)
659 def _standardizeColumns(self, fakeCat, band):
660 """Use config variables to 'standardize' the expected columns and column
661 names in the input catalog.
665 fakeCat : `pandas.core.frame.DataFrame`
666 The catalog of fake sources to be input
668 Label
for the current band being processed.
672 outCat : `pandas.core.frame.DataFrame`
673 The standardized catalog of fake sources
678 def add_to_replace_dict(new_name, depr_name, std_name):
679 if new_name
in fakeCat.columns:
680 replace_dict[new_name] = std_name
681 elif depr_name
in fakeCat.columns:
682 replace_dict[depr_name] = std_name
684 raise ValueError(f
"Could not determine column for {std_name}.")
688 for new_name, depr_name, std_name
in [
689 (cfg.ra_col, cfg.raColName,
'ra'),
690 (cfg.dec_col, cfg.decColName,
'dec'),
691 (cfg.mag_col%band, cfg.magVar%band,
'mag')
693 add_to_replace_dict(new_name, depr_name, std_name)
695 if not cfg.insertImages
and not cfg.insertOnlyStars:
696 for new_name, depr_name, std_name
in [
697 (cfg.bulge_n_col, cfg.nBulge,
'bulge_n'),
698 (cfg.bulge_pa_col, cfg.paBulge,
'bulge_pa'),
699 (cfg.disk_n_col, cfg.nDisk,
'disk_n'),
700 (cfg.disk_pa_col, cfg.paDisk,
'disk_pa'),
702 add_to_replace_dict(new_name, depr_name, std_name)
704 if cfg.doSubSelectSources:
707 cfg.sourceSelectionColName,
710 fakeCat = fakeCat.rename(columns=replace_dict, copy=
False)
715 if not cfg.insertImages
and not cfg.insertOnlyStars:
717 cfg.bulge_semimajor_col
in fakeCat.columns
718 and cfg.bulge_axis_ratio_col
in fakeCat.columns
720 fakeCat = fakeCat.rename(
722 cfg.bulge_semimajor_col:
'bulge_semimajor',
723 cfg.bulge_axis_ratio_col:
'bulge_axis_ratio',
724 cfg.disk_semimajor_col:
'disk_semimajor',
725 cfg.disk_axis_ratio_col:
'disk_axis_ratio',
730 cfg.bulgeHLR
in fakeCat.columns
731 and cfg.aBulge
in fakeCat.columns
732 and cfg.bBulge
in fakeCat.columns
734 fakeCat[
'bulge_axis_ratio'] = (
735 fakeCat[cfg.bBulge]/fakeCat[cfg.aBulge]
737 fakeCat[
'bulge_semimajor'] = (
738 fakeCat[cfg.bulgeHLR]/np.sqrt(fakeCat[
'bulge_axis_ratio'])
740 fakeCat[
'disk_axis_ratio'] = (
741 fakeCat[cfg.bDisk]/fakeCat[cfg.aDisk]
743 fakeCat[
'disk_semimajor'] = (
744 fakeCat[cfg.diskHLR]/np.sqrt(fakeCat[
'disk_axis_ratio'])
748 "Could not determine columns for half-light radius and "
753 if cfg.bulge_disk_flux_ratio_col
in fakeCat.columns:
754 fakeCat = fakeCat.rename(
756 cfg.bulge_disk_flux_ratio_col:
'bulge_disk_flux_ratio'
761 fakeCat[
'bulge_disk_flux_ratio'] = 1.0
765 def _generateGSObjectsFromCatalog(self, exposure, fakeCat, galCheckVal, starCheckVal, trailCheckVal):
766 """Process catalog to generate `galsim.GSObject` s.
770 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
771 The exposure into which the fake sources should be added
772 fakeCat : `pandas.core.frame.DataFrame`
773 The catalog of fake sources to be input
774 galCheckVal : `str`, `bytes` or `int`
775 The value that
is set
in the sourceType column to specify an object
is a galaxy.
776 starCheckVal : `str`, `bytes`
or `int`
777 The value that
is set
in the sourceType column to specify an object
is a star.
778 trailCheckVal : `str`, `bytes`
or `int`
779 The value that
is set
in the sourceType column to specify an object
is a star
783 gsObjects : `generator`
786 wcs = exposure.getWcs()
787 photoCalib = exposure.getPhotoCalib()
789 self.log.info("Making %d objects for insertion", len(fakeCat))
791 for (index, row)
in fakeCat.iterrows():
795 xy = wcs.skyToPixel(skyCoord)
798 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
802 sourceType = row[self.config.sourceType]
803 if sourceType == galCheckVal:
805 bulge_gs_HLR = row[
'bulge_semimajor']*np.sqrt(row[
'bulge_axis_ratio'])
806 bulge = galsim.Sersic(n=row[
'bulge_n'], half_light_radius=bulge_gs_HLR)
807 bulge = bulge.shear(q=row[
'bulge_axis_ratio'], beta=((90 - row[
'bulge_pa'])*galsim.degrees))
809 disk_gs_HLR = row[
'disk_semimajor']*np.sqrt(row[
'disk_axis_ratio'])
810 disk = galsim.Sersic(n=row[
'disk_n'], half_light_radius=disk_gs_HLR)
811 disk = disk.shear(q=row[
'disk_axis_ratio'], beta=((90 - row[
'disk_pa'])*galsim.degrees))
813 gal = bulge*row[
'bulge_disk_flux_ratio'] + disk
814 gal = gal.withFlux(flux)
817 elif sourceType == starCheckVal:
818 star = galsim.DeltaFunction()
819 star = star.withFlux(flux)
821 elif sourceType == trailCheckVal:
822 length = row[
'trail_length']
823 angle = row[
'trail_angle']
827 theta = galsim.Angle(angle*galsim.radians)
828 trail = galsim.Box(length, thickness)
829 trail = trail.rotate(theta)
830 trail = trail.withFlux(flux*length)
835 mat = wcs.linearizePixelToSky(skyCoord, geom.arcseconds).getMatrix()
836 trail = trail.transform(mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1])
838 yield skyCoord, trail
840 raise TypeError(f
"Unknown sourceType {sourceType}")
842 def _generateGSObjectsFromImages(self, exposure, fakeCat):
843 """Process catalog to generate `galsim.GSObject` s.
847 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
848 The exposure into which the fake sources should be added
849 fakeCat : `pandas.core.frame.DataFrame`
850 The catalog of fake sources to be input
854 gsObjects : `generator`
857 band = exposure.getFilter().bandLabel
858 wcs = exposure.getWcs()
859 photoCalib = exposure.getPhotoCalib()
861 self.log.info("Processing %d fake images", len(fakeCat))
863 for (index, row)
in fakeCat.iterrows():
867 xy = wcs.skyToPixel(skyCoord)
870 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
874 imFile = row[band+
"imFilename"]
876 imFile = imFile.decode(
"utf-8")
877 except AttributeError:
879 imFile = imFile.strip()
880 im = galsim.fits.read(imFile, read_header=
True)
882 if self.config.fits_alignment ==
"wcs":
889 if _isWCSGalsimDefault(im.wcs, im.header):
891 f
"Cannot find WCS in input FITS file {imFile}"
893 elif self.config.fits_alignment ==
"pixel":
896 linWcs = wcs.linearizePixelToSky(skyCoord, geom.arcseconds)
897 mat = linWcs.getMatrix()
898 im.wcs = galsim.JacobianWCS(
899 mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1]
903 f
"Unknown fits_alignment type {self.config.fits_alignment}"
906 obj = galsim.InterpolatedImage(im, calculate_stepk=
False)
907 obj = obj.withFlux(flux)
911 """Process images from files into the format needed for insertion.
915 fakeCat : `pandas.core.frame.DataFrame`
916 The catalog of fake sources to be input
917 wcs : `lsst.afw.geom.skyWcs.skyWcs.SkyWc`
918 WCS to use to add fake sources
919 psf : `lsst.meas.algorithms.coaddPsf.coaddPsf.CoaddPsf` or
920 `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
921 The PSF information to use to make the PSF images
922 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
923 Photometric calibration to be used to calibrate the fake sources
925 The filter band that the observation was taken
in.
927 The pixel scale of the image the sources are to be added to.
932 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF`
and
934 For sources labelled
as galaxy.
936 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF`
and
938 For sources labelled
as star.
942 The input fakes catalog needs to contain the absolute path to the image
in the
943 band that
is being used to add images to. It also needs to have the R.A.
and
944 declination of the fake source
in radians
and the sourceType of the object.
949 self.log.info("Processing %d fake images", len(fakeCat))
951 for (imFile, sourceType, mag, x, y)
in zip(fakeCat[band +
"imFilename"].array,
952 fakeCat[
"sourceType"].array,
953 fakeCat[
'mag'].array,
954 fakeCat[
"x"].array, fakeCat[
"y"].array):
956 im = afwImage.ImageF.readFits(imFile)
963 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
964 psfKernel = psf.computeKernelImage(xy).getArray()
965 psfKernel /= correctedFlux
967 except InvalidParameterError:
968 self.log.info(
"%s at %0.4f, %0.4f outside of image", sourceType, x, y)
971 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
972 galsimIm = galsim.InterpolatedImage(galsim.Image(im.array), scale=pixelScale)
973 convIm = galsim.Convolve([galsimIm, psfIm])
976 outIm = convIm.drawImage(scale=pixelScale, method=
"real_space").array
977 except (galsim.errors.GalSimFFTSizeError, MemoryError):
980 imSum = np.sum(outIm)
984 flux = photoCalib.magnitudeToInstFlux(mag, xy)
988 imWithFlux = flux*divIm
990 if sourceType == b
"galaxy":
991 galImages.append((afwImage.ImageF(imWithFlux), xy))
992 if sourceType == b
"star":
993 starImages.append((afwImage.ImageF(imWithFlux), xy))
995 return galImages, starImages
999 """Add pixel coordinates to the catalog of fakes.
1003 fakeCat : `pandas.core.frame.DataFrame`
1004 The catalog of fake sources to be input
1005 image : `lsst.afw.image.exposure.exposure.ExposureF`
1006 The image into which the fake sources should be added
1010 fakeCat : `pandas.core.frame.DataFrame`
1012 wcs = image.getWcs()
1013 ras = fakeCat['ra'].values
1014 decs = fakeCat[
'dec'].values
1015 xs, ys = wcs.skyToPixelArray(ras, decs)
1022 """Trim the fake cat to the size of the input image plus trimBuffer padding.
1024 `fakeCat` must be processed with addPixCoords before using this method.
1028 fakeCat : `pandas.core.frame.DataFrame`
1029 The catalog of fake sources to be input
1030 image : `lsst.afw.image.exposure.exposure.ExposureF`
1031 The image into which the fake sources should be added
1035 fakeCat : `pandas.core.frame.DataFrame`
1036 The original fakeCat trimmed to the area of the image
1038 wideBbox = Box2D(image.getBBox()).dilatedBy(self.config.trimBuffer)
1042 ras = fakeCat[self.config.ra_col].values * u.rad
1043 decs = fakeCat[self.config.dec_col].values * u.rad
1045 isContainedRaDec = image.containsSkyCoords(ras, decs, padding=self.config.trimBuffer)
1048 xs = fakeCat[
"x"].values
1049 ys = fakeCat[
"y"].values
1051 isContainedXy = xs >= wideBbox.minX
1052 isContainedXy &= xs <= wideBbox.maxX
1053 isContainedXy &= ys >= wideBbox.minY
1054 isContainedXy &= ys <= wideBbox.maxY
1056 return fakeCat[isContainedRaDec & isContainedXy]
1059 """Make images of fake galaxies using GalSim.
1064 pixelScale : `float`
1065 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
1066 The PSF information to use to make the PSF images
1067 fakeCat : `pandas.core.frame.DataFrame`
1068 The catalog of fake sources to be input
1069 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
1070 Photometric calibration to be used to calibrate the fake sources
1074 galImages : `generator`
1075 A generator of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
1081 Fake galaxies are made by combining two sersic profiles, one
for the bulge
and one
for the disk. Each
1082 component has an individual sersic index (n), a, b
and position angle (PA). The combined profile
is
1083 then convolved
with the PSF at the specified x, y position on the image.
1085 The names of the columns
in the ``fakeCat`` are configurable
and are the column names
from the
1086 University of Washington simulations database
as default. For more information see the doc strings
1087 attached to the config options.
1089 See mkFakeStars doc string
for an explanation of calibration to instrumental flux.
1092 self.log.info("Making %d fake galaxy images", len(fakeCat))
1094 for (index, row)
in fakeCat.iterrows():
1100 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1101 psfKernel = psf.computeKernelImage(xy).getArray()
1102 psfKernel /= correctedFlux
1104 except InvalidParameterError:
1105 self.log.info(
"Galaxy at %0.4f, %0.4f outside of image", row[
"x"], row[
"y"])
1109 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
1114 bulge_gs_HLR = row[
'bulge_semimajor']*np.sqrt(row[
'bulge_axis_ratio'])
1115 bulge = galsim.Sersic(n=row[
'bulge_n'], half_light_radius=bulge_gs_HLR)
1116 bulge = bulge.shear(q=row[
'bulge_axis_ratio'], beta=((90 - row[
'bulge_pa'])*galsim.degrees))
1118 disk_gs_HLR = row[
'disk_semimajor']*np.sqrt(row[
'disk_axis_ratio'])
1119 disk = galsim.Sersic(n=row[
'disk_n'], half_light_radius=disk_gs_HLR)
1120 disk = disk.shear(q=row[
'disk_axis_ratio'], beta=((90 - row[
'disk_pa'])*galsim.degrees))
1122 gal = bulge*row[
'bulge_disk_flux_ratio'] + disk
1123 gal = gal.withFlux(flux)
1125 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
1126 gal = galsim.Convolve([gal, psfIm])
1128 galIm = gal.drawImage(scale=pixelScale, method=
"real_space").array
1129 except (galsim.errors.GalSimFFTSizeError, MemoryError):
1132 yield (afwImage.ImageF(galIm), xy)
1136 """Make fake stars based off the properties in the fakeCat.
1141 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
1142 The PSF information to use to make the PSF images
1143 fakeCat : `pandas.core.frame.DataFrame`
1144 The catalog of fake sources to be input
1145 image : `lsst.afw.image.exposure.exposure.ExposureF`
1146 The image into which the fake sources should be added
1147 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
1148 Photometric calibration to be used to calibrate the fake sources
1152 starImages : `generator`
1153 A generator of tuples of `lsst.afw.image.ImageF` of fake stars and
1158 To take a given magnitude
and translate to the number of counts
in the image
1159 we use photoCalib.magnitudeToInstFlux, which returns the instrumental flux
for the
1160 given calibration radius used
in the photometric calibration step.
1161 Thus `calibFluxRadius` should be set to this same radius so that we can normalize
1162 the PSF model to the correct instrumental flux within calibFluxRadius.
1165 self.log.info("Making %d fake star images", len(fakeCat))
1167 for (index, row)
in fakeCat.iterrows():
1173 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1174 starIm = psf.computeImage(xy)
1175 starIm /= correctedFlux
1177 except InvalidParameterError:
1178 self.log.info(
"Star at %0.4f, %0.4f outside of image", row[
"x"], row[
"y"])
1182 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
1187 yield ((starIm.convertF(), xy))
1190 """Remove rows from the fakes catalog which have HLR = 0 for either the buldge or disk component,
1191 also remove galaxies that have Sersic index outside the galsim min and max
1192 allowed (0.3 <= n <= 6.2).
1196 fakeCat : `pandas.core.frame.DataFrame`
1197 The catalog of fake sources to be input
1198 starCheckVal : `str`, `bytes`
or `int`
1199 The value that
is set
in the sourceType column to specifiy an object
is a star.
1203 fakeCat : `pandas.core.frame.DataFrame`
1204 The input catalog of fake sources but
with the bad objects removed
1207 rowsToKeep = (((fakeCat['bulge_semimajor'] != 0.0) & (fakeCat[
'disk_semimajor'] != 0.0))
1208 | (fakeCat[self.config.sourceType] == starCheckVal))
1209 numRowsNotUsed = len(fakeCat) - len(np.where(rowsToKeep)[0])
1210 self.log.info(
"Removing %d rows with HLR = 0 for either the bulge or disk", numRowsNotUsed)
1211 fakeCat = fakeCat[rowsToKeep]
1213 minN = galsim.Sersic._minimum_n
1214 maxN = galsim.Sersic._maximum_n
1215 rowsWithGoodSersic = (((fakeCat[
'bulge_n'] >= minN) & (fakeCat[
'bulge_n'] <= maxN)
1216 & (fakeCat[
'disk_n'] >= minN) & (fakeCat[
'disk_n'] <= maxN))
1217 | (fakeCat[self.config.sourceType] == starCheckVal))
1218 numRowsNotUsed = len(fakeCat) - len(np.where(rowsWithGoodSersic)[0])
1219 self.log.info(
"Removing %d rows of galaxies with nBulge or nDisk outside of %0.2f <= n <= %0.2f",
1220 numRowsNotUsed, minN, maxN)
1221 fakeCat = fakeCat[rowsWithGoodSersic]
1223 if self.config.doSubSelectSources:
1224 numRowsNotUsed = len(fakeCat) - len(fakeCat[
'select'])
1225 self.log.info(
"Removing %d rows which were not designated as template sources", numRowsNotUsed)
1226 fakeCat = fakeCat[fakeCat[
'select']]
1231 """Add the fake sources to the given image
1235 image : `lsst.afw.image.exposure.exposure.ExposureF`
1236 The image into which the fake sources should be added
1237 fakeImages : `typing.Iterator` [`tuple` ['lsst.afw.image.ImageF`, `lsst.geom.Point2d`]]
1238 An iterator of tuples that contains (or generates) images of fake sources,
1239 and the locations they are to be inserted at.
1241 The type (star/galaxy) of fake sources input
1245 image : `lsst.afw.image.exposure.exposure.ExposureF`
1249 Uses the x, y information
in the ``fakeCat`` to position an image of the fake interpolated onto the
1250 pixel grid of the image. Sets the ``FAKE`` mask plane
for the pixels added
with the fake source.
1253 imageBBox = image.getBBox()
1254 imageMI = image.maskedImage
1256 for (fakeImage, xy)
in fakeImages:
1257 X0 = xy.getX() - fakeImage.getWidth()/2 + 0.5
1258 Y0 = xy.getY() - fakeImage.getHeight()/2 + 0.5
1259 self.log.debug(
"Adding fake source at %d, %d", xy.getX(), xy.getY())
1260 if sourceType ==
"galaxy":
1263 interpFakeImage = fakeImage
1265 interpFakeImBBox = interpFakeImage.getBBox()
1266 interpFakeImBBox.clip(imageBBox)
1268 if interpFakeImBBox.getArea() > 0:
1269 imageMIView = imageMI[interpFakeImBBox]
1270 clippedFakeImage = interpFakeImage[interpFakeImBBox]
1271 clippedFakeImageMI = afwImage.MaskedImageF(clippedFakeImage)
1272 clippedFakeImageMI.mask.set(self.bitmask)
1273 imageMIView += clippedFakeImageMI
A 2-dimensional celestial WCS that transform pixels to ICRS RA/Dec, using the LSST standard for pixel...
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.
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)