lsst.pipe.tasks ge54c72f270+896438102f
Loading...
Searching...
No Matches
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# (https://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 <https://www.gnu.org/licenses/>.
21
22"""
23Insert fakes into deepCoadds
24"""
25
26__all__ = ["InsertFakesConfig", "InsertFakesTask"]
27
28import galsim
29import numpy as np
30from astropy import units as u
31
32import lsst.geom as geom
33import lsst.afw.image as afwImage
34import lsst.afw.math as afwMath
35import lsst.pex.config as pexConfig
36import lsst.pipe.base as pipeBase
37
38from lsst.pipe.base import PipelineTask, PipelineTaskConfig, PipelineTaskConnections
39import lsst.pipe.base.connectionTypes as cT
40from lsst.pex.exceptions import LogicError, InvalidParameterError
41from lsst.geom import SpherePoint, radians, Box2D, Point2D
42
43
44def _add_fake_sources(exposure, objects, calibFluxRadius=12.0, logger=None):
45 """Add fake sources to the given exposure
46
47 Parameters
48 ----------
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
60 Logger.
61 """
62 exposure.mask.addMaskPlane("FAKE")
63 bitmask = exposure.mask.getPlaneBitMask("FAKE")
64 if logger:
65 logger.info(f"Adding mask plane with bitmask {bitmask}")
66
67 wcs = exposure.getWcs()
68 psf = exposure.getPsf()
69
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)
73
74 pixScale = wcs.getPixelScale(bbox.getCenter()).asArcseconds()
75
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)
80 if logger:
81 logger.debug(f"Adding fake source at {pt}")
82
83 mat = wcs.linearizePixelToSky(spt, geom.arcseconds).getMatrix()
84 gsWCS = galsim.JacobianWCS(mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1])
85
86 # This check is here because sometimes the WCS
87 # is multivalued and objects that should not be
88 # were being included.
89 gsPixScale = np.sqrt(gsWCS.pixelArea())
90 if gsPixScale < pixScale/2 or gsPixScale > pixScale*2:
91 continue
92
93 try:
94 psfArr = psf.computeKernelImage(pt).array
95 except InvalidParameterError:
96 # Try mapping to nearest point contained in bbox.
97 contained_pt = Point2D(
98 np.clip(pt.x, bbox.minX, bbox.maxX),
99 np.clip(pt.y, bbox.minY, bbox.maxY)
100 )
101 if pt == contained_pt: # no difference, so skip immediately
102 if logger:
103 logger.infof(
104 "Cannot compute Psf for object at {}; skipping",
105 pt
106 )
107 continue
108 # otherwise, try again with new point
109 try:
110 psfArr = psf.computeKernelImage(contained_pt).array
111 except InvalidParameterError:
112 if logger:
113 logger.infof(
114 "Cannot compute Psf for object at {}; skipping",
115 pt
116 )
117 continue
118
119 apCorr = psf.computeApertureFlux(calibFluxRadius)
120 psfArr /= apCorr
121 gsPSF = galsim.InterpolatedImage(galsim.Image(psfArr), wcs=gsWCS)
122
123 conv = galsim.Convolve(gsObj, gsPSF)
124 stampSize = conv.getGoodImageSize(gsWCS.minLinearScale())
125 subBounds = galsim.BoundsI(posi).withBorder(stampSize//2)
126 subBounds &= fullBounds
127
128 if subBounds.area() > 0:
129 subImg = gsImg[subBounds]
130 offset = posd - subBounds.true_center
131 # Note, for calexp injection, pixel is already part of the PSF and
132 # for coadd injection, it's incorrect to include the output pixel.
133 # So for both cases, we draw using method='no_pixel'.
134
135 conv.drawImage(
136 subImg,
137 add_to_image=True,
138 offset=offset,
139 wcs=gsWCS,
140 method='no_pixel'
141 )
142
143 subBox = geom.Box2I(
144 geom.Point2I(subBounds.xmin, subBounds.ymin),
145 geom.Point2I(subBounds.xmax, subBounds.ymax)
146 )
147 exposure[subBox].mask.array |= bitmask
148
149
150def _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.
153
154 Parameters
155 ----------
156 wcs : galsim.BaseWCS
157 Potentially default WCS.
158 hdr : galsim.fits.FitsHeader
159 Header as read in by galsim.
160
161 Returns
162 -------
163 isDefault : bool
164 True if default, False if explicitly set in header.
165 """
166 if wcs != galsim.PixelScale(1.0):
167 return False
168 if hdr.get('GS_WCS') is not None:
169 return False
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:
173 # If one of these succeeds, then assume result is explicit
174 try:
175 wcs_type._readHeader(hdr)
176 return False
177 except Exception:
178 pass
179 else:
180 return not any(k in hdr for k in ['CD1_1', 'CDELT1'])
181
182
183class InsertFakesConnections(PipelineTaskConnections,
184 defaultTemplates={"coaddName": "deep",
185 "fakesType": "fakes_"},
186 dimensions=("tract", "patch", "band", "skymap")):
187
188 image = cT.Input(
189 doc="Image into which fakes are to be added.",
190 name="{coaddName}Coadd",
191 storageClass="ExposureF",
192 dimensions=("tract", "patch", "band", "skymap")
193 )
194
195 fakeCat = cT.Input(
196 doc="Catalog of fake sources to draw inputs from.",
197 name="{fakesType}fakeSourceCat",
198 storageClass="DataFrame",
199 dimensions=("tract", "skymap")
200 )
201
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")
207 )
208
209
210class InsertFakesConfig(PipelineTaskConfig,
211 pipelineConnections=InsertFakesConnections):
212 """Config for inserting fake sources
213 """
214
215 # Unchanged
216
217 doCleanCat = pexConfig.Field(
218 doc="If true removes bad sources from the catalog.",
219 dtype=bool,
220 default=True,
221 )
222
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"
226 "catalog.",
227 dtype=str,
228 default="static",
229 )
230
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.",
235 dtype=float,
236 default=12.0,
237 )
238
239 coaddName = pexConfig.Field(
240 doc="The name of the type of coadd used",
241 dtype=str,
242 default="deep",
243 )
244
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.",
248 dtype=bool,
249 default=False
250 )
251
252 insertImages = pexConfig.Field(
253 doc="Insert images directly? True or False.",
254 dtype=bool,
255 default=False,
256 )
257
258 insertOnlyStars = pexConfig.Field(
259 doc="Insert only stars? True or False.",
260 dtype=bool,
261 default=False,
262 )
263
264 doProcessAllDataIds = pexConfig.Field(
265 doc="If True, all input data IDs will be processed, even those containing no fake sources.",
266 dtype=bool,
267 default=False,
268 )
269
270 trimBuffer = pexConfig.Field(
271 doc="Size of the pixel buffer surrounding the image. Only those fake sources with a centroid"
272 "falling within the image+buffer region will be considered for fake source injection.",
273 dtype=int,
274 default=100,
275 )
276
277 sourceType = pexConfig.Field(
278 doc="The column name for the source type used in the fake source catalog.",
279 dtype=str,
280 default="sourceType",
281 )
282
283 fits_alignment = pexConfig.ChoiceField(
284 doc="How should injections from FITS files be aligned?",
285 dtype=str,
286 allowed={
287 "wcs": (
288 "Input image will be transformed such that the local WCS in "
289 "the FITS header matches the local WCS in the target image. "
290 "I.e., North, East, and angular distances in the input image "
291 "will match North, East, and angular distances in the target "
292 "image."
293 ),
294 "pixel": (
295 "Input image will _not_ be transformed. Up, right, and pixel "
296 "distances in the input image will match up, right and pixel "
297 "distances in the target image."
298 )
299 },
300 default="pixel"
301 )
302
303 # New source catalog config variables
304
305 ra_col = pexConfig.Field(
306 doc="Source catalog column name for RA (in radians).",
307 dtype=str,
308 default="ra",
309 )
310
311 dec_col = pexConfig.Field(
312 doc="Source catalog column name for dec (in radians).",
313 dtype=str,
314 default="dec",
315 )
316
317 bulge_semimajor_col = pexConfig.Field(
318 doc="Source catalog column name for the semimajor axis (in arcseconds) "
319 "of the bulge half-light ellipse.",
320 dtype=str,
321 default="bulge_semimajor",
322 )
323
324 bulge_axis_ratio_col = pexConfig.Field(
325 doc="Source catalog column name for the axis ratio of the bulge "
326 "half-light ellipse.",
327 dtype=str,
328 default="bulge_axis_ratio",
329 )
330
331 bulge_pa_col = pexConfig.Field(
332 doc="Source catalog column name for the position angle (measured from "
333 "North through East in degrees) of the semimajor axis of the bulge "
334 "half-light ellipse.",
335 dtype=str,
336 default="bulge_pa",
337 )
338
339 bulge_n_col = pexConfig.Field(
340 doc="Source catalog column name for the Sersic index of the bulge.",
341 dtype=str,
342 default="bulge_n",
343 )
344
345 disk_semimajor_col = pexConfig.Field(
346 doc="Source catalog column name for the semimajor axis (in arcseconds) "
347 "of the disk half-light ellipse.",
348 dtype=str,
349 default="disk_semimajor",
350 )
351
352 disk_axis_ratio_col = pexConfig.Field(
353 doc="Source catalog column name for the axis ratio of the disk "
354 "half-light ellipse.",
355 dtype=str,
356 default="disk_axis_ratio",
357 )
358
359 disk_pa_col = pexConfig.Field(
360 doc="Source catalog column name for the position angle (measured from "
361 "North through East in degrees) of the semimajor axis of the disk "
362 "half-light ellipse.",
363 dtype=str,
364 default="disk_pa",
365 )
366
367 disk_n_col = pexConfig.Field(
368 doc="Source catalog column name for the Sersic index of the disk.",
369 dtype=str,
370 default="disk_n",
371 )
372
373 bulge_disk_flux_ratio_col = pexConfig.Field(
374 doc="Source catalog column name for the bulge/disk flux ratio.",
375 dtype=str,
376 default="bulge_disk_flux_ratio",
377 )
378
379 mag_col = pexConfig.Field(
380 doc="Source catalog column name template for magnitudes, in the format "
381 "``filter name``_mag_col. E.g., if this config variable is set to "
382 "``%s_mag``, then the i-band magnitude will be searched for in the "
383 "``i_mag`` column of the source catalog.",
384 dtype=str,
385 default="%s_mag"
386 )
387
388 select_col = pexConfig.Field(
389 doc="Source catalog column name to be used to select which sources to "
390 "add.",
391 dtype=str,
392 default="select",
393 )
394
395 length_col = pexConfig.Field(
396 doc="Source catalog column name for trail length (in pixels).",
397 dtype=str,
398 default="trail_length",
399 )
400
401 angle_col = pexConfig.Field(
402 doc="Source catalog column name for trail angle (in radians).",
403 dtype=str,
404 default="trail_angle",
405 )
406
407 # Deprecated config variables
408
409 raColName = pexConfig.Field(
410 doc="RA column name used in the fake source catalog.",
411 dtype=str,
412 default="raJ2000",
413 deprecated="Use `ra_col` instead."
414 )
415
416 decColName = pexConfig.Field(
417 doc="Dec. column name used in the fake source catalog.",
418 dtype=str,
419 default="decJ2000",
420 deprecated="Use `dec_col` instead."
421 )
422
423 diskHLR = pexConfig.Field(
424 doc="Column name for the disk half light radius used in the fake source catalog.",
425 dtype=str,
426 default="DiskHalfLightRadius",
427 deprecated=(
428 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
429 " to specify disk half-light ellipse."
430 )
431 )
432
433 aDisk = pexConfig.Field(
434 doc="The column name for the semi major axis length of the disk component used in the fake source"
435 "catalog.",
436 dtype=str,
437 default="a_d",
438 deprecated=(
439 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
440 " to specify disk half-light ellipse."
441 )
442 )
443
444 bDisk = pexConfig.Field(
445 doc="The column name for the semi minor axis length of the disk component.",
446 dtype=str,
447 default="b_d",
448 deprecated=(
449 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
450 " to specify disk half-light ellipse."
451 )
452 )
453
454 paDisk = pexConfig.Field(
455 doc="The column name for the PA of the disk component used in the fake source catalog.",
456 dtype=str,
457 default="pa_disk",
458 deprecated=(
459 "Use `disk_semimajor_col`, `disk_axis_ratio_col`, and `disk_pa_col`"
460 " to specify disk half-light ellipse."
461 )
462 )
463
464 nDisk = pexConfig.Field(
465 doc="The column name for the sersic index of the disk component used in the fake source catalog.",
466 dtype=str,
467 default="disk_n",
468 deprecated="Use `disk_n_col` instead."
469 )
470
471 bulgeHLR = pexConfig.Field(
472 doc="Column name for the bulge half light radius used in the fake source catalog.",
473 dtype=str,
474 default="BulgeHalfLightRadius",
475 deprecated=(
476 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
477 "`bulge_pa_col` to specify disk half-light ellipse."
478 )
479 )
480
481 aBulge = pexConfig.Field(
482 doc="The column name for the semi major axis length of the bulge component.",
483 dtype=str,
484 default="a_b",
485 deprecated=(
486 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
487 "`bulge_pa_col` to specify disk half-light ellipse."
488 )
489 )
490
491 bBulge = pexConfig.Field(
492 doc="The column name for the semi minor axis length of the bulge component used in the fake source "
493 "catalog.",
494 dtype=str,
495 default="b_b",
496 deprecated=(
497 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
498 "`bulge_pa_col` to specify disk half-light ellipse."
499 )
500 )
501
502 paBulge = pexConfig.Field(
503 doc="The column name for the PA of the bulge component used in the fake source catalog.",
504 dtype=str,
505 default="pa_bulge",
506 deprecated=(
507 "Use `bulge_semimajor_col`, `bulge_axis_ratio_col`, and "
508 "`bulge_pa_col` to specify disk half-light ellipse."
509 )
510 )
511
512 nBulge = pexConfig.Field(
513 doc="The column name for the sersic index of the bulge component used in the fake source catalog.",
514 dtype=str,
515 default="bulge_n",
516 deprecated="Use `bulge_n_col` instead."
517 )
518
519 magVar = pexConfig.Field(
520 doc="The column name for the magnitude calculated taking variability into account. In the format "
521 "``filter name``magVar, e.g. imagVar for the magnitude in the i band.",
522 dtype=str,
523 default="%smagVar",
524 deprecated="Use `mag_col` instead."
525 )
526
527 sourceSelectionColName = pexConfig.Field(
528 doc="The name of the column in the input fakes catalogue to be used to determine which sources to"
529 "add, default is none and when this is used all sources are added.",
530 dtype=str,
531 default="templateSource",
532 deprecated="Use `select_col` instead."
533 )
534
535
536class InsertFakesTask(PipelineTask):
537 """Insert fake objects into images.
538
539 Add fake stars and galaxies to the given image, read in through the dataRef. Galaxy parameters are read in
540 from the specified file and then modelled using galsim.
541
542 `InsertFakesTask` has five functions that make images of the fake sources and then add them to the
543 image.
544
545 `addPixCoords`
546 Use the WCS information to add the pixel coordinates of each source.
547 `mkFakeGalsimGalaxies`
548 Use Galsim to make fake double sersic galaxies for each set of galaxy parameters in the input file.
549 `mkFakeStars`
550 Use the PSF information from the image to make a fake star using the magnitude information from the
551 input file.
552 `cleanCat`
553 Remove rows of the input fake catalog which have half light radius, of either the bulge or the disk,
554 that are 0. Also removes rows that have Sersic index outside of galsim's allowed paramters. If
555 the config option sourceSelectionColName is set then this function limits the catalog of input fakes
556 to only those which are True in this column.
557 `addFakeSources`
558 Add the fake sources to the image.
559
560 """
561
562 _DefaultName = "insertFakes"
563 ConfigClass = InsertFakesConfig
564
565 def runQuantum(self, butlerQC, inputRefs, outputRefs):
566 inputs = butlerQC.get(inputRefs)
567 inputs["wcs"] = inputs["image"].getWcs()
568 inputs["photoCalib"] = inputs["image"].getPhotoCalib()
569
570 outputs = self.run(**inputs)
571 butlerQC.put(outputs, outputRefs)
572
573 def run(self, fakeCat, image, wcs, photoCalib):
574 """Add fake sources to an image.
575
576 Parameters
577 ----------
578 fakeCat : `pandas.core.frame.DataFrame`
579 The catalog of fake sources to be input
580 image : `lsst.afw.image.exposure.exposure.ExposureF`
581 The image into which the fake sources should be added
583 WCS to use to add fake sources
584 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
585 Photometric calibration to be used to calibrate the fake sources
586
587 Returns
588 -------
589 resultStruct : `lsst.pipe.base.struct.Struct`
590 contains : image : `lsst.afw.image.exposure.exposure.ExposureF`
591
592 Notes
593 -----
594 Adds pixel coordinates for each source to the fakeCat and removes objects with bulge or disk half
595 light radius = 0 (if ``config.doCleanCat = True``).
596
597 Adds the ``Fake`` mask plane to the image which is then set by `addFakeSources` to mark where fake
598 sources have been added. Uses the information in the ``fakeCat`` to make fake galaxies (using galsim)
599 and fake stars, using the PSF models from the PSF information for the image. These are then added to
600 the image and the image with fakes included returned.
601
602 The galsim galaxies are made using a double sersic profile, one for the bulge and one for the disk,
603 this is then convolved with the PSF at that point.
604 """
605 # Attach overriding wcs and photoCalib to image, but retain originals
606 # so we can reset at the end.
607 origWcs = image.getWcs()
608 origPhotoCalib = image.getPhotoCalib()
609 image.setWcs(wcs)
610 image.setPhotoCalib(photoCalib)
611
612 band = image.getFilter().bandLabel
613 fakeCat = self._standardizeColumns(fakeCat, band)
614
615 fakeCat = self.addPixCoords(fakeCat, image)
616 fakeCat = self.trimFakeCat(fakeCat, image)
617
618 if len(fakeCat) > 0:
619 if not self.config.insertImages:
620 if isinstance(fakeCat[self.config.sourceType].iloc[0], str):
621 galCheckVal = "galaxy"
622 starCheckVal = "star"
623 trailCheckVal = "trail"
624 elif isinstance(fakeCat[self.config.sourceType].iloc[0], bytes):
625 galCheckVal = b"galaxy"
626 starCheckVal = b"star"
627 trailCheckVal = b"trail"
628 elif isinstance(fakeCat[self.config.sourceType].iloc[0], (int, float)):
629 galCheckVal = 1
630 starCheckVal = 0
631 trailCheckVal = 2
632 else:
633 raise TypeError(
634 "sourceType column does not have required type, should be str, bytes or int"
635 )
636 if self.config.doCleanCat:
637 fakeCat = self.cleanCat(fakeCat, starCheckVal)
638
639 generator = self._generateGSObjectsFromCatalog(image, fakeCat, galCheckVal, starCheckVal,
640 trailCheckVal)
641 else:
642 generator = self._generateGSObjectsFromImages(image, fakeCat)
643 _add_fake_sources(image, generator, calibFluxRadius=self.config.calibFluxRadius, logger=self.log)
644 elif len(fakeCat) == 0 and self.config.doProcessAllDataIds:
645 self.log.warning("No fakes found for this dataRef; processing anyway.")
646 image.mask.addMaskPlane("FAKE")
647 else:
648 raise RuntimeError("No fakes found for this dataRef.")
649
650 # restore original exposure WCS and photoCalib
651 image.setWcs(origWcs)
652 image.setPhotoCalib(origPhotoCalib)
653
654 resultStruct = pipeBase.Struct(imageWithFakes=image)
655
656 return resultStruct
657
658 def _standardizeColumns(self, fakeCat, band):
659 """Use config variables to 'standardize' the expected columns and column
660 names in the input catalog.
661
662 Parameters
663 ----------
664 fakeCat : `pandas.core.frame.DataFrame`
665 The catalog of fake sources to be input
666 band : `str`
667 Label for the current band being processed.
668
669 Returns
670 -------
671 outCat : `pandas.core.frame.DataFrame`
672 The standardized catalog of fake sources
673 """
674 cfg = self.config
675 replace_dict = {}
676
677 def add_to_replace_dict(new_name, depr_name, std_name):
678 if new_name in fakeCat.columns:
679 replace_dict[new_name] = std_name
680 elif depr_name in fakeCat.columns:
681 replace_dict[depr_name] = std_name
682 else:
683 raise ValueError(f"Could not determine column for {std_name}.")
684
685 # Prefer new config variables over deprecated config variables.
686 # RA, dec, and mag are always required. Do these first
687 for new_name, depr_name, std_name in [
688 (cfg.ra_col, cfg.raColName, 'ra'),
689 (cfg.dec_col, cfg.decColName, 'dec'),
690 (cfg.mag_col%band, cfg.magVar%band, 'mag')
691 ]:
692 add_to_replace_dict(new_name, depr_name, std_name)
693 # Only handle bulge/disk params if not injecting images
694 if not cfg.insertImages and not cfg.insertOnlyStars:
695 for new_name, depr_name, std_name in [
696 (cfg.bulge_n_col, cfg.nBulge, 'bulge_n'),
697 (cfg.bulge_pa_col, cfg.paBulge, 'bulge_pa'),
698 (cfg.disk_n_col, cfg.nDisk, 'disk_n'),
699 (cfg.disk_pa_col, cfg.paDisk, 'disk_pa'),
700 ]:
701 add_to_replace_dict(new_name, depr_name, std_name)
702
703 if cfg.doSubSelectSources:
704 add_to_replace_dict(
705 cfg.select_col,
706 cfg.sourceSelectionColName,
707 'select'
708 )
709 fakeCat = fakeCat.rename(columns=replace_dict, copy=False)
710
711 # Handling the half-light radius and axis-ratio are trickier, since we
712 # moved from expecting (HLR, a, b) to expecting (semimajor, axis_ratio).
713 # Just handle these manually.
714 if not cfg.insertImages and not cfg.insertOnlyStars:
715 if (
716 cfg.bulge_semimajor_col in fakeCat.columns
717 and cfg.bulge_axis_ratio_col in fakeCat.columns
718 ):
719 fakeCat = fakeCat.rename(
720 columns={
721 cfg.bulge_semimajor_col: 'bulge_semimajor',
722 cfg.bulge_axis_ratio_col: 'bulge_axis_ratio',
723 cfg.disk_semimajor_col: 'disk_semimajor',
724 cfg.disk_axis_ratio_col: 'disk_axis_ratio',
725 },
726 copy=False
727 )
728 elif (
729 cfg.bulgeHLR in fakeCat.columns
730 and cfg.aBulge in fakeCat.columns
731 and cfg.bBulge in fakeCat.columns
732 ):
733 fakeCat['bulge_axis_ratio'] = (
734 fakeCat[cfg.bBulge]/fakeCat[cfg.aBulge]
735 )
736 fakeCat['bulge_semimajor'] = (
737 fakeCat[cfg.bulgeHLR]/np.sqrt(fakeCat['bulge_axis_ratio'])
738 )
739 fakeCat['disk_axis_ratio'] = (
740 fakeCat[cfg.bDisk]/fakeCat[cfg.aDisk]
741 )
742 fakeCat['disk_semimajor'] = (
743 fakeCat[cfg.diskHLR]/np.sqrt(fakeCat['disk_axis_ratio'])
744 )
745 else:
746 raise ValueError(
747 "Could not determine columns for half-light radius and "
748 "axis ratio."
749 )
750
751 # Process the bulge/disk flux ratio if possible.
752 if cfg.bulge_disk_flux_ratio_col in fakeCat.columns:
753 fakeCat = fakeCat.rename(
754 columns={
755 cfg.bulge_disk_flux_ratio_col: 'bulge_disk_flux_ratio'
756 },
757 copy=False
758 )
759 else:
760 fakeCat['bulge_disk_flux_ratio'] = 1.0
761
762 return fakeCat
763
764 def _generateGSObjectsFromCatalog(self, exposure, fakeCat, galCheckVal, starCheckVal, trailCheckVal):
765 """Process catalog to generate `galsim.GSObject` s.
766
767 Parameters
768 ----------
769 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
770 The exposure into which the fake sources should be added
771 fakeCat : `pandas.core.frame.DataFrame`
772 The catalog of fake sources to be input
773 galCheckVal : `str`, `bytes` or `int`
774 The value that is set in the sourceType column to specify an object is a galaxy.
775 starCheckVal : `str`, `bytes` or `int`
776 The value that is set in the sourceType column to specify an object is a star.
777 trailCheckVal : `str`, `bytes` or `int`
778 The value that is set in the sourceType column to specify an object is a star
779
780 Yields
781 ------
782 gsObjects : `generator`
783 A generator of tuples of `lsst.geom.SpherePoint` and `galsim.GSObject`.
784 """
785 wcs = exposure.getWcs()
786 photoCalib = exposure.getPhotoCalib()
787
788 self.log.info("Making %d objects for insertion", len(fakeCat))
789
790 for (index, row) in fakeCat.iterrows():
791 ra = row['ra']
792 dec = row['dec']
793 skyCoord = SpherePoint(ra, dec, radians)
794 xy = wcs.skyToPixel(skyCoord)
795
796 try:
797 flux = photoCalib.magnitudeToInstFlux(row['mag'], xy)
798 except LogicError:
799 continue
800
801 sourceType = row[self.config.sourceType]
802 if sourceType == galCheckVal:
803 # GalSim convention: HLR = sqrt(a * b) = a * sqrt(b / a)
804 bulge_gs_HLR = row['bulge_semimajor']*np.sqrt(row['bulge_axis_ratio'])
805 bulge = galsim.Sersic(n=row['bulge_n'], half_light_radius=bulge_gs_HLR)
806 bulge = bulge.shear(q=row['bulge_axis_ratio'], beta=((90 - row['bulge_pa'])*galsim.degrees))
807
808 disk_gs_HLR = row['disk_semimajor']*np.sqrt(row['disk_axis_ratio'])
809 disk = galsim.Sersic(n=row['disk_n'], half_light_radius=disk_gs_HLR)
810 disk = disk.shear(q=row['disk_axis_ratio'], beta=((90 - row['disk_pa'])*galsim.degrees))
811
812 gal = bulge*row['bulge_disk_flux_ratio'] + disk
813 gal = gal.withFlux(flux)
814
815 yield skyCoord, gal
816 elif sourceType == starCheckVal:
817 star = galsim.DeltaFunction()
818 star = star.withFlux(flux)
819 yield skyCoord, star
820 elif sourceType == trailCheckVal:
821 length = row['trail_length']
822 angle = row['trail_angle']
823
824 # Make a 'thin' box to mimic a line surface brightness profile
825 thickness = 1e-6 # Make the box much thinner than a pixel
826 theta = galsim.Angle(angle*galsim.radians)
827 trail = galsim.Box(length, thickness)
828 trail = trail.rotate(theta)
829 trail = trail.withFlux(flux*length)
830
831 # Galsim objects are assumed to be in sky-coordinates. Since
832 # we want the trail to appear as defined above in image-
833 # coordinates, we must transform the trail here.
834 mat = wcs.linearizePixelToSky(skyCoord, geom.arcseconds).getMatrix()
835 trail = trail.transform(mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1])
836
837 yield skyCoord, trail
838 else:
839 raise TypeError(f"Unknown sourceType {sourceType}")
840
841 def _generateGSObjectsFromImages(self, exposure, fakeCat):
842 """Process catalog to generate `galsim.GSObject` s.
843
844 Parameters
845 ----------
846 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
847 The exposure into which the fake sources should be added
848 fakeCat : `pandas.core.frame.DataFrame`
849 The catalog of fake sources to be input
850
851 Yields
852 ------
853 gsObjects : `generator`
854 A generator of tuples of `lsst.geom.SpherePoint` and `galsim.GSObject`.
855 """
856 band = exposure.getFilter().bandLabel
857 wcs = exposure.getWcs()
858 photoCalib = exposure.getPhotoCalib()
859
860 self.log.info("Processing %d fake images", len(fakeCat))
861
862 for (index, row) in fakeCat.iterrows():
863 ra = row['ra']
864 dec = row['dec']
865 skyCoord = SpherePoint(ra, dec, radians)
866 xy = wcs.skyToPixel(skyCoord)
867
868 try:
869 flux = photoCalib.magnitudeToInstFlux(row['mag'], xy)
870 except LogicError:
871 continue
872
873 imFile = row[band+"imFilename"]
874 try:
875 imFile = imFile.decode("utf-8")
876 except AttributeError:
877 pass
878 imFile = imFile.strip()
879 im = galsim.fits.read(imFile, read_header=True)
880
881 if self.config.fits_alignment == "wcs":
882 # galsim.fits.read will always attach a WCS to its output. If it
883 # can't find a WCS in the FITS header, then it defaults to
884 # scale = 1.0 arcsec / pix. So if that's the scale, then we
885 # need to check if it was explicitly set or if it's just the
886 # default. If it's just the default then we should raise an
887 # exception.
888 if _isWCSGalsimDefault(im.wcs, im.header):
889 raise RuntimeError(
890 f"Cannot find WCS in input FITS file {imFile}"
891 )
892 elif self.config.fits_alignment == "pixel":
893 # Here we need to set im.wcs to the local WCS at the target
894 # position.
895 linWcs = wcs.linearizePixelToSky(skyCoord, geom.arcseconds)
896 mat = linWcs.getMatrix()
897 im.wcs = galsim.JacobianWCS(
898 mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1]
899 )
900 else:
901 raise ValueError(
902 f"Unknown fits_alignment type {self.config.fits_alignment}"
903 )
904
905 obj = galsim.InterpolatedImage(im, calculate_stepk=False)
906 obj = obj.withFlux(flux)
907 yield skyCoord, obj
908
909 def processImagesForInsertion(self, fakeCat, wcs, psf, photoCalib, band, pixelScale):
910 """Process images from files into the format needed for insertion.
911
912 Parameters
913 ----------
914 fakeCat : `pandas.core.frame.DataFrame`
915 The catalog of fake sources to be input
916 wcs : `lsst.afw.geom.skyWcs.skyWcs.SkyWc`
917 WCS to use to add fake sources
918 psf : `lsst.meas.algorithms.coaddPsf.coaddPsf.CoaddPsf` or
919 `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
920 The PSF information to use to make the PSF images
921 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
922 Photometric calibration to be used to calibrate the fake sources
923 band : `str`
924 The filter band that the observation was taken in.
925 pixelScale : `float`
926 The pixel scale of the image the sources are to be added to.
927
928 Returns
929 -------
930 galImages : `list`
931 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
932 `lsst.geom.Point2D` of their locations.
933 For sources labelled as galaxy.
934 starImages : `list`
935 A list of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
936 `lsst.geom.Point2D` of their locations.
937 For sources labelled as star.
938
939 Notes
940 -----
941 The input fakes catalog needs to contain the absolute path to the image in the
942 band that is being used to add images to. It also needs to have the R.A. and
943 declination of the fake source in radians and the sourceType of the object.
944 """
945 galImages = []
946 starImages = []
947
948 self.log.info("Processing %d fake images", len(fakeCat))
949
950 for (imFile, sourceType, mag, x, y) in zip(fakeCat[band + "imFilename"].array,
951 fakeCat["sourceType"].array,
952 fakeCat['mag'].array,
953 fakeCat["x"].array, fakeCat["y"].array):
954
955 im = afwImage.ImageF.readFits(imFile)
956
957 xy = geom.Point2D(x, y)
958
959 # We put these two PSF calculations within this same try block so that we catch cases
960 # where the object's position is outside of the image.
961 try:
962 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
963 psfKernel = psf.computeKernelImage(xy).getArray()
964 psfKernel /= correctedFlux
965
966 except InvalidParameterError:
967 self.log.info("%s at %0.4f, %0.4f outside of image", sourceType, x, y)
968 continue
969
970 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
971 galsimIm = galsim.InterpolatedImage(galsim.Image(im.array), scale=pixelScale)
972 convIm = galsim.Convolve([galsimIm, psfIm])
973
974 try:
975 outIm = convIm.drawImage(scale=pixelScale, method="real_space").array
976 except (galsim.errors.GalSimFFTSizeError, MemoryError):
977 continue
978
979 imSum = np.sum(outIm)
980 divIm = outIm/imSum
981
982 try:
983 flux = photoCalib.magnitudeToInstFlux(mag, xy)
984 except LogicError:
985 flux = 0
986
987 imWithFlux = flux*divIm
988
989 if sourceType == b"galaxy":
990 galImages.append((afwImage.ImageF(imWithFlux), xy))
991 if sourceType == b"star":
992 starImages.append((afwImage.ImageF(imWithFlux), xy))
993
994 return galImages, starImages
995
996 def addPixCoords(self, fakeCat, image):
997
998 """Add pixel coordinates to the catalog of fakes.
999
1000 Parameters
1001 ----------
1002 fakeCat : `pandas.core.frame.DataFrame`
1003 The catalog of fake sources to be input
1004 image : `lsst.afw.image.exposure.exposure.ExposureF`
1005 The image into which the fake sources should be added
1006
1007 Returns
1008 -------
1009 fakeCat : `pandas.core.frame.DataFrame`
1010 """
1011 wcs = image.getWcs()
1012 ras = fakeCat['ra'].values
1013 decs = fakeCat['dec'].values
1014 xs, ys = wcs.skyToPixelArray(ras, decs)
1015 fakeCat["x"] = xs
1016 fakeCat["y"] = ys
1017
1018 return fakeCat
1019
1020 def trimFakeCat(self, fakeCat, image):
1021 """Trim the fake cat to the size of the input image plus trimBuffer padding.
1022
1023 `fakeCat` must be processed with addPixCoords before using this method.
1024
1025 Parameters
1026 ----------
1027 fakeCat : `pandas.core.frame.DataFrame`
1028 The catalog of fake sources to be input
1029 image : `lsst.afw.image.exposure.exposure.ExposureF`
1030 The image into which the fake sources should be added
1031
1032 Returns
1033 -------
1034 fakeCat : `pandas.core.frame.DataFrame`
1035 The original fakeCat trimmed to the area of the image
1036 """
1037 wideBbox = Box2D(image.getBBox()).dilatedBy(self.config.trimBuffer)
1038
1039 # prefilter in ra/dec to avoid cases where the wcs incorrectly maps
1040 # input fakes which are really off the chip onto it.
1041 ras = fakeCat[self.config.ra_col].values * u.rad
1042 decs = fakeCat[self.config.dec_col].values * u.rad
1043
1044 isContainedRaDec = image.containsSkyCoords(ras, decs, padding=self.config.trimBuffer)
1045
1046 # also filter on the image BBox in pixel space
1047 xs = fakeCat["x"].values
1048 ys = fakeCat["y"].values
1049
1050 isContainedXy = xs >= wideBbox.minX
1051 isContainedXy &= xs <= wideBbox.maxX
1052 isContainedXy &= ys >= wideBbox.minY
1053 isContainedXy &= ys <= wideBbox.maxY
1054
1055 return fakeCat[isContainedRaDec & isContainedXy]
1056
1057 def mkFakeGalsimGalaxies(self, fakeCat, band, photoCalib, pixelScale, psf, image):
1058 """Make images of fake galaxies using GalSim.
1059
1060 Parameters
1061 ----------
1062 band : `str`
1063 pixelScale : `float`
1064 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
1065 The PSF information to use to make the PSF images
1066 fakeCat : `pandas.core.frame.DataFrame`
1067 The catalog of fake sources to be input
1068 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
1069 Photometric calibration to be used to calibrate the fake sources
1070
1071 Yields
1072 ------
1073 galImages : `generator`
1074 A generator of tuples of `lsst.afw.image.exposure.exposure.ExposureF` and
1075 `lsst.geom.Point2D` of their locations.
1076
1077 Notes
1078 -----
1079
1080 Fake galaxies are made by combining two sersic profiles, one for the bulge and one for the disk. Each
1081 component has an individual sersic index (n), a, b and position angle (PA). The combined profile is
1082 then convolved with the PSF at the specified x, y position on the image.
1083
1084 The names of the columns in the ``fakeCat`` are configurable and are the column names from the
1085 University of Washington simulations database as default. For more information see the doc strings
1086 attached to the config options.
1087
1088 See mkFakeStars doc string for an explanation of calibration to instrumental flux.
1089 """
1090
1091 self.log.info("Making %d fake galaxy images", len(fakeCat))
1092
1093 for (index, row) in fakeCat.iterrows():
1094 xy = geom.Point2D(row["x"], row["y"])
1095
1096 # We put these two PSF calculations within this same try block so that we catch cases
1097 # where the object's position is outside of the image.
1098 try:
1099 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1100 psfKernel = psf.computeKernelImage(xy).getArray()
1101 psfKernel /= correctedFlux
1102
1103 except InvalidParameterError:
1104 self.log.info("Galaxy at %0.4f, %0.4f outside of image", row["x"], row["y"])
1105 continue
1106
1107 try:
1108 flux = photoCalib.magnitudeToInstFlux(row['mag'], xy)
1109 except LogicError:
1110 flux = 0
1111
1112 # GalSim convention: HLR = sqrt(a * b) = a * sqrt(b / a)
1113 bulge_gs_HLR = row['bulge_semimajor']*np.sqrt(row['bulge_axis_ratio'])
1114 bulge = galsim.Sersic(n=row['bulge_n'], half_light_radius=bulge_gs_HLR)
1115 bulge = bulge.shear(q=row['bulge_axis_ratio'], beta=((90 - row['bulge_pa'])*galsim.degrees))
1116
1117 disk_gs_HLR = row['disk_semimajor']*np.sqrt(row['disk_axis_ratio'])
1118 disk = galsim.Sersic(n=row['disk_n'], half_light_radius=disk_gs_HLR)
1119 disk = disk.shear(q=row['disk_axis_ratio'], beta=((90 - row['disk_pa'])*galsim.degrees))
1120
1121 gal = bulge*row['bulge_disk_flux_ratio'] + disk
1122 gal = gal.withFlux(flux)
1123
1124 psfIm = galsim.InterpolatedImage(galsim.Image(psfKernel), scale=pixelScale)
1125 gal = galsim.Convolve([gal, psfIm])
1126 try:
1127 galIm = gal.drawImage(scale=pixelScale, method="real_space").array
1128 except (galsim.errors.GalSimFFTSizeError, MemoryError):
1129 continue
1130
1131 yield (afwImage.ImageF(galIm), xy)
1132
1133 def mkFakeStars(self, fakeCat, band, photoCalib, psf, image):
1134
1135 """Make fake stars based off the properties in the fakeCat.
1136
1137 Parameters
1138 ----------
1139 band : `str`
1140 psf : `lsst.meas.extensions.psfex.psfexPsf.PsfexPsf`
1141 The PSF information to use to make the PSF images
1142 fakeCat : `pandas.core.frame.DataFrame`
1143 The catalog of fake sources to be input
1144 image : `lsst.afw.image.exposure.exposure.ExposureF`
1145 The image into which the fake sources should be added
1146 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
1147 Photometric calibration to be used to calibrate the fake sources
1148
1149 Yields
1150 ------
1151 starImages : `generator`
1152 A generator of tuples of `lsst.afw.image.ImageF` of fake stars and
1153 `lsst.geom.Point2D` of their locations.
1154
1155 Notes
1156 -----
1157 To take a given magnitude and translate to the number of counts in the image
1158 we use photoCalib.magnitudeToInstFlux, which returns the instrumental flux for the
1159 given calibration radius used in the photometric calibration step.
1160 Thus `calibFluxRadius` should be set to this same radius so that we can normalize
1161 the PSF model to the correct instrumental flux within calibFluxRadius.
1162 """
1163
1164 self.log.info("Making %d fake star images", len(fakeCat))
1165
1166 for (index, row) in fakeCat.iterrows():
1167 xy = geom.Point2D(row["x"], row["y"])
1168
1169 # We put these two PSF calculations within this same try block so that we catch cases
1170 # where the object's position is outside of the image.
1171 try:
1172 correctedFlux = psf.computeApertureFlux(self.config.calibFluxRadius, xy)
1173 starIm = psf.computeImage(xy)
1174 starIm /= correctedFlux
1175
1176 except InvalidParameterError:
1177 self.log.info("Star at %0.4f, %0.4f outside of image", row["x"], row["y"])
1178 continue
1179
1180 try:
1181 flux = photoCalib.magnitudeToInstFlux(row['mag'], xy)
1182 except LogicError:
1183 flux = 0
1184
1185 starIm *= flux
1186 yield ((starIm.convertF(), xy))
1187
1188 def cleanCat(self, fakeCat, starCheckVal):
1189 """Remove rows from the fakes catalog which have HLR = 0 for either the buldge or disk component,
1190 also remove galaxies that have Sersic index outside the galsim min and max
1191 allowed (0.3 <= n <= 6.2).
1192
1193 Parameters
1194 ----------
1195 fakeCat : `pandas.core.frame.DataFrame`
1196 The catalog of fake sources to be input
1197 starCheckVal : `str`, `bytes` or `int`
1198 The value that is set in the sourceType column to specifiy an object is a star.
1199
1200 Returns
1201 -------
1202 fakeCat : `pandas.core.frame.DataFrame`
1203 The input catalog of fake sources but with the bad objects removed
1204 """
1205
1206 rowsToKeep = (((fakeCat['bulge_semimajor'] != 0.0) & (fakeCat['disk_semimajor'] != 0.0))
1207 | (fakeCat[self.config.sourceType] == starCheckVal))
1208 numRowsNotUsed = len(fakeCat) - len(np.where(rowsToKeep)[0])
1209 self.log.info("Removing %d rows with HLR = 0 for either the bulge or disk", numRowsNotUsed)
1210 fakeCat = fakeCat[rowsToKeep]
1211
1212 minN = galsim.Sersic._minimum_n
1213 maxN = galsim.Sersic._maximum_n
1214 rowsWithGoodSersic = (((fakeCat['bulge_n'] >= minN) & (fakeCat['bulge_n'] <= maxN)
1215 & (fakeCat['disk_n'] >= minN) & (fakeCat['disk_n'] <= maxN))
1216 | (fakeCat[self.config.sourceType] == starCheckVal))
1217 numRowsNotUsed = len(fakeCat) - len(np.where(rowsWithGoodSersic)[0])
1218 self.log.info("Removing %d rows of galaxies with nBulge or nDisk outside of %0.2f <= n <= %0.2f",
1219 numRowsNotUsed, minN, maxN)
1220 fakeCat = fakeCat[rowsWithGoodSersic]
1221
1222 if self.config.doSubSelectSources:
1223 numRowsNotUsed = len(fakeCat) - len(fakeCat['select'])
1224 self.log.info("Removing %d rows which were not designated as template sources", numRowsNotUsed)
1225 fakeCat = fakeCat[fakeCat['select']]
1226
1227 return fakeCat
1228
1229 def addFakeSources(self, image, fakeImages, sourceType):
1230 """Add the fake sources to the given image
1231
1232 Parameters
1233 ----------
1234 image : `lsst.afw.image.exposure.exposure.ExposureF`
1235 The image into which the fake sources should be added
1236 fakeImages : `typing.Iterator` [`tuple` ['lsst.afw.image.ImageF`, `lsst.geom.Point2d`]]
1237 An iterator of tuples that contains (or generates) images of fake sources,
1238 and the locations they are to be inserted at.
1239 sourceType : `str`
1240 The type (star/galaxy) of fake sources input
1241
1242 Returns
1243 -------
1244 image : `lsst.afw.image.exposure.exposure.ExposureF`
1245
1246 Notes
1247 -----
1248 Uses the x, y information in the ``fakeCat`` to position an image of the fake interpolated onto the
1249 pixel grid of the image. Sets the ``FAKE`` mask plane for the pixels added with the fake source.
1250 """
1251
1252 imageBBox = image.getBBox()
1253 imageMI = image.maskedImage
1254
1255 for (fakeImage, xy) in fakeImages:
1256 X0 = xy.getX() - fakeImage.getWidth()/2 + 0.5
1257 Y0 = xy.getY() - fakeImage.getHeight()/2 + 0.5
1258 self.log.debug("Adding fake source at %d, %d", xy.getX(), xy.getY())
1259 if sourceType == "galaxy":
1260 interpFakeImage = afwMath.offsetImage(fakeImage, X0, Y0, "lanczos3")
1261 else:
1262 interpFakeImage = fakeImage
1263
1264 interpFakeImBBox = interpFakeImage.getBBox()
1265 interpFakeImBBox.clip(imageBBox)
1266
1267 if interpFakeImBBox.getArea() > 0:
1268 imageMIView = imageMI[interpFakeImBBox]
1269 clippedFakeImage = interpFakeImage[interpFakeImBBox]
1270 clippedFakeImageMI = afwImage.MaskedImageF(clippedFakeImage)
1271 clippedFakeImageMI.mask.set(self.bitmask)
1272 imageMIView += clippedFakeImageMI
1273
1274 return image
def addPixCoords(self, fakeCat, image)
Definition: insertFakes.py:996
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:909
def trimFakeCat(self, fakeCat, image)
def addFakeSources(self, image, fakeImages, sourceType)