lsst.pipe.tasks g6a99470703+f1f46aed25
insertFakes.py
Go to the documentation of this file.
1# This file is part of pipe tasks
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (http://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
21
22"""
23Insert fakes into deepCoadds
24"""
25import galsim
26import numpy as np
27from astropy import units as u
28
29import lsst.geom as geom
30import lsst.afw.image as afwImage
31import lsst.afw.math as afwMath
32import lsst.pex.config as pexConfig
33import lsst.pipe.base as pipeBase
34
35from lsst.pipe.base import PipelineTask, PipelineTaskConfig, PipelineTaskConnections
36import lsst.pipe.base.connectionTypes as cT
37from lsst.pex.exceptions import LogicError, InvalidParameterError
38from lsst.geom import SpherePoint, radians, Box2D, Point2D
39
40__all__ = ["InsertFakesConfig", "InsertFakesTask"]
41
42
43def _add_fake_sources(exposure, objects, calibFluxRadius=12.0, logger=None):
44 """Add fake sources to the given exposure
45
46 Parameters
47 ----------
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
59 Logger.
60 """
61 exposure.mask.addMaskPlane("FAKE")
62 bitmask = exposure.mask.getPlaneBitMask("FAKE")
63 if logger:
64 logger.info(f"Adding mask plane with bitmask {bitmask}")
65
66 wcs = exposure.getWcs()
67 psf = exposure.getPsf()
68
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)
72
73 pixScale = wcs.getPixelScale(bbox.getCenter()).asArcseconds()
74
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)
79 if logger:
80 logger.debug(f"Adding fake source at {pt}")
81
82 mat = wcs.linearizePixelToSky(spt, geom.arcseconds).getMatrix()
83 gsWCS = galsim.JacobianWCS(mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1])
84
85 # This check is here because sometimes the WCS
86 # is multivalued and objects that should not be
87 # were being included.
88 gsPixScale = np.sqrt(gsWCS.pixelArea())
89 if gsPixScale < pixScale/2 or gsPixScale > pixScale*2:
90 continue
91
92 try:
93 psfArr = psf.computeKernelImage(pt).array
94 except InvalidParameterError:
95 # Try mapping to nearest point contained in bbox.
96 contained_pt = Point2D(
97 np.clip(pt.x, bbox.minX, bbox.maxX),
98 np.clip(pt.y, bbox.minY, bbox.maxY)
99 )
100 if pt == contained_pt: # no difference, so skip immediately
101 if logger:
102 logger.infof(
103 "Cannot compute Psf for object at {}; skipping",
104 pt
105 )
106 continue
107 # otherwise, try again with new point
108 try:
109 psfArr = psf.computeKernelImage(contained_pt).array
110 except InvalidParameterError:
111 if logger:
112 logger.infof(
113 "Cannot compute Psf for object at {}; skipping",
114 pt
115 )
116 continue
117
118 apCorr = psf.computeApertureFlux(calibFluxRadius)
119 psfArr /= apCorr
120 gsPSF = galsim.InterpolatedImage(galsim.Image(psfArr), wcs=gsWCS)
121
122 conv = galsim.Convolve(gsObj, gsPSF)
123 stampSize = conv.getGoodImageSize(gsWCS.minLinearScale())
124 subBounds = galsim.BoundsI(posi).withBorder(stampSize//2)
125 subBounds &= fullBounds
126
127 if subBounds.area() > 0:
128 subImg = gsImg[subBounds]
129 offset = posd - subBounds.true_center
130 # Note, for calexp injection, pixel is already part of the PSF and
131 # for coadd injection, it's incorrect to include the output pixel.
132 # So for both cases, we draw using method='no_pixel'.
133
134 conv.drawImage(
135 subImg,
136 add_to_image=True,
137 offset=offset,
138 wcs=gsWCS,
139 method='no_pixel'
140 )
141
142 subBox = geom.Box2I(
143 geom.Point2I(subBounds.xmin, subBounds.ymin),
144 geom.Point2I(subBounds.xmax, subBounds.ymax)
145 )
146 exposure[subBox].mask.array |= bitmask
147
148
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.
152
153 Parameters
154 ----------
155 wcs : galsim.BaseWCS
156 Potentially default WCS.
157 hdr : galsim.fits.FitsHeader
158 Header as read in by galsim.
159
160 Returns
161 -------
162 isDefault : bool
163 True if default, False if explicitly set in header.
164 """
165 if wcs != galsim.PixelScale(1.0):
166 return False
167 if hdr.get('GS_WCS') is not None:
168 return False
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:
172 # If one of these succeeds, then assume result is explicit
173 try:
174 wcs_type._readHeader(hdr)
175 return False
176 except Exception:
177 pass
178 else:
179 return not any(k in hdr for k in ['CD1_1', 'CDELT1'])
180
181
182class InsertFakesConnections(PipelineTaskConnections,
183 defaultTemplates={"coaddName": "deep",
184 "fakesType": "fakes_"},
185 dimensions=("tract", "patch", "band", "skymap")):
186
187 image = cT.Input(
188 doc="Image into which fakes are to be added.",
189 name="{coaddName}Coadd",
190 storageClass="ExposureF",
191 dimensions=("tract", "patch", "band", "skymap")
192 )
193
194 fakeCat = cT.Input(
195 doc="Catalog of fake sources to draw inputs from.",
196 name="{fakesType}fakeSourceCat",
197 storageClass="DataFrame",
198 dimensions=("tract", "skymap")
199 )
200
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")
206 )
207
208
209class InsertFakesConfig(PipelineTaskConfig,
210 pipelineConnections=InsertFakesConnections):
211 """Config for inserting fake sources
212 """
213
214 # Unchanged
215
216 doCleanCat = pexConfig.Field(
217 doc="If true removes bad sources from the catalog.",
218 dtype=bool,
219 default=True,
220 )
221
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"
225 "catalog.",
226 dtype=str,
227 default="static",
228 )
229
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.",
234 dtype=float,
235 default=12.0,
236 )
237
238 coaddName = pexConfig.Field(
239 doc="The name of the type of coadd used",
240 dtype=str,
241 default="deep",
242 )
243
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.",
247 dtype=bool,
248 default=False
249 )
250
251 insertImages = pexConfig.Field(
252 doc="Insert images directly? True or False.",
253 dtype=bool,
254 default=False,
255 )
256
257 insertOnlyStars = pexConfig.Field(
258 doc="Insert only stars? True or False.",
259 dtype=bool,
260 default=False,
261 )
262
263 doProcessAllDataIds = pexConfig.Field(
264 doc="If True, all input data IDs will be processed, even those containing no fake sources.",
265 dtype=bool,
266 default=False,
267 )
268
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.",
272 dtype=int,
273 default=100,
274 )
275
276 sourceType = pexConfig.Field(
277 doc="The column name for the source type used in the fake source catalog.",
278 dtype=str,
279 default="sourceType",
280 )
281
282 fits_alignment = pexConfig.ChoiceField(
283 doc="How should injections from FITS files be aligned?",
284 dtype=str,
285 allowed={
286 "wcs": (
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 "
291 "image."
292 ),
293 "pixel": (
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."
297 )
298 },
299 default="pixel"
300 )
301
302 # New source catalog config variables
303
304 ra_col = pexConfig.Field(
305 doc="Source catalog column name for RA (in radians).",
306 dtype=str,
307 default="ra",
308 )
309
310 dec_col = pexConfig.Field(
311 doc="Source catalog column name for dec (in radians).",
312 dtype=str,
313 default="dec",
314 )
315
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.",
319 dtype=str,
320 default="bulge_semimajor",
321 )
322
323 bulge_axis_ratio_col = pexConfig.Field(
324 doc="Source catalog column name for the axis ratio of the bulge "
325 "half-light ellipse.",
326 dtype=str,
327 default="bulge_axis_ratio",
328 )
329
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.",
334 dtype=str,
335 default="bulge_pa",
336 )
337
338 bulge_n_col = pexConfig.Field(
339 doc="Source catalog column name for the Sersic index of the bulge.",
340 dtype=str,
341 default="bulge_n",
342 )
343
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.",
347 dtype=str,
348 default="disk_semimajor",
349 )
350
351 disk_axis_ratio_col = pexConfig.Field(
352 doc="Source catalog column name for the axis ratio of the disk "
353 "half-light ellipse.",
354 dtype=str,
355 default="disk_axis_ratio",
356 )
357
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.",
362 dtype=str,
363 default="disk_pa",
364 )
365
366 disk_n_col = pexConfig.Field(
367 doc="Source catalog column name for the Sersic index of the disk.",
368 dtype=str,
369 default="disk_n",
370 )
371
372 bulge_disk_flux_ratio_col = pexConfig.Field(
373 doc="Source catalog column name for the bulge/disk flux ratio.",
374 dtype=str,
375 default="bulge_disk_flux_ratio",
376 )
377
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.",
383 dtype=str,
384 default="%s_mag"
385 )
386
387 select_col = pexConfig.Field(
388 doc="Source catalog column name to be used to select which sources to "
389 "add.",
390 dtype=str,
391 default="select",
392 )
393
394 length_col = pexConfig.Field(
395 doc="Source catalog column name for trail length (in pixels).",
396 dtype=str,
397 default="trail_length",
398 )
399
400 angle_col = pexConfig.Field(
401 doc="Source catalog column name for trail angle (in radians).",
402 dtype=str,
403 default="trail_angle",
404 )
405
406 # Deprecated config variables
407
408 raColName = pexConfig.Field(
409 doc="RA column name used in the fake source catalog.",
410 dtype=str,
411 default="raJ2000",
412 deprecated="Use `ra_col` instead."
413 )
414
415 decColName = pexConfig.Field(
416 doc="Dec. column name used in the fake source catalog.",
417 dtype=str,
418 default="decJ2000",
419 deprecated="Use `dec_col` instead."
420 )
421
422 diskHLR = pexConfig.Field(
423 doc="Column name for the disk half light radius used in the fake source catalog.",
424 dtype=str,
425 default="DiskHalfLightRadius",
426 deprecated=(
427 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
428 " to specify disk half-light ellipse."
429 )
430 )
431
432 aDisk = pexConfig.Field(
433 doc="The column name for the semi major axis length of the disk component used in the fake source"
434 "catalog.",
435 dtype=str,
436 default="a_d",
437 deprecated=(
438 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
439 " to specify disk half-light ellipse."
440 )
441 )
442
443 bDisk = pexConfig.Field(
444 doc="The column name for the semi minor axis length of the disk component.",
445 dtype=str,
446 default="b_d",
447 deprecated=(
448 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
449 " to specify disk half-light ellipse."
450 )
451 )
452
453 paDisk = pexConfig.Field(
454 doc="The column name for the PA of the disk component used in the fake source catalog.",
455 dtype=str,
456 default="pa_disk",
457 deprecated=(
458 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
459 " to specify disk half-light ellipse."
460 )
461 )
462
463 nDisk = pexConfig.Field(
464 doc="The column name for the sersic index of the disk component used in the fake source catalog.",
465 dtype=str,
466 default="disk_n",
467 deprecated="Use `disk_n_col` instead."
468 )
469
470 bulgeHLR = pexConfig.Field(
471 doc="Column name for the bulge half light radius used in the fake source catalog.",
472 dtype=str,
473 default="BulgeHalfLightRadius",
474 deprecated=(
475 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
476 "`bulge_pa_col` to specify disk half-light ellipse."
477 )
478 )
479
480 aBulge = pexConfig.Field(
481 doc="The column name for the semi major axis length of the bulge component.",
482 dtype=str,
483 default="a_b",
484 deprecated=(
485 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
486 "`bulge_pa_col` to specify disk half-light ellipse."
487 )
488 )
489
490 bBulge = pexConfig.Field(
491 doc="The column name for the semi minor axis length of the bulge component used in the fake source "
492 "catalog.",
493 dtype=str,
494 default="b_b",
495 deprecated=(
496 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
497 "`bulge_pa_col` to specify disk half-light ellipse."
498 )
499 )
500
501 paBulge = pexConfig.Field(
502 doc="The column name for the PA of the bulge component used in the fake source catalog.",
503 dtype=str,
504 default="pa_bulge",
505 deprecated=(
506 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
507 "`bulge_pa_col` to specify disk half-light ellipse."
508 )
509 )
510
511 nBulge = pexConfig.Field(
512 doc="The column name for the sersic index of the bulge component used in the fake source catalog.",
513 dtype=str,
514 default="bulge_n",
515 deprecated="Use `bulge_n_col` instead."
516 )
517
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.",
521 dtype=str,
522 default="%smagVar",
523 deprecated="Use `mag_col` instead."
524 )
525
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.",
529 dtype=str,
530 default="templateSource",
531 deprecated="Use `select_col` instead."
532 )
533
534
535class InsertFakesTask(PipelineTask):
536 """Insert fake objects into images.
537
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.
540
541 `InsertFakesTask` has five functions that make images of the fake sources and then add them to the
542 image.
543
544 `addPixCoords`
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.
548 `mkFakeStars`
549 Use the PSF information from the image to make a fake star using the magnitude information from the
550 input file.
551 `cleanCat`
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.
556 `addFakeSources`
557 Add the fake sources to the image.
558
559 """
560
561 _DefaultName = "insertFakes"
562 ConfigClass = InsertFakesConfig
563
564 def runQuantum(self, butlerQC, inputRefs, outputRefs):
565 inputs = butlerQC.get(inputRefs)
566 inputs["wcs"] = inputs["image"].getWcs()
567 inputs["photoCalib"] = inputs["image"].getPhotoCalib()
568
569 outputs = self.run(**inputs)
570 butlerQC.put(outputs, outputRefs)
571
572 def run(self, fakeCat, image, wcs, photoCalib):
573 """Add fake sources to an image.
574
575 Parameters
576 ----------
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
585
586 Returns
587 -------
588 resultStruct : `lsst.pipe.base.struct.Struct`
589 contains : image : `lsst.afw.image.exposure.exposure.ExposureF`
590
591 Notes
592 -----
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``).
595
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.
600
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.
603 """
604 # Attach overriding wcs and photoCalib to image, but retain originals
605 # so we can reset at the end.
606 origWcs = image.getWcs()
607 origPhotoCalib = image.getPhotoCalib()
608 image.setWcs(wcs)
609 image.setPhotoCalib(photoCalib)
610
611 band = image.getFilter().bandLabel
612 fakeCat = self._standardizeColumns(fakeCat, band)
613
614 fakeCat = self.addPixCoords(fakeCat, image)
615 fakeCat = self.trimFakeCat(fakeCat, image)
616
617 if len(fakeCat) > 0:
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)):
628 galCheckVal = 1
629 starCheckVal = 0
630 trailCheckVal = 2
631 else:
632 raise TypeError(
633 "sourceType column does not have required type, should be str, bytes or int"
634 )
635 if self.config.doCleanCat:
636 fakeCat = self.cleanCat(fakeCat, starCheckVal)
637
638 generator = self._generateGSObjectsFromCatalog(image, fakeCat, galCheckVal, starCheckVal,
639 trailCheckVal)
640 else:
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")
646 else:
647 raise RuntimeError("No fakes found for this dataRef.")
648
649 # restore original exposure WCS and photoCalib
650 image.setWcs(origWcs)
651 image.setPhotoCalib(origPhotoCalib)
652
653 resultStruct = pipeBase.Struct(imageWithFakes=image)
654
655 return resultStruct
656
657 def _standardizeColumns(self, fakeCat, band):
658 """Use config variables to 'standardize' the expected columns and column
659 names in the input catalog.
660
661 Parameters
662 ----------
663 fakeCat : `pandas.core.frame.DataFrame`
664 The catalog of fake sources to be input
665 band : `str`
666 Label for the current band being processed.
667
668 Returns
669 -------
670 outCat : `pandas.core.frame.DataFrame`
671 The standardized catalog of fake sources
672 """
673 cfg = self.config
674 replace_dict = {}
675
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
681 else:
682 raise ValueError(f"Could not determine column for {std_name}.")
683
684 # Prefer new config variables over deprecated config variables.
685 # RA, dec, and mag are always required. Do these first
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')
690 ]:
691 add_to_replace_dict(new_name, depr_name, std_name)
692 # Only handle bulge/disk params if not injecting images
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'),
699 ]:
700 add_to_replace_dict(new_name, depr_name, std_name)
701
702 if cfg.doSubSelectSources:
703 add_to_replace_dict(
704 cfg.select_col,
705 cfg.sourceSelectionColName,
706 'select'
707 )
708 fakeCat = fakeCat.rename(columns=replace_dict, copy=False)
709
710 # Handling the half-light radius and axis-ratio are trickier, since we
711 # moved from expecting (HLR, a, b) to expecting (semimajor, axis_ratio).
712 # Just handle these manually.
713 if not cfg.insertImages and not cfg.insertOnlyStars:
714 if (
715 cfg.bulge_semimajor_col in fakeCat.columns
716 and cfg.bulge_axis_ratio_col in fakeCat.columns
717 ):
718 fakeCat = fakeCat.rename(
719 columns={
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',
724 },
725 copy=False
726 )
727 elif (
728 cfg.bulgeHLR in fakeCat.columns
729 and cfg.aBulge in fakeCat.columns
730 and cfg.bBulge in fakeCat.columns
731 ):
732 fakeCat['bulge_axis_ratio'] = (
733 fakeCat[cfg.bBulge]/fakeCat[cfg.aBulge]
734 )
735 fakeCat['bulge_semimajor'] = (
736 fakeCat[cfg.bulgeHLR]/np.sqrt(fakeCat['bulge_axis_ratio'])
737 )
738 fakeCat['disk_axis_ratio'] = (
739 fakeCat[cfg.bDisk]/fakeCat[cfg.aDisk]
740 )
741 fakeCat['disk_semimajor'] = (
742 fakeCat[cfg.diskHLR]/np.sqrt(fakeCat['disk_axis_ratio'])
743 )
744 else:
745 raise ValueError(
746 "Could not determine columns for half-light radius and "
747 "axis ratio."
748 )
749
750 # Process the bulge/disk flux ratio if possible.
751 if cfg.bulge_disk_flux_ratio_col in fakeCat.columns:
752 fakeCat = fakeCat.rename(
753 columns={
754 cfg.bulge_disk_flux_ratio_col: 'bulge_disk_flux_ratio'
755 },
756 copy=False
757 )
758 else:
759 fakeCat['bulge_disk_flux_ratio'] = 1.0
760
761 return fakeCat
762
763 def _generateGSObjectsFromCatalog(self, exposure, fakeCat, galCheckVal, starCheckVal, trailCheckVal):
764 """Process catalog to generate `galsim.GSObject` s.
765
766 Parameters
767 ----------
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
778
779 Yields
780 ------
781 gsObjects : `generator`
782 A generator of tuples of `lsst.geom.SpherePoint` and `galsim.GSObject`.
783 """
784 wcs = exposure.getWcs()
785 photoCalib = exposure.getPhotoCalib()
786
787 self.log.info("Making %d objects for insertion", len(fakeCat))
788
789 for (index, row) in fakeCat.iterrows():
790 ra = row['ra']
791 dec = row['dec']
792 skyCoord = SpherePoint(ra, dec, radians)
793 xy = wcs.skyToPixel(skyCoord)
794
795 try:
796 flux = photoCalib.magnitudeToInstFlux(row['mag'], xy)
797 except LogicError:
798 continue
799
800 sourceType = row[self.config.sourceType]
801 if sourceType == galCheckVal:
802 # GalSim convention: HLR = sqrt(a * b) = a * sqrt(b / a)
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))
806
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))
810
811 gal = bulge*row['bulge_disk_flux_ratio'] + disk
812 gal = gal.withFlux(flux)
813
814 yield skyCoord, gal
815 elif sourceType == starCheckVal:
816 star = galsim.DeltaFunction()
817 star = star.withFlux(flux)
818 yield skyCoord, star
819 elif sourceType == trailCheckVal:
820 length = row['trail_length']
821 angle = row['trail_angle']
822
823 # Make a 'thin' box to mimic a line surface brightness profile
824 thickness = 1e-6 # Make the box much thinner than a pixel
825 theta = galsim.Angle(angle*galsim.radians)
826 trail = galsim.Box(length, thickness)
827 trail = trail.rotate(theta)
828 trail = trail.withFlux(flux*length)
829
830 # Galsim objects are assumed to be in sky-coordinates. Since
831 # we want the trail to appear as defined above in image-
832 # coordinates, we must transform the trail here.
833 mat = wcs.linearizePixelToSky(skyCoord, geom.arcseconds).getMatrix()
834 trail = trail.transform(mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1])
835
836 yield skyCoord, trail
837 else:
838 raise TypeError(f"Unknown sourceType {sourceType}")
839
840 def _generateGSObjectsFromImages(self, exposure, fakeCat):
841 """Process catalog to generate `galsim.GSObject` s.
842
843 Parameters
844 ----------
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
849
850 Yields
851 ------
852 gsObjects : `generator`
853 A generator of tuples of `lsst.geom.SpherePoint` and `galsim.GSObject`.
854 """
855 band = exposure.getFilter().bandLabel
856 wcs = exposure.getWcs()
857 photoCalib = exposure.getPhotoCalib()
858
859 self.log.info("Processing %d fake images", len(fakeCat))
860
861 for (index, row) in fakeCat.iterrows():
862 ra = row['ra']
863 dec = row['dec']
864 skyCoord = SpherePoint(ra, dec, radians)
865 xy = wcs.skyToPixel(skyCoord)
866
867 try:
868 flux = photoCalib.magnitudeToInstFlux(row['mag'], xy)
869 except LogicError:
870 continue
871
872 imFile = row[band+"imFilename"]
873 try:
874 imFile = imFile.decode("utf-8")
875 except AttributeError:
876 pass
877 imFile = imFile.strip()
878 im = galsim.fits.read(imFile, read_header=True)
879
880 if self.config.fits_alignment == "wcs":
881 # galsim.fits.read will always attach a WCS to its output. If it
882 # can't find a WCS in the FITS header, then it defaults to
883 # scale = 1.0 arcsec / pix. So if that's the scale, then we
884 # need to check if it was explicitly set or if it's just the
885 # default. If it's just the default then we should raise an
886 # exception.
887 if _isWCSGalsimDefault(im.wcs, im.header):
888 raise RuntimeError(
889 f"Cannot find WCS in input FITS file {imFile}"
890 )
891 elif self.config.fits_alignment == "pixel":
892 # Here we need to set im.wcs to the local WCS at the target
893 # position.
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]
898 )
899 else:
900 raise ValueError(
901 f"Unknown fits_alignment type {self.config.fits_alignment}"
902 )
903
904 obj = galsim.InterpolatedImage(im, calculate_stepk=False)
905 obj = obj.withFlux(flux)
906 yield skyCoord, obj
907
908 def processImagesForInsertion(self, fakeCat, wcs, psf, photoCalib, band, pixelScale):
909 """Process images from files into the format needed for insertion.
910
911 Parameters
912 ----------
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
922 band : `str`
923 The filter band that the observation was taken in.
924 pixelScale : `float`
925 The pixel scale of the image the sources are to be added to.
926
927 Returns
928 -------
929 galImages : `list`
930 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
931 `lsst.geom.Point2D` of their locations.
932 For sources labelled as galaxy.
933 starImages : `list`
934 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
935 `lsst.geom.Point2D` of their locations.
936 For sources labelled as star.
937
938 Notes
939 -----
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.
943 """
944 galImages = []
945 starImages = []
946
947 self.log.info("Processing %d fake images", len(fakeCat))
948
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):
953
954 im = afwImage.ImageF.readFits(imFile)
955
956 xy = geom.Point2D(x, y)
957
958 # We put these two PSF calculations within this same try block so that we catch cases
959 # where the object's position is outside of the image.
960 try:
961 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
962 psfKernel = psf.computeKernelImage(xy).getArray()
963 psfKernel /= correctedFlux
964
965 except InvalidParameterError:
966 self.log.info("%s at %0.4f, %0.4f outside of image", sourceType, x, y)
967 continue
968
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])
972
973 try:
974 outIm = convIm.drawImage(scale=pixelScale, method="real_space").array
975 except (galsim.errors.GalSimFFTSizeError, MemoryError):
976 continue
977
978 imSum = np.sum(outIm)
979 divIm = outIm/imSum
980
981 try:
982 flux = photoCalib.magnitudeToInstFlux(mag, xy)
983 except LogicError:
984 flux = 0
985
986 imWithFlux = flux*divIm
987
988 if sourceType == b"galaxy":
989 galImages.append((afwImage.ImageF(imWithFlux), xy))
990 if sourceType == b"star":
991 starImages.append((afwImage.ImageF(imWithFlux), xy))
992
993 return galImages, starImages
994
995 def addPixCoords(self, fakeCat, image):
996
997 """Add pixel coordinates to the catalog of fakes.
998
999 Parameters
1000 ----------
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
1005
1006 Returns
1007 -------
1008 fakeCat : `pandas.core.frame.DataFrame`
1009 """
1010 wcs = image.getWcs()
1011 ras = fakeCat['ra'].values
1012 decs = fakeCat['dec'].values
1013 xs, ys = wcs.skyToPixelArray(ras, decs)
1014 fakeCat["x"] = xs
1015 fakeCat["y"] = ys
1016
1017 return fakeCat
1018
1019 def trimFakeCat(self, fakeCat, image):
1020 """Trim the fake cat to the size of the input image plus trimBuffer padding.
1021
1022 `fakeCat` must be processed with addPixCoords before using this method.
1023
1024 Parameters
1025 ----------
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
1030
1031 Returns
1032 -------
1033 fakeCat : `pandas.core.frame.DataFrame`
1034 The original fakeCat trimmed to the area of the image
1035 """
1036 wideBbox = Box2D(image.getBBox()).dilatedBy(self.config.trimBuffer)
1037
1038 # prefilter in ra/dec to avoid cases where the wcs incorrectly maps
1039 # input fakes which are really off the chip onto it.
1040 ras = fakeCat[self.config.ra_col].values * u.rad
1041 decs = fakeCat[self.config.dec_col].values * u.rad
1042
1043 isContainedRaDec = image.containsSkyCoords(ras, decs, padding=self.config.trimBuffer)
1044
1045 # also filter on the image BBox in pixel space
1046 xs = fakeCat["x"].values
1047 ys = fakeCat["y"].values
1048
1049 isContainedXy = xs >= wideBbox.minX
1050 isContainedXy &= xs <= wideBbox.maxX
1051 isContainedXy &= ys >= wideBbox.minY
1052 isContainedXy &= ys <= wideBbox.maxY
1053
1054 return fakeCat[isContainedRaDec & isContainedXy]
1055
1056 def mkFakeGalsimGalaxies(self, fakeCat, band, photoCalib, pixelScale, psf, image):
1057 """Make images of fake galaxies using GalSim.
1058
1059 Parameters
1060 ----------
1061 band : `str`
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
1069
1070 Yields
1071 -------
1072 galImages : `generator`
1073 A generator of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
1074 `lsst.geom.Point2D` of their locations.
1075
1076 Notes
1077 -----
1078
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.
1082
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.
1086
1087 See mkFakeStars doc string for an explanation of calibration to instrumental flux.
1088 """
1089
1090 self.log.info("Making %d fake galaxy images", len(fakeCat))
1091
1092 for (index, row) in fakeCat.iterrows():
1093 xy = geom.Point2D(row["x"], row["y"])
1094
1095 # We put these two PSF calculations within this same try block so that we catch cases
1096 # where the object's position is outside of the image.
1097 try:
1098 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1099 psfKernel = psf.computeKernelImage(xy).getArray()
1100 psfKernel /= correctedFlux
1101
1102 except InvalidParameterError:
1103 self.log.info("Galaxy at %0.4f, %0.4f outside of image", row["x"], row["y"])
1104 continue
1105
1106 try:
1107 flux = photoCalib.magnitudeToInstFlux(row['mag'], xy)
1108 except LogicError:
1109 flux = 0
1110
1111 # GalSim convention: HLR = sqrt(a * b) = a * sqrt(b / a)
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))
1115
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))
1119
1120 gal = bulge*row['bulge_disk_flux_ratio'] + disk
1121 gal = gal.withFlux(flux)
1122
1123 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
1124 gal = galsim.Convolve([gal, psfIm])
1125 try:
1126 galIm = gal.drawImage(scale=pixelScale, method="real_space").array
1127 except (galsim.errors.GalSimFFTSizeError, MemoryError):
1128 continue
1129
1130 yield (afwImage.ImageF(galIm), xy)
1131
1132 def mkFakeStars(self, fakeCat, band, photoCalib, psf, image):
1133
1134 """Make fake stars based off the properties in the fakeCat.
1135
1136 Parameters
1137 ----------
1138 band : `str`
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
1147
1148 Yields
1149 -------
1150 starImages : `generator`
1151 A generator of tuples of `lsst.afw.image.ImageF` of fake stars and
1152 `lsst.geom.Point2D` of their locations.
1153
1154 Notes
1155 -----
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.
1161 """
1162
1163 self.log.info("Making %d fake star images", len(fakeCat))
1164
1165 for (index, row) in fakeCat.iterrows():
1166 xy = geom.Point2D(row["x"], row["y"])
1167
1168 # We put these two PSF calculations within this same try block so that we catch cases
1169 # where the object's position is outside of the image.
1170 try:
1171 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1172 starIm = psf.computeImage(xy)
1173 starIm /= correctedFlux
1174
1175 except InvalidParameterError:
1176 self.log.info("Star at %0.4f, %0.4f outside of image", row["x"], row["y"])
1177 continue
1178
1179 try:
1180 flux = photoCalib.magnitudeToInstFlux(row['mag'], xy)
1181 except LogicError:
1182 flux = 0
1183
1184 starIm *= flux
1185 yield ((starIm.convertF(), xy))
1186
1187 def cleanCat(self, fakeCat, starCheckVal):
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).
1191
1192 Parameters
1193 ----------
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.
1198
1199 Returns
1200 -------
1201 fakeCat : `pandas.core.frame.DataFrame`
1202 The input catalog of fake sources but with the bad objects removed
1203 """
1204
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]
1210
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]
1220
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']]
1225
1226 return fakeCat
1227
1228 def addFakeSources(self, image, fakeImages, sourceType):
1229 """Add the fake sources to the given image
1230
1231 Parameters
1232 ----------
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.
1238 sourceType : `str`
1239 The type (star/galaxy) of fake sources input
1240
1241 Returns
1242 -------
1243 image : `lsst.afw.image.exposure.exposure.ExposureF`
1244
1245 Notes
1246 -----
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.
1249 """
1250
1251 imageBBox = image.getBBox()
1252 imageMI = image.maskedImage
1253
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")
1260 else:
1261 interpFakeImage = fakeImage
1262
1263 interpFakeImBBox = interpFakeImage.getBBox()
1264 interpFakeImBBox.clip(imageBBox)
1265
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
1272
1273 return image
def addPixCoords(self, fakeCat, image)
Definition: insertFakes.py:995
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)
Definition: insertFakes.py:908
def trimFakeCat(self, fakeCat, image)
def addFakeSources(self, image, fakeImages, sourceType)