23 Insert fakes into deepCoadds
26 from astropy.table
import Table
35 from lsst.pipe.base import CmdLineTask, PipelineTask, PipelineTaskConfig, PipelineTaskConnections
36 import lsst.pipe.base.connectionTypes
as cT
39 from lsst.geom import SpherePoint, radians, Box2D, Point2D
41 __all__ = [
"InsertFakesConfig",
"InsertFakesTask"]
44 def _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().asArcseconds()
76 for spt, gsObj
in objects:
77 pt = wcs.skyToPixel(spt)
78 posd = galsim.PositionD(pt.x, pt.y)
79 posi = galsim.PositionI(pt.x//1, pt.y//1)
81 logger.debug(f
"Adding fake source at {pt}")
83 mat = wcs.linearizePixelToSky(spt, geom.arcseconds).getMatrix()
84 gsWCS = galsim.JacobianWCS(mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1])
89 gsPixScale = np.sqrt(gsWCS.pixelArea())
90 if gsPixScale < pixScale/2
or gsPixScale > pixScale*2:
94 psfArr = psf.computeKernelImage(pt).array
95 except InvalidParameterError:
98 np.clip(pt.x, bbox.minX, bbox.maxX),
99 np.clip(pt.y, bbox.minY, bbox.maxY)
101 if pt == contained_pt:
104 "Cannot compute Psf for object at {}; skipping",
110 psfArr = psf.computeKernelImage(contained_pt).array
111 except InvalidParameterError:
114 "Cannot compute Psf for object at {}; skipping",
119 apCorr = psf.computeApertureFlux(calibFluxRadius)
121 gsPSF = galsim.InterpolatedImage(galsim.Image(psfArr), wcs=gsWCS)
123 conv = galsim.Convolve(gsObj, gsPSF)
124 stampSize = conv.getGoodImageSize(gsWCS.minLinearScale())
125 subBounds = galsim.BoundsI(posi).withBorder(stampSize//2)
126 subBounds &= fullBounds
128 if subBounds.area() > 0:
129 subImg = gsImg[subBounds]
130 offset = posd - subBounds.true_center
147 exposure[subBox].mask.array |= bitmask
150 def _isWCSGalsimDefault(wcs, hdr):
151 """Decide if wcs = galsim.PixelScale(1.0) is explicitly present in header,
152 or if it's just the galsim default.
157 Potentially default WCS.
158 hdr : galsim.fits.FitsHeader
159 Header as read in by galsim.
164 True if default, False if explicitly set in header.
166 if wcs != galsim.PixelScale(1.0):
168 if hdr.get(
'GS_WCS')
is not None:
170 if hdr.get(
'CTYPE1',
'LINEAR') ==
'LINEAR':
171 return not any(k
in hdr
for k
in [
'CD1_1',
'CDELT1'])
172 for wcs_type
in galsim.fitswcs.fits_wcs_types:
175 wcs_type._readHeader(hdr)
180 return not any(k
in hdr
for k
in [
'CD1_1',
'CDELT1'])
184 defaultTemplates={
"coaddName":
"deep",
185 "fakesType":
"fakes_"},
186 dimensions=(
"tract",
"patch",
"band",
"skymap")):
189 doc=
"Image into which fakes are to be added.",
190 name=
"{coaddName}Coadd",
191 storageClass=
"ExposureF",
192 dimensions=(
"tract",
"patch",
"band",
"skymap")
196 doc=
"Catalog of fake sources to draw inputs from.",
197 name=
"{fakesType}fakeSourceCat",
198 storageClass=
"DataFrame",
199 dimensions=(
"tract",
"skymap")
202 imageWithFakes = cT.Output(
203 doc=
"Image with fake sources added.",
204 name=
"{fakesType}{coaddName}Coadd",
205 storageClass=
"ExposureF",
206 dimensions=(
"tract",
"patch",
"band",
"skymap")
210 class InsertFakesConfig(PipelineTaskConfig,
211 pipelineConnections=InsertFakesConnections):
212 """Config for inserting fake sources
217 doCleanCat = pexConfig.Field(
218 doc=
"If true removes bad sources from the catalog.",
223 fakeType = pexConfig.Field(
224 doc=
"What type of fake catalog to use, snapshot (includes variability in the magnitudes calculated "
225 "from the MJD of the image), static (no variability) or filename for a user defined fits"
231 calibFluxRadius = pexConfig.Field(
232 doc=
"Aperture radius (in pixels) that was used to define the calibration for this image+catalog. "
233 "This will be used to produce the correct instrumental fluxes within the radius. "
234 "This value should match that of the field defined in slot_CalibFlux_instFlux.",
239 coaddName = pexConfig.Field(
240 doc=
"The name of the type of coadd used",
245 doSubSelectSources = pexConfig.Field(
246 doc=
"Set to True if you wish to sub select sources to be input based on the value in the column"
247 "set in the sourceSelectionColName config option.",
252 insertImages = pexConfig.Field(
253 doc=
"Insert images directly? True or False.",
258 doProcessAllDataIds = pexConfig.Field(
259 doc=
"If True, all input data IDs will be processed, even those containing no fake sources.",
264 trimBuffer = pexConfig.Field(
265 doc=
"Size of the pixel buffer surrounding the image. Only those fake sources with a centroid"
266 "falling within the image+buffer region will be considered for fake source injection.",
271 sourceType = pexConfig.Field(
272 doc=
"The column name for the source type used in the fake source catalog.",
274 default=
"sourceType",
277 fits_alignment = pexConfig.ChoiceField(
278 doc=
"How should injections from FITS files be aligned?",
282 "Input image will be transformed such that the local WCS in "
283 "the FITS header matches the local WCS in the target image. "
284 "I.e., North, East, and angular distances in the input image "
285 "will match North, East, and angular distances in the target "
289 "Input image will _not_ be transformed. Up, right, and pixel "
290 "distances in the input image will match up, right and pixel "
291 "distances in the target image."
299 ra_col = pexConfig.Field(
300 doc=
"Source catalog column name for RA (in radians).",
305 dec_col = pexConfig.Field(
306 doc=
"Source catalog column name for dec (in radians).",
311 bulge_semimajor_col = pexConfig.Field(
312 doc=
"Source catalog column name for the semimajor axis (in arcseconds) "
313 "of the bulge half-light ellipse.",
315 default=
"bulge_semimajor",
318 bulge_axis_ratio_col = pexConfig.Field(
319 doc=
"Source catalog column name for the axis ratio of the bulge "
320 "half-light ellipse.",
322 default=
"bulge_axis_ratio",
325 bulge_pa_col = pexConfig.Field(
326 doc=
"Source catalog column name for the position angle (measured from "
327 "North through East in degrees) of the semimajor axis of the bulge "
328 "half-light ellipse.",
333 bulge_n_col = pexConfig.Field(
334 doc=
"Source catalog column name for the Sersic index of the bulge.",
339 disk_semimajor_col = pexConfig.Field(
340 doc=
"Source catalog column name for the semimajor axis (in arcseconds) "
341 "of the disk half-light ellipse.",
343 default=
"disk_semimajor",
346 disk_axis_ratio_col = pexConfig.Field(
347 doc=
"Source catalog column name for the axis ratio of the disk "
348 "half-light ellipse.",
350 default=
"disk_axis_ratio",
353 disk_pa_col = pexConfig.Field(
354 doc=
"Source catalog column name for the position angle (measured from "
355 "North through East in degrees) of the semimajor axis of the disk "
356 "half-light ellipse.",
361 disk_n_col = pexConfig.Field(
362 doc=
"Source catalog column name for the Sersic index of the disk.",
367 bulge_disk_flux_ratio_col = pexConfig.Field(
368 doc=
"Source catalog column name for the bulge/disk flux ratio.",
370 default=
"bulge_disk_flux_ratio",
373 mag_col = pexConfig.Field(
374 doc=
"Source catalog column name template for magnitudes, in the format "
375 "``filter name``_mag_col. E.g., if this config variable is set to "
376 "``%s_mag``, then the i-band magnitude will be searched for in the "
377 "``i_mag`` column of the source catalog.",
382 select_col = pexConfig.Field(
383 doc=
"Source catalog column name to be used to select which sources to "
391 raColName = pexConfig.Field(
392 doc=
"RA column name used in the fake source catalog.",
395 deprecated=
"Use `ra_col` instead."
398 decColName = pexConfig.Field(
399 doc=
"Dec. column name used in the fake source catalog.",
402 deprecated=
"Use `dec_col` instead."
405 diskHLR = pexConfig.Field(
406 doc=
"Column name for the disk half light radius used in the fake source catalog.",
408 default=
"DiskHalfLightRadius",
410 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
411 " to specify disk half-light ellipse."
415 aDisk = pexConfig.Field(
416 doc=
"The column name for the semi major axis length of the disk component used in the fake source"
421 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
422 " to specify disk half-light ellipse."
426 bDisk = pexConfig.Field(
427 doc=
"The column name for the semi minor axis length of the disk component.",
431 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
432 " to specify disk half-light ellipse."
436 paDisk = pexConfig.Field(
437 doc=
"The column name for the PA of the disk component used in the fake source catalog.",
441 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
442 " to specify disk half-light ellipse."
446 nDisk = pexConfig.Field(
447 doc=
"The column name for the sersic index of the disk component used in the fake source catalog.",
450 deprecated=
"Use `disk_n` instead."
453 bulgeHLR = pexConfig.Field(
454 doc=
"Column name for the bulge half light radius used in the fake source catalog.",
456 default=
"BulgeHalfLightRadius",
458 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
459 "`bulge_pa_col` to specify disk half-light ellipse."
463 aBulge = pexConfig.Field(
464 doc=
"The column name for the semi major axis length of the bulge component.",
468 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
469 "`bulge_pa_col` to specify disk half-light ellipse."
473 bBulge = pexConfig.Field(
474 doc=
"The column name for the semi minor axis length of the bulge component used in the fake source "
479 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
480 "`bulge_pa_col` to specify disk half-light ellipse."
484 paBulge = pexConfig.Field(
485 doc=
"The column name for the PA of the bulge component used in the fake source catalog.",
489 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
490 "`bulge_pa_col` to specify disk half-light ellipse."
494 nBulge = pexConfig.Field(
495 doc=
"The column name for the sersic index of the bulge component used in the fake source catalog.",
498 deprecated=
"Use `bulge_n` instead."
501 magVar = pexConfig.Field(
502 doc=
"The column name for the magnitude calculated taking variability into account. In the format "
503 "``filter name``magVar, e.g. imagVar for the magnitude in the i band.",
506 deprecated=
"Use `mag_col` instead."
509 sourceSelectionColName = pexConfig.Field(
510 doc=
"The name of the column in the input fakes catalogue to be used to determine which sources to"
511 "add, default is none and when this is used all sources are added.",
513 default=
"templateSource",
514 deprecated=
"Use `select_col` instead."
518 class InsertFakesTask(PipelineTask, CmdLineTask):
519 """Insert fake objects into images.
521 Add fake stars and galaxies to the given image, read in through the dataRef. Galaxy parameters are read in
522 from the specified file and then modelled using galsim.
524 `InsertFakesTask` has five functions that make images of the fake sources and then add them to the
528 Use the WCS information to add the pixel coordinates of each source.
529 `mkFakeGalsimGalaxies`
530 Use Galsim to make fake double sersic galaxies for each set of galaxy parameters in the input file.
532 Use the PSF information from the image to make a fake star using the magnitude information from the
535 Remove rows of the input fake catalog which have half light radius, of either the bulge or the disk,
536 that are 0. Also removes rows that have Sersic index outside of galsim's allowed paramters. If
537 the config option sourceSelectionColName is set then this function limits the catalog of input fakes
538 to only those which are True in this column.
540 Add the fake sources to the image.
544 _DefaultName =
"insertFakes"
545 ConfigClass = InsertFakesConfig
547 def runDataRef(self, dataRef):
548 """Read in/write out the required data products and add fake sources to the deepCoadd.
552 dataRef : `lsst.daf.persistence.butlerSubset.ButlerDataRef`
553 Data reference defining the image to have fakes added to it
554 Used to access the following data products:
558 self.log.
info(
"Adding fakes to: tract: %d, patch: %s, filter: %s",
559 dataRef.dataId[
"tract"], dataRef.dataId[
"patch"], dataRef.dataId[
"filter"])
563 if self.config.fakeType ==
"static":
564 fakeCat = dataRef.get(
"deepCoadd_fakeSourceCat").toDataFrame()
567 self.fakeSourceCatType =
"deepCoadd_fakeSourceCat"
569 fakeCat = Table.read(self.config.fakeType).to_pandas()
571 coadd = dataRef.get(
"deepCoadd")
573 photoCalib = coadd.getPhotoCalib()
575 imageWithFakes = self.run(fakeCat, coadd, wcs, photoCalib)
577 dataRef.put(imageWithFakes.imageWithFakes,
"fakes_deepCoadd")
579 def runQuantum(self, butlerQC, inputRefs, outputRefs):
580 inputs = butlerQC.get(inputRefs)
581 inputs[
"wcs"] = inputs[
"image"].getWcs()
582 inputs[
"photoCalib"] = inputs[
"image"].getPhotoCalib()
584 outputs = self.run(**inputs)
585 butlerQC.put(outputs, outputRefs)
588 def _makeArgumentParser(cls):
589 parser = pipeBase.ArgumentParser(name=cls._DefaultName)
590 parser.add_id_argument(name=
"--id", datasetType=
"deepCoadd",
591 help=
"data IDs for the deepCoadd, e.g. --id tract=12345 patch=1,2 filter=r",
592 ContainerClass=ExistingCoaddDataIdContainer)
595 def run(self, fakeCat, image, wcs, photoCalib):
596 """Add fake sources to an image.
600 fakeCat : `pandas.core.frame.DataFrame`
601 The catalog of fake sources to be input
602 image : `lsst.afw.image.exposure.exposure.ExposureF`
603 The image into which the fake sources should be added
604 wcs : `lsst.afw.geom.SkyWcs`
605 WCS to use to add fake sources
606 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
607 Photometric calibration to be used to calibrate the fake sources
611 resultStruct : `lsst.pipe.base.struct.Struct`
612 contains : image : `lsst.afw.image.exposure.exposure.ExposureF`
616 Adds pixel coordinates for each source to the fakeCat and removes objects with bulge or disk half
617 light radius = 0 (if ``config.doCleanCat = True``).
619 Adds the ``Fake`` mask plane to the image which is then set by `addFakeSources` to mark where fake
620 sources have been added. Uses the information in the ``fakeCat`` to make fake galaxies (using galsim)
621 and fake stars, using the PSF models from the PSF information for the image. These are then added to
622 the image and the image with fakes included returned.
624 The galsim galaxies are made using a double sersic profile, one for the bulge and one for the disk,
625 this is then convolved with the PSF at that point.
629 origWcs = image.getWcs()
630 origPhotoCalib = image.getPhotoCalib()
632 image.setPhotoCalib(photoCalib)
634 band = image.getFilterLabel().bandLabel
635 fakeCat = self._standardizeColumns(fakeCat, band)
637 fakeCat = self.addPixCoords(fakeCat, image)
638 fakeCat = self.trimFakeCat(fakeCat, image)
641 if not self.config.insertImages:
642 if isinstance(fakeCat[self.config.sourceType].iloc[0], str):
643 galCheckVal =
"galaxy"
644 starCheckVal =
"star"
645 elif isinstance(fakeCat[self.config.sourceType].iloc[0], bytes):
646 galCheckVal = b
"galaxy"
647 starCheckVal = b
"star"
648 elif isinstance(fakeCat[self.config.sourceType].iloc[0], (int, float)):
653 "sourceType column does not have required type, should be str, bytes or int"
655 if self.config.doCleanCat:
656 fakeCat = self.cleanCat(fakeCat, starCheckVal)
658 generator = self._generateGSObjectsFromCatalog(image, fakeCat, galCheckVal, starCheckVal)
660 generator = self._generateGSObjectsFromImages(image, fakeCat)
661 _add_fake_sources(image, generator, calibFluxRadius=self.config.calibFluxRadius, logger=self.log)
662 elif len(fakeCat) == 0
and self.config.doProcessAllDataIds:
663 self.log.
warning(
"No fakes found for this dataRef; processing anyway.")
664 image.mask.addMaskPlane(
"FAKE")
666 raise RuntimeError(
"No fakes found for this dataRef.")
669 image.setWcs(origWcs)
670 image.setPhotoCalib(origPhotoCalib)
672 resultStruct = pipeBase.Struct(imageWithFakes=image)
676 def _standardizeColumns(self, fakeCat, band):
677 """Use config variables to 'standardize' the expected columns and column
678 names in the input catalog.
682 fakeCat : `pandas.core.frame.DataFrame`
683 The catalog of fake sources to be input
685 Label for the current band being processed.
689 outCat : `pandas.core.frame.DataFrame`
690 The standardized catalog of fake sources
695 def add_to_replace_dict(new_name, depr_name, std_name):
696 if new_name
in fakeCat.columns:
697 replace_dict[new_name] = std_name
698 elif depr_name
in fakeCat.columns:
699 replace_dict[depr_name] = std_name
701 raise ValueError(f
"Could not determine column for {std_name}.")
705 for new_name, depr_name, std_name
in [
706 (cfg.ra_col, cfg.raColName,
'ra'),
707 (cfg.dec_col, cfg.decColName,
'dec'),
708 (cfg.mag_col%band, cfg.magVar%band,
'mag')
710 add_to_replace_dict(new_name, depr_name, std_name)
712 if not cfg.insertImages:
713 for new_name, depr_name, std_name
in [
714 (cfg.bulge_n_col, cfg.nBulge,
'bulge_n'),
715 (cfg.bulge_pa_col, cfg.paBulge,
'bulge_pa'),
716 (cfg.disk_n_col, cfg.nDisk,
'disk_n'),
717 (cfg.disk_pa_col, cfg.paDisk,
'disk_pa'),
719 add_to_replace_dict(new_name, depr_name, std_name)
721 if cfg.doSubSelectSources:
724 cfg.sourceSelectionColName,
727 fakeCat = fakeCat.rename(columns=replace_dict, copy=
False)
732 if not cfg.insertImages:
734 cfg.bulge_semimajor_col
in fakeCat.columns
735 and cfg.bulge_axis_ratio_col
in fakeCat.columns
737 fakeCat = fakeCat.rename(
739 cfg.bulge_semimajor_col:
'bulge_semimajor',
740 cfg.bulge_axis_ratio_col:
'bulge_axis_ratio',
741 cfg.disk_semimajor_col:
'disk_semimajor',
742 cfg.disk_axis_ratio_col:
'disk_axis_ratio',
747 cfg.bulgeHLR
in fakeCat.columns
748 and cfg.aBulge
in fakeCat.columns
749 and cfg.bBulge
in fakeCat.columns
751 fakeCat[
'bulge_axis_ratio'] = (
752 fakeCat[cfg.bBulge]/fakeCat[cfg.aBulge]
754 fakeCat[
'bulge_semimajor'] = (
755 fakeCat[cfg.bulgeHLR]/np.sqrt(fakeCat[
'bulge_axis_ratio'])
757 fakeCat[
'disk_axis_ratio'] = (
758 fakeCat[cfg.bDisk]/fakeCat[cfg.aDisk]
760 fakeCat[
'disk_semimajor'] = (
761 fakeCat[cfg.diskHLR]/np.sqrt(fakeCat[
'disk_axis_ratio'])
765 "Could not determine columns for half-light radius and "
770 if cfg.bulge_disk_flux_ratio_col
in fakeCat.columns:
771 fakeCat = fakeCat.rename(
773 cfg.bulge_disk_flux_ratio_col:
'bulge_disk_flux_ratio'
778 fakeCat[
'bulge_disk_flux_ratio'] = 1.0
782 def _generateGSObjectsFromCatalog(self, exposure, fakeCat, galCheckVal, starCheckVal):
783 """Process catalog to generate `galsim.GSObject` s.
787 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
788 The exposure into which the fake sources should be added
789 fakeCat : `pandas.core.frame.DataFrame`
790 The catalog of fake sources to be input
791 galCheckVal : `str`, `bytes` or `int`
792 The value that is set in the sourceType column to specifiy an object is a galaxy.
793 starCheckVal : `str`, `bytes` or `int`
794 The value that is set in the sourceType column to specifiy an object is a star.
798 gsObjects : `generator`
799 A generator of tuples of `lsst.geom.SpherePoint` and `galsim.GSObject`.
801 wcs = exposure.getWcs()
802 photoCalib = exposure.getPhotoCalib()
804 self.log.
info(
"Making %d objects for insertion", len(fakeCat))
806 for (index, row)
in fakeCat.iterrows():
810 xy = wcs.skyToPixel(skyCoord)
813 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
817 sourceType = row[self.config.sourceType]
818 if sourceType == galCheckVal:
820 bulge_gs_HLR = row[
'bulge_semimajor']*np.sqrt(row[
'bulge_axis_ratio'])
821 bulge = galsim.Sersic(n=row[
'bulge_n'], half_light_radius=bulge_gs_HLR)
822 bulge = bulge.shear(q=row[
'bulge_axis_ratio'], beta=((90 - row[
'bulge_pa'])*galsim.degrees))
824 disk_gs_HLR = row[
'disk_semimajor']*np.sqrt(row[
'disk_axis_ratio'])
825 disk = galsim.Sersic(n=row[
'disk_n'], half_light_radius=disk_gs_HLR)
826 disk = disk.shear(q=row[
'disk_axis_ratio'], beta=((90 - row[
'disk_pa'])*galsim.degrees))
828 gal = bulge*row[
'bulge_disk_flux_ratio'] + disk
829 gal = gal.withFlux(flux)
832 elif sourceType == starCheckVal:
833 star = galsim.DeltaFunction()
834 star = star.withFlux(flux)
837 raise TypeError(f
"Unknown sourceType {sourceType}")
839 def _generateGSObjectsFromImages(self, exposure, fakeCat):
840 """Process catalog to generate `galsim.GSObject` s.
844 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
845 The exposure into which the fake sources should be added
846 fakeCat : `pandas.core.frame.DataFrame`
847 The catalog of fake sources to be input
851 gsObjects : `generator`
852 A generator of tuples of `lsst.geom.SpherePoint` and `galsim.GSObject`.
854 band = exposure.getFilterLabel().bandLabel
855 wcs = exposure.getWcs()
856 photoCalib = exposure.getPhotoCalib()
858 self.log.
info(
"Processing %d fake images", len(fakeCat))
860 for (index, row)
in fakeCat.iterrows():
864 xy = wcs.skyToPixel(skyCoord)
867 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
871 imFile = row[band+
"imFilename"]
873 imFile = imFile.decode(
"utf-8")
874 except AttributeError:
876 imFile = imFile.strip()
877 im = galsim.fits.read(imFile, read_header=
True)
879 if self.config.fits_alignment ==
"wcs":
886 if _isWCSGalsimDefault(im.wcs, im.header):
888 f
"Cannot find WCS in input FITS file {imFile}"
890 elif self.config.fits_alignment ==
"pixel":
893 linWcs = wcs.linearizePixelToSky(skyCoord, geom.arcseconds)
894 mat = linWcs.getMatrix()
895 im.wcs = galsim.JacobianWCS(
896 mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1]
900 f
"Unknown fits_alignment type {self.config.fits_alignment}"
903 obj = galsim.InterpolatedImage(im, calculate_stepk=
False)
904 obj = obj.withFlux(flux)
908 """Process images from files into the format needed for insertion.
912 fakeCat : `pandas.core.frame.DataFrame`
913 The catalog of fake sources to be input
914 wcs : `lsst.afw.geom.skyWcs.skyWcs.SkyWc`
915 WCS to use to add fake sources
916 psf : `lsst.meas.algorithms.coaddPsf.coaddPsf.CoaddPsf` or
917 `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
918 The PSF information to use to make the PSF images
919 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
920 Photometric calibration to be used to calibrate the fake sources
922 The filter band that the observation was taken in.
924 The pixel scale of the image the sources are to be added to.
929 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
930 `lsst.geom.Point2D` of their locations.
931 For sources labelled as galaxy.
933 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
934 `lsst.geom.Point2D` of their locations.
935 For sources labelled as star.
939 The input fakes catalog needs to contain the absolute path to the image in the
940 band that is being used to add images to. It also needs to have the R.A. and
941 declination of the fake source in radians and the sourceType of the object.
946 self.log.
info(
"Processing %d fake images", len(fakeCat))
948 for (imFile, sourceType, mag, x, y)
in zip(fakeCat[band +
"imFilename"].array,
949 fakeCat[
"sourceType"].array,
950 fakeCat[
'mag'].array,
951 fakeCat[
"x"].array, fakeCat[
"y"].array):
953 im = afwImage.ImageF.readFits(imFile)
960 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
961 psfKernel = psf.computeKernelImage(xy).getArray()
962 psfKernel /= correctedFlux
964 except InvalidParameterError:
965 self.log.
info(
"%s at %0.4f, %0.4f outside of image", sourceType, x, y)
968 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
969 galsimIm = galsim.InterpolatedImage(galsim.Image(im.array), scale=pixelScale)
970 convIm = galsim.Convolve([galsimIm, psfIm])
973 outIm = convIm.drawImage(scale=pixelScale, method=
"real_space").array
974 except (galsim.errors.GalSimFFTSizeError, MemoryError):
977 imSum = np.sum(outIm)
981 flux = photoCalib.magnitudeToInstFlux(mag, xy)
985 imWithFlux = flux*divIm
987 if sourceType == b
"galaxy":
988 galImages.append((afwImage.ImageF(imWithFlux), xy))
989 if sourceType == b
"star":
990 starImages.append((afwImage.ImageF(imWithFlux), xy))
992 return galImages, starImages
996 """Add pixel coordinates to the catalog of fakes.
1000 fakeCat : `pandas.core.frame.DataFrame`
1001 The catalog of fake sources to be input
1002 image : `lsst.afw.image.exposure.exposure.ExposureF`
1003 The image into which the fake sources should be added
1007 fakeCat : `pandas.core.frame.DataFrame`
1009 wcs = image.getWcs()
1010 ras = fakeCat[
'ra'].values
1011 decs = fakeCat[
'dec'].values
1012 xs, ys = wcs.skyToPixelArray(ras, decs)
1019 """Trim the fake cat to about the size of the input image.
1021 `fakeCat` must be processed with addPixCoords before using this method.
1025 fakeCat : `pandas.core.frame.DataFrame`
1026 The catalog of fake sources to be input
1027 image : `lsst.afw.image.exposure.exposure.ExposureF`
1028 The image into which the fake sources should be added
1032 fakeCat : `pandas.core.frame.DataFrame`
1033 The original fakeCat trimmed to the area of the image
1036 bbox =
Box2D(image.getBBox()).dilatedBy(self.config.trimBuffer)
1037 xs = fakeCat[
"x"].values
1038 ys = fakeCat[
"y"].values
1040 isContained = xs >= bbox.minX
1041 isContained &= xs <= bbox.maxX
1042 isContained &= ys >= bbox.minY
1043 isContained &= ys <= bbox.maxY
1045 return fakeCat[isContained]
1048 """Make images of fake galaxies using GalSim.
1053 pixelScale : `float`
1054 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
1055 The PSF information to use to make the PSF images
1056 fakeCat : `pandas.core.frame.DataFrame`
1057 The catalog of fake sources to be input
1058 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
1059 Photometric calibration to be used to calibrate the fake sources
1063 galImages : `generator`
1064 A generator of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
1065 `lsst.geom.Point2D` of their locations.
1070 Fake galaxies are made by combining two sersic profiles, one for the bulge and one for the disk. Each
1071 component has an individual sersic index (n), a, b and position angle (PA). The combined profile is
1072 then convolved with the PSF at the specified x, y position on the image.
1074 The names of the columns in the ``fakeCat`` are configurable and are the column names from the
1075 University of Washington simulations database as default. For more information see the doc strings
1076 attached to the config options.
1078 See mkFakeStars doc string for an explanation of calibration to instrumental flux.
1081 self.log.
info(
"Making %d fake galaxy images", len(fakeCat))
1083 for (index, row)
in fakeCat.iterrows():
1089 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1090 psfKernel = psf.computeKernelImage(xy).getArray()
1091 psfKernel /= correctedFlux
1093 except InvalidParameterError:
1094 self.log.
info(
"Galaxy at %0.4f, %0.4f outside of image", row[
"x"], row[
"y"])
1098 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
1103 bulge_gs_HLR = row[
'bulge_semimajor']*np.sqrt(row[
'bulge_axis_ratio'])
1104 bulge = galsim.Sersic(n=row[
'bulge_n'], half_light_radius=bulge_gs_HLR)
1105 bulge = bulge.shear(q=row[
'bulge_axis_ratio'], beta=((90 - row[
'bulge_pa'])*galsim.degrees))
1107 disk_gs_HLR = row[
'disk_semimajor']*np.sqrt(row[
'disk_axis_ratio'])
1108 disk = galsim.Sersic(n=row[
'disk_n'], half_light_radius=disk_gs_HLR)
1109 disk = disk.shear(q=row[
'disk_axis_ratio'], beta=((90 - row[
'disk_pa'])*galsim.degrees))
1111 gal = bulge*row[
'bulge_disk_flux_ratio'] + disk
1112 gal = gal.withFlux(flux)
1114 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
1115 gal = galsim.Convolve([gal, psfIm])
1117 galIm = gal.drawImage(scale=pixelScale, method=
"real_space").array
1118 except (galsim.errors.GalSimFFTSizeError, MemoryError):
1121 yield (afwImage.ImageF(galIm), xy)
1125 """Make fake stars based off the properties in the fakeCat.
1130 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
1131 The PSF information to use to make the PSF images
1132 fakeCat : `pandas.core.frame.DataFrame`
1133 The catalog of fake sources to be input
1134 image : `lsst.afw.image.exposure.exposure.ExposureF`
1135 The image into which the fake sources should be added
1136 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
1137 Photometric calibration to be used to calibrate the fake sources
1141 starImages : `generator`
1142 A generator of tuples of `lsst.afw.image.ImageF` of fake stars and
1143 `lsst.geom.Point2D` of their locations.
1147 To take a given magnitude and translate to the number of counts in the image
1148 we use photoCalib.magnitudeToInstFlux, which returns the instrumental flux for the
1149 given calibration radius used in the photometric calibration step.
1150 Thus `calibFluxRadius` should be set to this same radius so that we can normalize
1151 the PSF model to the correct instrumental flux within calibFluxRadius.
1154 self.log.
info(
"Making %d fake star images", len(fakeCat))
1156 for (index, row)
in fakeCat.iterrows():
1162 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1163 starIm = psf.computeImage(xy)
1164 starIm /= correctedFlux
1166 except InvalidParameterError:
1167 self.log.
info(
"Star at %0.4f, %0.4f outside of image", row[
"x"], row[
"y"])
1171 flux = photoCalib.magnitudeToInstFlux(row[
'mag'], xy)
1176 yield ((starIm.convertF(), xy))
1179 """Remove rows from the fakes catalog which have HLR = 0 for either the buldge or disk component,
1180 also remove galaxies that have Sersic index outside the galsim min and max
1181 allowed (0.3 <= n <= 6.2).
1185 fakeCat : `pandas.core.frame.DataFrame`
1186 The catalog of fake sources to be input
1187 starCheckVal : `str`, `bytes` or `int`
1188 The value that is set in the sourceType column to specifiy an object is a star.
1192 fakeCat : `pandas.core.frame.DataFrame`
1193 The input catalog of fake sources but with the bad objects removed
1196 rowsToKeep = (((fakeCat[
'bulge_semimajor'] != 0.0) & (fakeCat[
'disk_semimajor'] != 0.0))
1197 | (fakeCat[self.config.sourceType] == starCheckVal))
1198 numRowsNotUsed = len(fakeCat) - len(np.where(rowsToKeep)[0])
1199 self.log.
info(
"Removing %d rows with HLR = 0 for either the bulge or disk", numRowsNotUsed)
1200 fakeCat = fakeCat[rowsToKeep]
1202 minN = galsim.Sersic._minimum_n
1203 maxN = galsim.Sersic._maximum_n
1204 rowsWithGoodSersic = (((fakeCat[
'bulge_n'] >= minN) & (fakeCat[
'bulge_n'] <= maxN)
1205 & (fakeCat[
'disk_n'] >= minN) & (fakeCat[
'disk_n'] <= maxN))
1206 | (fakeCat[self.config.sourceType] == starCheckVal))
1207 numRowsNotUsed = len(fakeCat) - len(np.where(rowsWithGoodSersic)[0])
1208 self.log.
info(
"Removing %d rows of galaxies with nBulge or nDisk outside of %0.2f <= n <= %0.2f",
1209 numRowsNotUsed, minN, maxN)
1210 fakeCat = fakeCat[rowsWithGoodSersic]
1212 if self.config.doSubSelectSources:
1213 numRowsNotUsed = len(fakeCat) - len(fakeCat[
'select'])
1214 self.log.
info(
"Removing %d rows which were not designated as template sources", numRowsNotUsed)
1215 fakeCat = fakeCat[fakeCat[
'select']]
1220 """Add the fake sources to the given image
1224 image : `lsst.afw.image.exposure.exposure.ExposureF`
1225 The image into which the fake sources should be added
1226 fakeImages : `typing.Iterator` [`tuple` ['lsst.afw.image.ImageF`, `lsst.geom.Point2d`]]
1227 An iterator of tuples that contains (or generates) images of fake sources,
1228 and the locations they are to be inserted at.
1230 The type (star/galaxy) of fake sources input
1234 image : `lsst.afw.image.exposure.exposure.ExposureF`
1238 Uses the x, y information in the ``fakeCat`` to position an image of the fake interpolated onto the
1239 pixel grid of the image. Sets the ``FAKE`` mask plane for the pixels added with the fake source.
1242 imageBBox = image.getBBox()
1243 imageMI = image.maskedImage
1245 for (fakeImage, xy)
in fakeImages:
1246 X0 = xy.getX() - fakeImage.getWidth()/2 + 0.5
1247 Y0 = xy.getY() - fakeImage.getHeight()/2 + 0.5
1248 self.log.
debug(
"Adding fake source at %d, %d", xy.getX(), xy.getY())
1249 if sourceType ==
"galaxy":
1252 interpFakeImage = fakeImage
1254 interpFakeImBBox = interpFakeImage.getBBox()
1255 interpFakeImBBox.clip(imageBBox)
1257 if interpFakeImBBox.getArea() > 0:
1258 imageMIView = imageMI[interpFakeImBBox]
1259 clippedFakeImage = interpFakeImage[interpFakeImBBox]
1260 clippedFakeImageMI = afwImage.MaskedImageF(clippedFakeImage)
1261 clippedFakeImageMI.mask.set(self.bitmask)
1262 imageMIView += clippedFakeImageMI
1266 def _getMetadataName(self):
1267 """Disable metadata writing"""
A floating-point coordinate rectangle geometry.
An integer coordinate rectangle.
Point in an unspecified spherical coordinate system.
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
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.
bool any(CoordinateExpr< N > const &expr) noexcept
Return true if any elements are true.
Point< double, 2 > Point2D
def run(self, coaddExposures, bbox, wcs)
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)