23Insert fakes into deepCoadds
27from astropy
import units
as u
35from lsst.pipe.base import PipelineTask, PipelineTaskConfig, PipelineTaskConnections
36import lsst.pipe.base.connectionTypes
as cT
38from lsst.geom import SpherePoint, radians, Box2D, Point2D
40__all__ = [
"InsertFakesConfig",
"InsertFakesTask"]
43def _add_fake_sources(exposure, objects, calibFluxRadius=12.0, logger=None):
44 """Add fake sources to the given exposure
48 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
49 The exposure into which the fake sources should be added
50 objects : `typing.Iterator` [`tuple` ['lsst.geom.SpherePoint`, `galsim.GSObject`]]
51 An iterator of tuples that contains (or generates) locations
and object
52 surface brightness profiles to inject.
53 calibFluxRadius : `float`, optional
54 Aperture radius (
in pixels) used to define the calibration
for this
55 exposure+catalog. This
is used to produce the correct instrumental fluxes
56 within the radius. The value should match that of the field defined
in
57 slot_CalibFlux_instFlux.
58 logger : `lsst.log.log.log.Log`
or `logging.Logger`, optional
61 exposure.mask.addMaskPlane("FAKE")
62 bitmask = exposure.mask.getPlaneBitMask(
"FAKE")
64 logger.info(f
"Adding mask plane with bitmask {bitmask}")
66 wcs = exposure.getWcs()
67 psf = exposure.getPsf()
69 bbox = exposure.getBBox()
70 fullBounds = galsim.BoundsI(bbox.minX, bbox.maxX, bbox.minY, bbox.maxY)
71 gsImg = galsim.Image(exposure.image.array, bounds=fullBounds)
73 pixScale = wcs.getPixelScale(bbox.getCenter()).asArcseconds()
75 for spt, gsObj
in objects:
76 pt = wcs.skyToPixel(spt)
77 posd = galsim.PositionD(pt.x, pt.y)
78 posi = galsim.PositionI(pt.x//1, pt.y//1)
80 logger.debug(f
"Adding fake source at {pt}")
82 mat = wcs.linearizePixelToSky(spt, geom.arcseconds).getMatrix()
83 gsWCS = galsim.JacobianWCS(mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1])
88 gsPixScale = np.sqrt(gsWCS.pixelArea())
89 if gsPixScale < pixScale/2
or gsPixScale > pixScale*2:
93 psfArr = psf.computeKernelImage(pt).array
94 except InvalidParameterError:
96 contained_pt = Point2D(
97 np.clip(pt.x, bbox.minX, bbox.maxX),
98 np.clip(pt.y, bbox.minY, bbox.maxY)
100 if pt == contained_pt:
103 "Cannot compute Psf for object at {}; skipping",
109 psfArr = psf.computeKernelImage(contained_pt).array
110 except InvalidParameterError:
113 "Cannot compute Psf for object at {}; skipping",
118 apCorr = psf.computeApertureFlux(calibFluxRadius)
120 gsPSF = galsim.InterpolatedImage(galsim.Image(psfArr), wcs=gsWCS)
122 conv = galsim.Convolve(gsObj, gsPSF)
123 stampSize = conv.getGoodImageSize(gsWCS.minLinearScale())
124 subBounds = galsim.BoundsI(posi).withBorder(stampSize//2)
125 subBounds &= fullBounds
127 if subBounds.area() > 0:
128 subImg = gsImg[subBounds]
129 offset = posd - subBounds.true_center
146 exposure[subBox].mask.array |= bitmask
149def _isWCSGalsimDefault(wcs, hdr):
150 """Decide if wcs = galsim.PixelScale(1.0) is explicitly present in header,
151 or if it
's just the galsim default.
156 Potentially default WCS.
157 hdr : galsim.fits.FitsHeader
158 Header as read
in by galsim.
163 True if default,
False if explicitly set
in header.
165 if wcs != galsim.PixelScale(1.0):
167 if hdr.get(
'GS_WCS')
is not None:
169 if hdr.get(
'CTYPE1',
'LINEAR') ==
'LINEAR':
170 return not any(k
in hdr
for k
in [
'CD1_1',
'CDELT1'])
171 for wcs_type
in galsim.fitswcs.fits_wcs_types:
174 wcs_type._readHeader(hdr)
179 return not any(k
in hdr
for k
in [
'CD1_1',
'CDELT1'])
183 defaultTemplates={
"coaddName":
"deep",
184 "fakesType":
"fakes_"},
185 dimensions=(
"tract",
"patch",
"band",
"skymap")):
188 doc=
"Image into which fakes are to be added.",
189 name=
"{coaddName}Coadd",
190 storageClass=
"ExposureF",
191 dimensions=(
"tract",
"patch",
"band",
"skymap")
195 doc=
"Catalog of fake sources to draw inputs from.",
196 name=
"{fakesType}fakeSourceCat",
197 storageClass=
"DataFrame",
198 dimensions=(
"tract",
"skymap")
201 imageWithFakes = cT.Output(
202 doc=
"Image with fake sources added.",
203 name=
"{fakesType}{coaddName}Coadd",
204 storageClass=
"ExposureF",
205 dimensions=(
"tract",
"patch",
"band",
"skymap")
209class InsertFakesConfig(PipelineTaskConfig,
210 pipelineConnections=InsertFakesConnections):
211 """Config for inserting fake sources
216 doCleanCat = pexConfig.Field(
217 doc=
"If true removes bad sources from the catalog.",
222 fakeType = pexConfig.Field(
223 doc=
"What type of fake catalog to use, snapshot (includes variability in the magnitudes calculated "
224 "from the MJD of the image), static (no variability) or filename for a user defined fits"
230 calibFluxRadius = pexConfig.Field(
231 doc=
"Aperture radius (in pixels) that was used to define the calibration for this image+catalog. "
232 "This will be used to produce the correct instrumental fluxes within the radius. "
233 "This value should match that of the field defined in slot_CalibFlux_instFlux.",
238 coaddName = pexConfig.Field(
239 doc=
"The name of the type of coadd used",
244 doSubSelectSources = pexConfig.Field(
245 doc=
"Set to True if you wish to sub select sources to be input based on the value in the column"
246 "set in the sourceSelectionColName config option.",
251 insertImages = pexConfig.Field(
252 doc=
"Insert images directly? True or False.",
257 insertOnlyStars = pexConfig.Field(
258 doc=
"Insert only stars? True or False.",
263 doProcessAllDataIds = pexConfig.Field(
264 doc=
"If True, all input data IDs will be processed, even those containing no fake sources.",
269 trimBuffer = pexConfig.Field(
270 doc=
"Size of the pixel buffer surrounding the image. Only those fake sources with a centroid"
271 "falling within the image+buffer region will be considered for fake source injection.",
276 sourceType = pexConfig.Field(
277 doc=
"The column name for the source type used in the fake source catalog.",
279 default=
"sourceType",
282 fits_alignment = pexConfig.ChoiceField(
283 doc=
"How should injections from FITS files be aligned?",
287 "Input image will be transformed such that the local WCS in "
288 "the FITS header matches the local WCS in the target image. "
289 "I.e., North, East, and angular distances in the input image "
290 "will match North, East, and angular distances in the target "
294 "Input image will _not_ be transformed. Up, right, and pixel "
295 "distances in the input image will match up, right and pixel "
296 "distances in the target image."
304 ra_col = pexConfig.Field(
305 doc=
"Source catalog column name for RA (in radians).",
310 dec_col = pexConfig.Field(
311 doc=
"Source catalog column name for dec (in radians).",
316 bulge_semimajor_col = pexConfig.Field(
317 doc=
"Source catalog column name for the semimajor axis (in arcseconds) "
318 "of the bulge half-light ellipse.",
320 default=
"bulge_semimajor",
323 bulge_axis_ratio_col = pexConfig.Field(
324 doc=
"Source catalog column name for the axis ratio of the bulge "
325 "half-light ellipse.",
327 default=
"bulge_axis_ratio",
330 bulge_pa_col = pexConfig.Field(
331 doc=
"Source catalog column name for the position angle (measured from "
332 "North through East in degrees) of the semimajor axis of the bulge "
333 "half-light ellipse.",
338 bulge_n_col = pexConfig.Field(
339 doc=
"Source catalog column name for the Sersic index of the bulge.",
344 disk_semimajor_col = pexConfig.Field(
345 doc=
"Source catalog column name for the semimajor axis (in arcseconds) "
346 "of the disk half-light ellipse.",
348 default=
"disk_semimajor",
351 disk_axis_ratio_col = pexConfig.Field(
352 doc=
"Source catalog column name for the axis ratio of the disk "
353 "half-light ellipse.",
355 default=
"disk_axis_ratio",
358 disk_pa_col = pexConfig.Field(
359 doc=
"Source catalog column name for the position angle (measured from "
360 "North through East in degrees) of the semimajor axis of the disk "
361 "half-light ellipse.",
366 disk_n_col = pexConfig.Field(
367 doc=
"Source catalog column name for the Sersic index of the disk.",
372 bulge_disk_flux_ratio_col = pexConfig.Field(
373 doc=
"Source catalog column name for the bulge/disk flux ratio.",
375 default=
"bulge_disk_flux_ratio",
378 mag_col = pexConfig.Field(
379 doc=
"Source catalog column name template for magnitudes, in the format "
380 "``filter name``_mag_col. E.g., if this config variable is set to "
381 "``%s_mag``, then the i-band magnitude will be searched for in the "
382 "``i_mag`` column of the source catalog.",
387 select_col = pexConfig.Field(
388 doc=
"Source catalog column name to be used to select which sources to "
394 length_col = pexConfig.Field(
395 doc=
"Source catalog column name for trail length (in pixels).",
397 default=
"trail_length",
400 angle_col = pexConfig.Field(
401 doc=
"Source catalog column name for trail angle (in radians).",
403 default=
"trail_angle",
408 raColName = pexConfig.Field(
409 doc=
"RA column name used in the fake source catalog.",
412 deprecated=
"Use `ra_col` instead."
415 decColName = pexConfig.Field(
416 doc=
"Dec. column name used in the fake source catalog.",
419 deprecated=
"Use `dec_col` instead."
422 diskHLR = pexConfig.Field(
423 doc=
"Column name for the disk half light radius used in the fake source catalog.",
425 default=
"DiskHalfLightRadius",
427 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
428 " to specify disk half-light ellipse."
432 aDisk = pexConfig.Field(
433 doc=
"The column name for the semi major axis length of the disk component used in the fake source"
438 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
439 " to specify disk half-light ellipse."
443 bDisk = pexConfig.Field(
444 doc=
"The column name for the semi minor axis length of the disk component.",
448 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
449 " to specify disk half-light ellipse."
453 paDisk = pexConfig.Field(
454 doc=
"The column name for the PA of the disk component used in the fake source catalog.",
458 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
459 " to specify disk half-light ellipse."
463 nDisk = pexConfig.Field(
464 doc=
"The column name for the sersic index of the disk component used in the fake source catalog.",
467 deprecated=
"Use `disk_n_col` instead."
470 bulgeHLR = pexConfig.Field(
471 doc=
"Column name for the bulge half light radius used in the fake source catalog.",
473 default=
"BulgeHalfLightRadius",
475 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
476 "`bulge_pa_col` to specify disk half-light ellipse."
480 aBulge = pexConfig.Field(
481 doc=
"The column name for the semi major axis length of the bulge component.",
485 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
486 "`bulge_pa_col` to specify disk half-light ellipse."
490 bBulge = pexConfig.Field(
491 doc=
"The column name for the semi minor axis length of the bulge component used in the fake source "
496 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
497 "`bulge_pa_col` to specify disk half-light ellipse."
501 paBulge = pexConfig.Field(
502 doc=
"The column name for the PA of the bulge component used in the fake source catalog.",
506 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
507 "`bulge_pa_col` to specify disk half-light ellipse."
511 nBulge = pexConfig.Field(
512 doc=
"The column name for the sersic index of the bulge component used in the fake source catalog.",
515 deprecated=
"Use `bulge_n_col` instead."
518 magVar = pexConfig.Field(
519 doc=
"The column name for the magnitude calculated taking variability into account. In the format "
520 "``filter name``magVar, e.g. imagVar for the magnitude in the i band.",
523 deprecated=
"Use `mag_col` instead."
526 sourceSelectionColName = pexConfig.Field(
527 doc=
"The name of the column in the input fakes catalogue to be used to determine which sources to"
528 "add, default is none and when this is used all sources are added.",
530 default=
"templateSource",
531 deprecated=
"Use `select_col` instead."
535class InsertFakesTask(PipelineTask):
536 """Insert fake objects into images.
538 Add fake stars and galaxies to the given image, read
in through the dataRef. Galaxy parameters are read
in
539 from the specified file
and then modelled using galsim.
541 `InsertFakesTask` has five functions that make images of the fake sources
and then add them to the
545 Use the WCS information to add the pixel coordinates of each source.
546 `mkFakeGalsimGalaxies`
547 Use Galsim to make fake double sersic galaxies
for each set of galaxy parameters
in the input file.
549 Use the PSF information
from the image to make a fake star using the magnitude information
from the
552 Remove rows of the input fake catalog which have half light radius, of either the bulge
or the disk,
553 that are 0. Also removes rows that have Sersic index outside of galsim
's allowed paramters. If
554 the config option sourceSelectionColName is set then this function limits the catalog of input fakes
555 to only those which are
True in this column.
557 Add the fake sources to the image.
561 _DefaultName = "insertFakes"
562 ConfigClass = InsertFakesConfig
564 def runQuantum(self, butlerQC, inputRefs, outputRefs):
565 inputs = butlerQC.get(inputRefs)
566 inputs[
"wcs"] = inputs[
"image"].getWcs()
567 inputs[
"photoCalib"] = inputs[
"image"].getPhotoCalib()
569 outputs = self.run(**inputs)
570 butlerQC.put(outputs, outputRefs)
572 def run(self, fakeCat, image, wcs, photoCalib):
573 """Add fake sources to an image.
577 fakeCat : `pandas.core.frame.DataFrame`
578 The catalog of fake sources to be input
579 image : `lsst.afw.image.exposure.exposure.ExposureF`
580 The image into which the fake sources should be added
582 WCS to use to add fake sources
583 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
584 Photometric calibration to be used to calibrate the fake sources
588 resultStruct : `lsst.pipe.base.struct.Struct`
589 contains : image : `lsst.afw.image.exposure.exposure.ExposureF`
593 Adds pixel coordinates for each source to the fakeCat
and removes objects
with bulge
or disk half
594 light radius = 0 (
if ``config.doCleanCat =
True``).
596 Adds the ``Fake`` mask plane to the image which
is then set by `addFakeSources` to mark where fake
597 sources have been added. Uses the information
in the ``fakeCat`` to make fake galaxies (using galsim)
598 and fake stars, using the PSF models
from the PSF information
for the image. These are then added to
599 the image
and the image
with fakes included returned.
601 The galsim galaxies are made using a double sersic profile, one
for the bulge
and one
for the disk,
602 this
is then convolved
with the PSF at that point.
606 origWcs = image.getWcs()
607 origPhotoCalib = image.getPhotoCalib()
609 image.setPhotoCalib(photoCalib)
611 band = image.getFilter().bandLabel
612 fakeCat = self._standardizeColumns(fakeCat, band)
614 fakeCat = self.addPixCoords(fakeCat, image)
615 fakeCat = self.trimFakeCat(fakeCat, image)
618 if not self.config.insertImages:
619 if isinstance(fakeCat[self.config.sourceType].iloc[0], str):
620 galCheckVal =
"galaxy"
621 starCheckVal =
"star"
622 trailCheckVal =
"trail"
623 elif isinstance(fakeCat[self.config.sourceType].iloc[0], bytes):
624 galCheckVal = b
"galaxy"
625 starCheckVal = b
"star"
626 trailCheckVal = b
"trail"
627 elif isinstance(fakeCat[self.config.sourceType].iloc[0], (int, float)):
633 "sourceType column does not have required type, should be str, bytes or int"
635 if self.config.doCleanCat:
636 fakeCat = self.cleanCat(fakeCat, starCheckVal)
638 generator = self._generateGSObjectsFromCatalog(image, fakeCat, galCheckVal, starCheckVal,
641 generator = self._generateGSObjectsFromImages(image, fakeCat)
642 _add_fake_sources(image, generator, calibFluxRadius=self.config.calibFluxRadius, logger=self.log)
643 elif len(fakeCat) == 0
and self.config.doProcessAllDataIds:
644 self.log.warning(
"No fakes found for this dataRef; processing anyway.")
645 image.mask.addMaskPlane(
"FAKE")
647 raise RuntimeError(
"No fakes found for this dataRef.")
650 image.setWcs(origWcs)
651 image.setPhotoCalib(origPhotoCalib)
653 resultStruct = pipeBase.Struct(imageWithFakes=image)
657 def _standardizeColumns(self, fakeCat, band):
658 """Use config variables to 'standardize' the expected columns and column
659 names in the input catalog.
663 fakeCat : `pandas.core.frame.DataFrame`
664 The catalog of fake sources to be input
666 Label
for the current band being processed.
670 outCat : `pandas.core.frame.DataFrame`
671 The standardized catalog of fake sources
676 def add_to_replace_dict(new_name, depr_name, std_name):
677 if new_name
in fakeCat.columns:
678 replace_dict[new_name] = std_name
679 elif depr_name
in fakeCat.columns:
680 replace_dict[depr_name] = std_name
682 raise ValueError(f
"Could not determine column for {std_name}.")
686 for new_name, depr_name, std_name
in [
687 (cfg.ra_col, cfg.raColName,
'ra'),
688 (cfg.dec_col, cfg.decColName,
'dec'),
689 (cfg.mag_col%band, cfg.magVar%band,
'mag')
691 add_to_replace_dict(new_name, depr_name, std_name)
693 if not cfg.insertImages
and not cfg.insertOnlyStars:
694 for new_name, depr_name, std_name
in [
695 (cfg.bulge_n_col, cfg.nBulge,
'bulge_n'),
696 (cfg.bulge_pa_col, cfg.paBulge,
'bulge_pa'),
697 (cfg.disk_n_col, cfg.nDisk,
'disk_n'),
698 (cfg.disk_pa_col, cfg.paDisk,
'disk_pa'),
700 add_to_replace_dict(new_name, depr_name, std_name)
702 if cfg.doSubSelectSources:
705 cfg.sourceSelectionColName,
708 fakeCat = fakeCat.rename(columns=replace_dict, copy=
False)
713 if not cfg.insertImages
and not cfg.insertOnlyStars:
715 cfg.bulge_semimajor_col
in fakeCat.columns
716 and cfg.bulge_axis_ratio_col
in fakeCat.columns
718 fakeCat = fakeCat.rename(
720 cfg.bulge_semimajor_col:
'bulge_semimajor',
721 cfg.bulge_axis_ratio_col:
'bulge_axis_ratio',
722 cfg.disk_semimajor_col:
'disk_semimajor',
723 cfg.disk_axis_ratio_col:
'disk_axis_ratio',
728 cfg.bulgeHLR
in fakeCat.columns
729 and cfg.aBulge
in fakeCat.columns
730 and cfg.bBulge
in fakeCat.columns
732 fakeCat[
'bulge_axis_ratio'] = (
733 fakeCat[cfg.bBulge]/fakeCat[cfg.aBulge]
735 fakeCat[
'bulge_semimajor'] = (
736 fakeCat[cfg.bulgeHLR]/np.sqrt(fakeCat[
'bulge_axis_ratio'])
738 fakeCat[
'disk_axis_ratio'] = (
739 fakeCat[cfg.bDisk]/fakeCat[cfg.aDisk]
741 fakeCat[
'disk_semimajor'] = (
742 fakeCat[cfg.diskHLR]/np.sqrt(fakeCat[
'disk_axis_ratio'])
746 "Could not determine columns for half-light radius and "
751 if cfg.bulge_disk_flux_ratio_col
in fakeCat.columns:
752 fakeCat = fakeCat.rename(
754 cfg.bulge_disk_flux_ratio_col:
'bulge_disk_flux_ratio'
759 fakeCat[
'bulge_disk_flux_ratio'] = 1.0
763 def _generateGSObjectsFromCatalog(self, exposure, fakeCat, galCheckVal, starCheckVal, trailCheckVal):
764 """Process catalog to generate `galsim.GSObject` s.
768 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
769 The exposure into which the fake sources should be added
770 fakeCat : `pandas.core.frame.DataFrame`
771 The catalog of fake sources to be input
772 galCheckVal : `str`, `bytes` or `int`
773 The value that
is set
in the sourceType column to specify an object
is a galaxy.
774 starCheckVal : `str`, `bytes`
or `int`
775 The value that
is set
in the sourceType column to specify an object
is a star.
776 trailCheckVal : `str`, `bytes`
or `int`
777 The value that
is set
in the sourceType column to specify an object
is a star
781 gsObjects : `generator`
784 wcs = exposure.getWcs()
785 photoCalib = exposure.getPhotoCalib()
787 self.log.info("Making %d objects for insertion", len(fakeCat))
789 for (index, row)
in fakeCat.iterrows():
793 xy = wcs.skyToPixel(skyCoord)
796 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
800 sourceType = row[self.config.sourceType]
801 if sourceType == galCheckVal:
803 bulge_gs_HLR = row[
'bulge_semimajor']*np.sqrt(row[
'bulge_axis_ratio'])
804 bulge = galsim.Sersic(n=row[
'bulge_n'], half_light_radius=bulge_gs_HLR)
805 bulge = bulge.shear(q=row[
'bulge_axis_ratio'], beta=((90 - row[
'bulge_pa'])*galsim.degrees))
807 disk_gs_HLR = row[
'disk_semimajor']*np.sqrt(row[
'disk_axis_ratio'])
808 disk = galsim.Sersic(n=row[
'disk_n'], half_light_radius=disk_gs_HLR)
809 disk = disk.shear(q=row[
'disk_axis_ratio'], beta=((90 - row[
'disk_pa'])*galsim.degrees))
811 gal = bulge*row[
'bulge_disk_flux_ratio'] + disk
812 gal = gal.withFlux(flux)
815 elif sourceType == starCheckVal:
816 star = galsim.DeltaFunction()
817 star = star.withFlux(flux)
819 elif sourceType == trailCheckVal:
820 length = row[
'trail_length']
821 angle = row[
'trail_angle']
825 theta = galsim.Angle(angle*galsim.radians)
826 trail = galsim.Box(length, thickness)
827 trail = trail.rotate(theta)
828 trail = trail.withFlux(flux*length)
833 mat = wcs.linearizePixelToSky(skyCoord, geom.arcseconds).getMatrix()
834 trail = trail.transform(mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1])
836 yield skyCoord, trail
838 raise TypeError(f
"Unknown sourceType {sourceType}")
840 def _generateGSObjectsFromImages(self, exposure, fakeCat):
841 """Process catalog to generate `galsim.GSObject` s.
845 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
846 The exposure into which the fake sources should be added
847 fakeCat : `pandas.core.frame.DataFrame`
848 The catalog of fake sources to be input
852 gsObjects : `generator`
855 band = exposure.getFilter().bandLabel
856 wcs = exposure.getWcs()
857 photoCalib = exposure.getPhotoCalib()
859 self.log.info("Processing %d fake images", len(fakeCat))
861 for (index, row)
in fakeCat.iterrows():
865 xy = wcs.skyToPixel(skyCoord)
868 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
872 imFile = row[band+
"imFilename"]
874 imFile = imFile.decode(
"utf-8")
875 except AttributeError:
877 imFile = imFile.strip()
878 im = galsim.fits.read(imFile, read_header=
True)
880 if self.config.fits_alignment ==
"wcs":
887 if _isWCSGalsimDefault(im.wcs, im.header):
889 f
"Cannot find WCS in input FITS file {imFile}"
891 elif self.config.fits_alignment ==
"pixel":
894 linWcs = wcs.linearizePixelToSky(skyCoord, geom.arcseconds)
895 mat = linWcs.getMatrix()
896 im.wcs = galsim.JacobianWCS(
897 mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1]
901 f
"Unknown fits_alignment type {self.config.fits_alignment}"
904 obj = galsim.InterpolatedImage(im, calculate_stepk=
False)
905 obj = obj.withFlux(flux)
909 """Process images from files into the format needed for insertion.
913 fakeCat : `pandas.core.frame.DataFrame`
914 The catalog of fake sources to be input
915 wcs : `lsst.afw.geom.skyWcs.skyWcs.SkyWc`
916 WCS to use to add fake sources
917 psf : `lsst.meas.algorithms.coaddPsf.coaddPsf.CoaddPsf` or
918 `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
919 The PSF information to use to make the PSF images
920 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
921 Photometric calibration to be used to calibrate the fake sources
923 The filter band that the observation was taken
in.
925 The pixel scale of the image the sources are to be added to.
930 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF`
and
932 For sources labelled
as galaxy.
934 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF`
and
936 For sources labelled
as star.
940 The input fakes catalog needs to contain the absolute path to the image
in the
941 band that
is being used to add images to. It also needs to have the R.A.
and
942 declination of the fake source
in radians
and the sourceType of the object.
947 self.log.info("Processing %d fake images", len(fakeCat))
949 for (imFile, sourceType, mag, x, y)
in zip(fakeCat[band +
"imFilename"].array,
950 fakeCat[
"sourceType"].array,
951 fakeCat[
'mag'].array,
952 fakeCat[
"x"].array, fakeCat[
"y"].array):
954 im = afwImage.ImageF.readFits(imFile)
961 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
962 psfKernel = psf.computeKernelImage(xy).getArray()
963 psfKernel /= correctedFlux
965 except InvalidParameterError:
966 self.log.info(
"%s at %0.4f, %0.4f outside of image", sourceType, x, y)
969 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
970 galsimIm = galsim.InterpolatedImage(galsim.Image(im.array), scale=pixelScale)
971 convIm = galsim.Convolve([galsimIm, psfIm])
974 outIm = convIm.drawImage(scale=pixelScale, method=
"real_space").array
975 except (galsim.errors.GalSimFFTSizeError, MemoryError):
978 imSum = np.sum(outIm)
982 flux = photoCalib.magnitudeToInstFlux(mag, xy)
986 imWithFlux = flux*divIm
988 if sourceType == b
"galaxy":
989 galImages.append((afwImage.ImageF(imWithFlux), xy))
990 if sourceType == b
"star":
991 starImages.append((afwImage.ImageF(imWithFlux), xy))
993 return galImages, starImages
997 """Add pixel coordinates to the catalog of fakes.
1001 fakeCat : `pandas.core.frame.DataFrame`
1002 The catalog of fake sources to be input
1003 image : `lsst.afw.image.exposure.exposure.ExposureF`
1004 The image into which the fake sources should be added
1008 fakeCat : `pandas.core.frame.DataFrame`
1010 wcs = image.getWcs()
1011 ras = fakeCat['ra'].values
1012 decs = fakeCat[
'dec'].values
1013 xs, ys = wcs.skyToPixelArray(ras, decs)
1020 """Trim the fake cat to the size of the input image plus trimBuffer padding.
1022 `fakeCat` must be processed with addPixCoords before using this method.
1026 fakeCat : `pandas.core.frame.DataFrame`
1027 The catalog of fake sources to be input
1028 image : `lsst.afw.image.exposure.exposure.ExposureF`
1029 The image into which the fake sources should be added
1033 fakeCat : `pandas.core.frame.DataFrame`
1034 The original fakeCat trimmed to the area of the image
1036 wideBbox = Box2D(image.getBBox()).dilatedBy(self.config.trimBuffer)
1040 ras = fakeCat[self.config.ra_col].values * u.rad
1041 decs = fakeCat[self.config.dec_col].values * u.rad
1043 isContainedRaDec = image.containsSkyCoords(ras, decs, padding=self.config.trimBuffer)
1046 xs = fakeCat[
"x"].values
1047 ys = fakeCat[
"y"].values
1049 isContainedXy = xs >= wideBbox.minX
1050 isContainedXy &= xs <= wideBbox.maxX
1051 isContainedXy &= ys >= wideBbox.minY
1052 isContainedXy &= ys <= wideBbox.maxY
1054 return fakeCat[isContainedRaDec & isContainedXy]
1057 """Make images of fake galaxies using GalSim.
1062 pixelScale : `float`
1063 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
1064 The PSF information to use to make the PSF images
1065 fakeCat : `pandas.core.frame.DataFrame`
1066 The catalog of fake sources to be input
1067 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
1068 Photometric calibration to be used to calibrate the fake sources
1072 galImages : `generator`
1073 A generator of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
1079 Fake galaxies are made by combining two sersic profiles, one
for the bulge
and one
for the disk. Each
1080 component has an individual sersic index (n), a, b
and position angle (PA). The combined profile
is
1081 then convolved
with the PSF at the specified x, y position on the image.
1083 The names of the columns
in the ``fakeCat`` are configurable
and are the column names
from the
1084 University of Washington simulations database
as default. For more information see the doc strings
1085 attached to the config options.
1087 See mkFakeStars doc string
for an explanation of calibration to instrumental flux.
1090 self.log.info("Making %d fake galaxy images", len(fakeCat))
1092 for (index, row)
in fakeCat.iterrows():
1098 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1099 psfKernel = psf.computeKernelImage(xy).getArray()
1100 psfKernel /= correctedFlux
1102 except InvalidParameterError:
1103 self.log.info(
"Galaxy at %0.4f, %0.4f outside of image", row[
"x"], row[
"y"])
1107 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
1112 bulge_gs_HLR = row[
'bulge_semimajor']*np.sqrt(row[
'bulge_axis_ratio'])
1113 bulge = galsim.Sersic(n=row[
'bulge_n'], half_light_radius=bulge_gs_HLR)
1114 bulge = bulge.shear(q=row[
'bulge_axis_ratio'], beta=((90 - row[
'bulge_pa'])*galsim.degrees))
1116 disk_gs_HLR = row[
'disk_semimajor']*np.sqrt(row[
'disk_axis_ratio'])
1117 disk = galsim.Sersic(n=row[
'disk_n'], half_light_radius=disk_gs_HLR)
1118 disk = disk.shear(q=row[
'disk_axis_ratio'], beta=((90 - row[
'disk_pa'])*galsim.degrees))
1120 gal = bulge*row[
'bulge_disk_flux_ratio'] + disk
1121 gal = gal.withFlux(flux)
1123 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
1124 gal = galsim.Convolve([gal, psfIm])
1126 galIm = gal.drawImage(scale=pixelScale, method=
"real_space").array
1127 except (galsim.errors.GalSimFFTSizeError, MemoryError):
1130 yield (afwImage.ImageF(galIm), xy)
1134 """Make fake stars based off the properties in the fakeCat.
1139 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
1140 The PSF information to use to make the PSF images
1141 fakeCat : `pandas.core.frame.DataFrame`
1142 The catalog of fake sources to be input
1143 image : `lsst.afw.image.exposure.exposure.ExposureF`
1144 The image into which the fake sources should be added
1145 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
1146 Photometric calibration to be used to calibrate the fake sources
1150 starImages : `generator`
1151 A generator of tuples of `lsst.afw.image.ImageF` of fake stars and
1156 To take a given magnitude
and translate to the number of counts
in the image
1157 we use photoCalib.magnitudeToInstFlux, which returns the instrumental flux
for the
1158 given calibration radius used
in the photometric calibration step.
1159 Thus `calibFluxRadius` should be set to this same radius so that we can normalize
1160 the PSF model to the correct instrumental flux within calibFluxRadius.
1163 self.log.info("Making %d fake star images", len(fakeCat))
1165 for (index, row)
in fakeCat.iterrows():
1171 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1172 starIm = psf.computeImage(xy)
1173 starIm /= correctedFlux
1175 except InvalidParameterError:
1176 self.log.info(
"Star at %0.4f, %0.4f outside of image", row[
"x"], row[
"y"])
1180 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
1185 yield ((starIm.convertF(), xy))
1188 """Remove rows from the fakes catalog which have HLR = 0 for either the buldge or disk component,
1189 also remove galaxies that have Sersic index outside the galsim min and max
1190 allowed (0.3 <= n <= 6.2).
1194 fakeCat : `pandas.core.frame.DataFrame`
1195 The catalog of fake sources to be input
1196 starCheckVal : `str`, `bytes`
or `int`
1197 The value that
is set
in the sourceType column to specifiy an object
is a star.
1201 fakeCat : `pandas.core.frame.DataFrame`
1202 The input catalog of fake sources but
with the bad objects removed
1205 rowsToKeep = (((fakeCat['bulge_semimajor'] != 0.0) & (fakeCat[
'disk_semimajor'] != 0.0))
1206 | (fakeCat[self.config.sourceType] == starCheckVal))
1207 numRowsNotUsed = len(fakeCat) - len(np.where(rowsToKeep)[0])
1208 self.log.info(
"Removing %d rows with HLR = 0 for either the bulge or disk", numRowsNotUsed)
1209 fakeCat = fakeCat[rowsToKeep]
1211 minN = galsim.Sersic._minimum_n
1212 maxN = galsim.Sersic._maximum_n
1213 rowsWithGoodSersic = (((fakeCat[
'bulge_n'] >= minN) & (fakeCat[
'bulge_n'] <= maxN)
1214 & (fakeCat[
'disk_n'] >= minN) & (fakeCat[
'disk_n'] <= maxN))
1215 | (fakeCat[self.config.sourceType] == starCheckVal))
1216 numRowsNotUsed = len(fakeCat) - len(np.where(rowsWithGoodSersic)[0])
1217 self.log.info(
"Removing %d rows of galaxies with nBulge or nDisk outside of %0.2f <= n <= %0.2f",
1218 numRowsNotUsed, minN, maxN)
1219 fakeCat = fakeCat[rowsWithGoodSersic]
1221 if self.config.doSubSelectSources:
1222 numRowsNotUsed = len(fakeCat) - len(fakeCat[
'select'])
1223 self.log.info(
"Removing %d rows which were not designated as template sources", numRowsNotUsed)
1224 fakeCat = fakeCat[fakeCat[
'select']]
1229 """Add the fake sources to the given image
1233 image : `lsst.afw.image.exposure.exposure.ExposureF`
1234 The image into which the fake sources should be added
1235 fakeImages : `typing.Iterator` [`tuple` ['lsst.afw.image.ImageF`, `lsst.geom.Point2d`]]
1236 An iterator of tuples that contains (or generates) images of fake sources,
1237 and the locations they are to be inserted at.
1239 The type (star/galaxy) of fake sources input
1243 image : `lsst.afw.image.exposure.exposure.ExposureF`
1247 Uses the x, y information
in the ``fakeCat`` to position an image of the fake interpolated onto the
1248 pixel grid of the image. Sets the ``FAKE`` mask plane
for the pixels added
with the fake source.
1251 imageBBox = image.getBBox()
1252 imageMI = image.maskedImage
1254 for (fakeImage, xy)
in fakeImages:
1255 X0 = xy.getX() - fakeImage.getWidth()/2 + 0.5
1256 Y0 = xy.getY() - fakeImage.getHeight()/2 + 0.5
1257 self.log.debug(
"Adding fake source at %d, %d", xy.getX(), xy.getY())
1258 if sourceType ==
"galaxy":
1259 interpFakeImage = afwMath.offsetImage(fakeImage, X0, Y0,
"lanczos3")
1261 interpFakeImage = fakeImage
1263 interpFakeImBBox = interpFakeImage.getBBox()
1264 interpFakeImBBox.clip(imageBBox)
1266 if interpFakeImBBox.getArea() > 0:
1267 imageMIView = imageMI[interpFakeImBBox]
1268 clippedFakeImage = interpFakeImage[interpFakeImBBox]
1269 clippedFakeImageMI = afwImage.MaskedImageF(clippedFakeImage)
1270 clippedFakeImageMI.mask.set(self.bitmask)
1271 imageMIView += clippedFakeImageMI
def addPixCoords(self, fakeCat, image)
def mkFakeStars(self, fakeCat, band, photoCalib, psf, image)
def mkFakeGalsimGalaxies(self, fakeCat, band, photoCalib, pixelScale, psf, image)
def cleanCat(self, fakeCat, starCheckVal)
def processImagesForInsertion(self, fakeCat, wcs, psf, photoCalib, band, pixelScale)
def trimFakeCat(self, fakeCat, image)
def addFakeSources(self, image, fakeImages, sourceType)