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