LSST Applications  21.0.0+04719a4bac,21.0.0-1-ga51b5d4+f5e6047307,21.0.0-11-g2b59f77+a9c1acf22d,21.0.0-11-ga42c5b2+86977b0b17,21.0.0-12-gf4ce030+76814010d2,21.0.0-13-g1721dae+760e7a6536,21.0.0-13-g3a573fe+768d78a30a,21.0.0-15-g5a7caf0+f21cbc5713,21.0.0-16-g0fb55c1+b60e2d390c,21.0.0-19-g4cded4ca+71a93a33c0,21.0.0-2-g103fe59+bb20972958,21.0.0-2-g45278ab+04719a4bac,21.0.0-2-g5242d73+3ad5d60fb1,21.0.0-2-g7f82c8f+8babb168e8,21.0.0-2-g8f08a60+06509c8b61,21.0.0-2-g8faa9b5+616205b9df,21.0.0-2-ga326454+8babb168e8,21.0.0-2-gde069b7+5e4aea9c2f,21.0.0-2-gecfae73+1d3a86e577,21.0.0-2-gfc62afb+3ad5d60fb1,21.0.0-25-g1d57be3cd+e73869a214,21.0.0-3-g357aad2+ed88757d29,21.0.0-3-g4a4ce7f+3ad5d60fb1,21.0.0-3-g4be5c26+3ad5d60fb1,21.0.0-3-g65f322c+e0b24896a3,21.0.0-3-g7d9da8d+616205b9df,21.0.0-3-ge02ed75+a9c1acf22d,21.0.0-4-g591bb35+a9c1acf22d,21.0.0-4-g65b4814+b60e2d390c,21.0.0-4-gccdca77+0de219a2bc,21.0.0-4-ge8a399c+6c55c39e83,21.0.0-5-gd00fb1e+05fce91b99,21.0.0-6-gc675373+3ad5d60fb1,21.0.0-64-g1122c245+4fb2b8f86e,21.0.0-7-g04766d7+cd19d05db2,21.0.0-7-gdf92d54+04719a4bac,21.0.0-8-g5674e7b+d1bd76f71f,master-gac4afde19b+a9c1acf22d,w.2021.13
LSST Data Management Base Package
processCcdWithFakes.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 fake sources into calexps
24 """
25 from astropy.table import Table
26 
27 import lsst.pex.config as pexConfig
28 import lsst.pipe.base as pipeBase
29 
30 from .insertFakes import InsertFakesTask
31 from lsst.meas.base import PerTractCcdDataIdContainer
32 from lsst.afw.table import SourceTable
33 from lsst.obs.base import ExposureIdInfo
34 from lsst.pipe.base import PipelineTask, PipelineTaskConfig, CmdLineTask, PipelineTaskConnections
36 import lsst.afw.table as afwTable
37 from lsst.pipe.tasks.calibrate import CalibrateTask
38 
39 __all__ = ["ProcessCcdWithFakesConfig", "ProcessCcdWithFakesTask"]
40 
41 
43  dimensions=("skymap", "tract", "instrument", "visit", "detector"),
44  defaultTemplates={"coaddName": "deep",
45  "wcsName": "jointcal",
46  "photoCalibName": "jointcal",
47  "fakesType": "fakes_"}):
48 
49  exposure = cT.Input(
50  doc="Exposure into which fakes are to be added.",
51  name="calexp",
52  storageClass="ExposureF",
53  dimensions=("instrument", "visit", "detector")
54  )
55 
56  fakeCat = cT.Input(
57  doc="Catalog of fake sources to draw inputs from.",
58  name="{fakesType}fakeSourceCat",
59  storageClass="DataFrame",
60  dimensions=("tract", "skymap")
61  )
62 
63  wcs = cT.Input(
64  doc="WCS information for the input exposure.",
65  name="{wcsName}_wcs",
66  storageClass="Wcs",
67  dimensions=("tract", "skymap", "instrument", "visit", "detector")
68  )
69 
70  photoCalib = cT.Input(
71  doc="Calib information for the input exposure.",
72  name="{photoCalibName}_photoCalib",
73  storageClass="PhotoCalib",
74  dimensions=("tract", "skymap", "instrument", "visit", "detector")
75  )
76 
77  icSourceCat = cT.Input(
78  doc="Catalog of calibration sources",
79  name="icSrc",
80  storageClass="SourceCatalog",
81  dimensions=("instrument", "visit", "detector")
82  )
83 
84  sfdSourceCat = cT.Input(
85  doc="Catalog of calibration sources",
86  name="src",
87  storageClass="SourceCatalog",
88  dimensions=("instrument", "visit", "detector")
89  )
90 
91  outputExposure = cT.Output(
92  doc="Exposure with fake sources added.",
93  name="{fakesType}calexp",
94  storageClass="ExposureF",
95  dimensions=("instrument", "visit", "detector")
96  )
97 
98  outputCat = cT.Output(
99  doc="Source catalog produced in calibrate task with fakes also measured.",
100  name="{fakesType}src",
101  storageClass="SourceCatalog",
102  dimensions=("instrument", "visit", "detector"),
103  )
104 
105  def __init__(self, *, config=None):
106  super().__init__(config=config)
107 
108  if config.doApplyExternalSkyWcs is False:
109  self.inputsinputs.remove("wcs")
110  if config.doApplyExternalPhotoCalib is False:
111  self.inputsinputs.remove("photoCalib")
112 
113 
114 class ProcessCcdWithFakesConfig(PipelineTaskConfig,
115  pipelineConnections=ProcessCcdWithFakesConnections):
116  """Config for inserting fake sources
117 
118  Notes
119  -----
120  The default column names are those from the UW sims database.
121  """
122 
123  doApplyExternalPhotoCalib = pexConfig.Field(
124  dtype=bool,
125  default=False,
126  doc="Whether to apply an external photometric calibration via an "
127  "`lsst.afw.image.PhotoCalib` object. Uses the "
128  "`externalPhotoCalibName` config option to determine which "
129  "calibration to use."
130  )
131 
132  externalPhotoCalibName = pexConfig.ChoiceField(
133  doc="What type of external photo calib to use.",
134  dtype=str,
135  default="jointcal",
136  allowed={"jointcal": "Use jointcal_photoCalib",
137  "fgcm": "Use fgcm_photoCalib",
138  "fgcm_tract": "Use fgcm_tract_photoCalib"}
139  )
140 
141  doApplyExternalSkyWcs = pexConfig.Field(
142  dtype=bool,
143  default=False,
144  doc="Whether to apply an external astrometric calibration via an "
145  "`lsst.afw.geom.SkyWcs` object. Uses the "
146  "`externalSkyWcsName` config option to determine which "
147  "calibration to use."
148  )
149 
150  externalSkyWcsName = pexConfig.ChoiceField(
151  doc="What type of updated WCS calib to use.",
152  dtype=str,
153  default="jointcal",
154  allowed={"jointcal": "Use jointcal_wcs"}
155  )
156 
157  coaddName = pexConfig.Field(
158  doc="The name of the type of coadd used",
159  dtype=str,
160  default="deep",
161  )
162 
163  srcFieldsToCopy = pexConfig.ListField(
164  dtype=str,
165  default=("calib_photometry_reserved", "calib_photometry_used", "calib_astrometry_used",
166  "calib_psf_candidate", "calib_psf_used", "calib_psf_reserved"),
167  doc=("Fields to copy from the `src` catalog to the output catalog "
168  "for matching sources Any missing fields will trigger a "
169  "RuntimeError exception.")
170  )
171 
172  matchRadiusPix = pexConfig.Field(
173  dtype=float,
174  default=3,
175  doc=("Match radius for matching icSourceCat objects to sourceCat objects (pixels)"),
176  )
177 
178  calibrate = pexConfig.ConfigurableField(target=CalibrateTask,
179  doc="The calibration task to use.")
180 
181  insertFakes = pexConfig.ConfigurableField(target=InsertFakesTask,
182  doc="Configuration for the fake sources")
183 
184  def setDefaults(self):
185  super().setDefaults()
186  self.calibrate.measurement.plugins["base_PixelFlags"].masksFpAnywhere.append("FAKE")
187  self.calibrate.measurement.plugins["base_PixelFlags"].masksFpCenter.append("FAKE")
188  self.calibrate.doAstrometry = False
189  self.calibrate.doWriteMatches = False
190  self.calibrate.doPhotoCal = False
191  self.calibrate.detection.reEstimateBackground = False
192 
193 
194 class ProcessCcdWithFakesTask(PipelineTask, CmdLineTask):
195  """Insert fake objects into calexps.
196 
197  Add fake stars and galaxies to the given calexp, specified in the dataRef. Galaxy parameters are read in
198  from the specified file and then modelled using galsim. Re-runs characterize image and calibrate image to
199  give a new background estimation and measurement of the calexp.
200 
201  `ProcessFakeSourcesTask` inherits six functions from insertFakesTask that make images of the fake
202  sources and then add them to the calexp.
203 
204  `addPixCoords`
205  Use the WCS information to add the pixel coordinates of each source
206  Adds an ``x`` and ``y`` column to the catalog of fake sources.
207  `trimFakeCat`
208  Trim the fake cat to about the size of the input image.
209  `mkFakeGalsimGalaxies`
210  Use Galsim to make fake double sersic galaxies for each set of galaxy parameters in the input file.
211  `mkFakeStars`
212  Use the PSF information from the calexp to make a fake star using the magnitude information from the
213  input file.
214  `cleanCat`
215  Remove rows of the input fake catalog which have half light radius, of either the bulge or the disk,
216  that are 0.
217  `addFakeSources`
218  Add the fake sources to the calexp.
219 
220  Notes
221  -----
222  The ``calexp`` with fake souces added to it is written out as the datatype ``calexp_fakes``.
223  """
224 
225  _DefaultName = "processCcdWithFakes"
226  ConfigClass = ProcessCcdWithFakesConfig
227 
228  def __init__(self, schema=None, butler=None, **kwargs):
229  """Initalize things! This should go above in the class docstring
230  """
231 
232  super().__init__(**kwargs)
233 
234  if schema is None:
235  schema = SourceTable.makeMinimalSchema()
236  self.schema = schema
237  self.makeSubtask("insertFakes")
238  self.makeSubtask("calibrate")
239 
240  def runDataRef(self, dataRef):
241  """Read in/write out the required data products and add fake sources to the calexp.
242 
243  Parameters
244  ----------
245  dataRef : `lsst.daf.persistence.butlerSubset.ButlerDataRef`
246  Data reference defining the ccd to have fakes added to it.
247  Used to access the following data products:
248  calexp
249  jointcal_wcs
250  jointcal_photoCalib
251 
252  Notes
253  -----
254  Uses the calibration and WCS information attached to the calexp for the posistioning and calibration
255  of the sources unless the config option config.externalPhotoCalibName or config.externalSkyWcsName
256  are set then it uses the specified outputs. The config defualts for the column names in the catalog
257  of fakes are taken from the University of Washington simulations database.
258  Operates on one ccd at a time.
259  """
260  exposureIdInfo = dataRef.get("expIdInfo")
261 
262  if self.config.insertFakes.fakeType == "snapshot":
263  fakeCat = dataRef.get("fakeSourceCat").toDataFrame()
264  elif self.config.insertFakes.fakeType == "static":
265  fakeCat = dataRef.get("deepCoadd_fakeSourceCat").toDataFrame()
266  else:
267  fakeCat = Table.read(self.config.insertFakes.fakeType).to_pandas()
268 
269  calexp = dataRef.get("calexp")
270  if self.config.doApplyExternalSkyWcs:
271  self.log.info("Using external wcs from " + self.config.externalSkyWcsName)
272  wcs = dataRef.get(self.config.externalSkyWcsName + "_wcs")
273  else:
274  wcs = calexp.getWcs()
275 
276  if self.config.doApplyExternalPhotoCalib:
277  self.log.info("Using external photocalib from " + self.config.externalPhotoCalibName)
278  photoCalib = dataRef.get(self.config.externalPhotoCalibName + "_photoCalib")
279  else:
280  photoCalib = calexp.getPhotoCalib()
281 
282  icSourceCat = dataRef.get("icSrc", immediate=True)
283  sfdSourceCat = dataRef.get("src", immediate=True)
284 
285  resultStruct = self.run(fakeCat, calexp, wcs=wcs, photoCalib=photoCalib,
286  exposureIdInfo=exposureIdInfo, icSourceCat=icSourceCat,
287  sfdSourceCat=sfdSourceCat)
288 
289  dataRef.put(resultStruct.outputExposure, "fakes_calexp")
290  dataRef.put(resultStruct.outputCat, "fakes_src")
291  return resultStruct
292 
293  def runQuantum(self, butlerQC, inputRefs, outputRefs):
294  inputs = butlerQC.get(inputRefs)
295  if 'exposureIdInfo' not in inputs.keys():
296  expId, expBits = butlerQC.quantum.dataId.pack("visit_detector", returnMaxBits=True)
297  inputs['exposureIdInfo'] = ExposureIdInfo(expId, expBits)
298 
299  if not self.config.doApplyExternalSkyWcs:
300  inputs["wcs"] = inputs["exposure"].getWcs()
301 
302  if not self.config.doApplyExternalPhotoCalib:
303  inputs["photoCalib"] = inputs["exposure"].getPhotoCalib()
304 
305  outputs = self.run(**inputs)
306  butlerQC.put(outputs, outputRefs)
307 
308  @classmethod
309  def _makeArgumentParser(cls):
310  parser = pipeBase.ArgumentParser(name=cls._DefaultName)
311  parser.add_id_argument("--id", "fakes_calexp", help="data ID with raw CCD keys [+ tract optionally], "
312  "e.g. --id visit=12345 ccd=1,2 [tract=0]",
313  ContainerClass=PerTractCcdDataIdContainer)
314  return parser
315 
316  def run(self, fakeCat, exposure, wcs=None, photoCalib=None, exposureIdInfo=None, icSourceCat=None,
317  sfdSourceCat=None):
318  """Add fake sources to a calexp and then run detection, deblending and measurement.
319 
320  Parameters
321  ----------
322  fakeCat : `pandas.core.frame.DataFrame`
323  The catalog of fake sources to add to the exposure
324  exposure : `lsst.afw.image.exposure.exposure.ExposureF`
325  The exposure to add the fake sources to
326  wcs : `lsst.afw.geom.SkyWcs`
327  WCS to use to add fake sources
328  photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
329  Photometric calibration to be used to calibrate the fake sources
330  exposureIdInfo : `lsst.obs.base.ExposureIdInfo`
331  icSourceCat : `lsst.afw.table.SourceCatalog`
332  Default : None
333  Catalog to take the information about which sources were used for calibration from.
334  sfdSourceCat : `lsst.afw.table.SourceCatalog`
335  Default : None
336  Catalog produced by singleFrameDriver, needed to copy some calibration flags from.
337 
338  Returns
339  -------
340  resultStruct : `lsst.pipe.base.struct.Struct`
341  contains : outputExposure : `lsst.afw.image.exposure.exposure.ExposureF`
342  outputCat : `lsst.afw.table.source.source.SourceCatalog`
343 
344  Notes
345  -----
346  Adds pixel coordinates for each source to the fakeCat and removes objects with bulge or disk half
347  light radius = 0 (if ``config.cleanCat = True``). These columns are called ``x`` and ``y`` and are in
348  pixels.
349 
350  Adds the ``Fake`` mask plane to the exposure which is then set by `addFakeSources` to mark where fake
351  sources have been added. Uses the information in the ``fakeCat`` to make fake galaxies (using galsim)
352  and fake stars, using the PSF models from the PSF information for the calexp. These are then added to
353  the calexp and the calexp with fakes included returned.
354 
355  The galsim galaxies are made using a double sersic profile, one for the bulge and one for the disk,
356  this is then convolved with the PSF at that point.
357 
358  If exposureIdInfo is not provided then the SourceCatalog IDs will not be globally unique.
359  """
360 
361  if wcs is None:
362  wcs = exposure.getWcs()
363 
364  if photoCalib is None:
365  photoCalib = exposure.getPhotoCalib()
366 
367  self.insertFakes.run(fakeCat, exposure, wcs, photoCalib)
368 
369  # detect, deblend and measure sources
370  if exposureIdInfo is None:
371  exposureIdInfo = ExposureIdInfo()
372  returnedStruct = self.calibrate.run(exposure, exposureIdInfo=exposureIdInfo)
373  sourceCat = returnedStruct.sourceCat
374 
375  sourceCat = self.copyCalibrationFields(sfdSourceCat, sourceCat, self.config.srcFieldsToCopy)
376 
377  resultStruct = pipeBase.Struct(outputExposure=exposure, outputCat=sourceCat)
378  return resultStruct
379 
380  def copyCalibrationFields(self, calibCat, sourceCat, fieldsToCopy):
381  """Match sources in calibCat and sourceCat and copy the specified fields
382 
383  Parameters
384  ----------
385  calibCat : `lsst.afw.table.SourceCatalog`
386  Catalog from which to copy fields.
387  sourceCat : `lsst.afw.table.SourceCatalog`
388  Catalog to which to copy fields.
389  fieldsToCopy : `lsst.pex.config.listField.List`
390  Fields to copy from calibCat to SoourceCat.
391 
392  Returns
393  -------
394  newCat : `lsst.afw.table.SourceCatalog`
395  Catalog which includes the copied fields.
396 
397  The fields copied are those specified by `fieldsToCopy` that actually exist
398  in the schema of `calibCat`.
399 
400  This version was based on and adapted from the one in calibrateTask.
401  """
402 
403  # Make a new SourceCatalog with the data from sourceCat so that we can add the new columns to it
404  sourceSchemaMapper = afwTable.SchemaMapper(sourceCat.schema)
405  sourceSchemaMapper.addMinimalSchema(sourceCat.schema, True)
406 
407  calibSchemaMapper = afwTable.SchemaMapper(calibCat.schema, sourceCat.schema)
408 
409  # Add the desired columns from the option fieldsToCopy
410  missingFieldNames = []
411  for fieldName in fieldsToCopy:
412  if fieldName in calibCat.schema:
413  schemaItem = calibCat.schema.find(fieldName)
414  calibSchemaMapper.editOutputSchema().addField(schemaItem.getField())
415  schema = calibSchemaMapper.editOutputSchema()
416  calibSchemaMapper.addMapping(schemaItem.getKey(), schema.find(fieldName).getField())
417  else:
418  missingFieldNames.append(fieldName)
419  if missingFieldNames:
420  raise RuntimeError(f"calibCat is missing fields {missingFieldNames} specified in "
421  "fieldsToCopy")
422 
423  if "calib_detected" not in calibSchemaMapper.getOutputSchema():
424  self.calibSourceKey = calibSchemaMapper.addOutputField(afwTable.Field["Flag"]("calib_detected",
425  "Source was detected as an icSource"))
426  else:
427  self.calibSourceKey = None
428 
429  schema = calibSchemaMapper.getOutputSchema()
430  newCat = afwTable.SourceCatalog(schema)
431  newCat.reserve(len(sourceCat))
432  newCat.extend(sourceCat, sourceSchemaMapper)
433 
434  # Set the aliases so it doesn't complain.
435  for k, v in sourceCat.schema.getAliasMap().items():
436  newCat.schema.getAliasMap().set(k, v)
437 
438  select = newCat["deblend_nChild"] == 0
439  matches = afwTable.matchXy(newCat[select], calibCat, self.config.matchRadiusPix)
440  # Check that no sourceCat sources are listed twice (we already know
441  # that each match has a unique calibCat source ID, due to using
442  # that ID as the key in bestMatches)
443  numMatches = len(matches)
444  numUniqueSources = len(set(m[1].getId() for m in matches))
445  if numUniqueSources != numMatches:
446  self.log.warn("%d calibCat sources matched only %d sourceCat sources", numMatches,
447  numUniqueSources)
448 
449  self.log.info("Copying flags from calibCat to sourceCat for %s sources", numMatches)
450 
451  # For each match: set the calibSourceKey flag and copy the desired
452  # fields
453  for src, calibSrc, d in matches:
454  if self.calibSourceKey:
455  src.setFlag(self.calibSourceKey, True)
456  # src.assign copies the footprint from calibSrc, which we don't want
457  # (DM-407)
458  # so set calibSrc's footprint to src's footprint before src.assign,
459  # then restore it
460  calibSrcFootprint = calibSrc.getFootprint()
461  try:
462  calibSrc.setFootprint(src.getFootprint())
463  src.assign(calibSrc, calibSchemaMapper)
464  finally:
465  calibSrc.setFootprint(calibSrcFootprint)
466 
467  return newCat
std::vector< SchemaItem< Flag > > * items
A mapping between the keys of two Schemas, used to copy data between them.
Definition: SchemaMapper.h:21
def __init__(self, *'PipelineTaskConfig' config=None)
Definition: connections.py:364
daf::base::PropertySet * set
Definition: fits.cc:912
SourceMatchVector matchXy(SourceCatalog const &cat1, SourceCatalog const &cat2, double radius, MatchControl const &mc=MatchControl())
Compute all tuples (s1,s2,d) where s1 belings to cat1, s2 belongs to cat2 and d, the distance between...
Definition: Match.cc:305
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, altMaskList=None, mask=None, supplementaryData=None)
A description of a field in a table.
Definition: Field.h:24