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