LSST Applications g0b6bd0c080+a72a5dd7e6,g1182afd7b4+2a019aa3bb,g17e5ecfddb+2b8207f7de,g1d67935e3f+06cf436103,g38293774b4+ac198e9f13,g396055baef+6a2097e274,g3b44f30a73+6611e0205b,g480783c3b1+98f8679e14,g48ccf36440+89c08d0516,g4b93dc025c+98f8679e14,g5c4744a4d9+a302e8c7f0,g613e996a0d+e1c447f2e0,g6c8d09e9e7+25247a063c,g7271f0639c+98f8679e14,g7a9cd813b8+124095ede6,g9d27549199+a302e8c7f0,ga1cf026fa3+ac198e9f13,ga32aa97882+7403ac30ac,ga786bb30fb+7a139211af,gaa63f70f4e+9994eb9896,gabf319e997+ade567573c,gba47b54d5d+94dc90c3ea,gbec6a3398f+06cf436103,gc6308e37c7+07dd123edb,gc655b1545f+ade567573c,gcc9029db3c+ab229f5caf,gd01420fc67+06cf436103,gd877ba84e5+06cf436103,gdb4cecd868+6f279b5b48,ge2d134c3d5+cc4dbb2e3f,ge448b5faa6+86d1ceac1d,gecc7e12556+98f8679e14,gf3ee170dca+25247a063c,gf4ac96e456+ade567573c,gf9f5ea5b4d+ac198e9f13,gff490e6085+8c2580be5c,w.2022.27
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"""
23Insert fake sources into calexps
24"""
25from astropy.table import Table
26import numpy as np
27import pandas as pd
28
29import lsst.pex.config as pexConfig
30import lsst.pipe.base as pipeBase
31
32from .insertFakes import InsertFakesTask
33from lsst.meas.base import PerTractCcdDataIdContainer
34from lsst.afw.table import SourceTable
35from lsst.obs.base import ExposureIdInfo
36from lsst.pipe.base import PipelineTask, PipelineTaskConfig, CmdLineTask, PipelineTaskConnections
37import lsst.pipe.base.connectionTypes as cT
38import lsst.afw.table as afwTable
39from lsst.skymap import BaseSkyMap
40from lsst.pipe.tasks.calibrate import CalibrateTask
41
42__all__ = ["ProcessCcdWithFakesConfig", "ProcessCcdWithFakesTask",
43 "ProcessCcdWithVariableFakesConfig", "ProcessCcdWithVariableFakesTask"]
44
45
46class ProcessCcdWithFakesConnections(PipelineTaskConnections,
47 dimensions=("instrument", "visit", "detector"),
48 defaultTemplates={"coaddName": "deep",
49 "wcsName": "jointcal",
50 "photoCalibName": "jointcal",
51 "fakesType": "fakes_"}):
52 skyMap = cT.Input(
53 doc="Input definition of geometry/bbox and projection/wcs for "
54 "template exposures. Needed to test which tract to generate ",
55 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
56 dimensions=("skymap",),
57 storageClass="SkyMap",
58 )
59
60 exposure = cT.Input(
61 doc="Exposure into which fakes are to be added.",
62 name="calexp",
63 storageClass="ExposureF",
64 dimensions=("instrument", "visit", "detector")
65 )
66
67 fakeCats = cT.Input(
68 doc="Set of catalogs of fake sources to draw inputs from. We "
69 "concatenate the tract catalogs for detectorVisits that cover "
70 "multiple tracts.",
71 name="{fakesType}fakeSourceCat",
72 storageClass="DataFrame",
73 dimensions=("tract", "skymap"),
74 deferLoad=True,
75 multiple=True,
76 )
77
78 externalSkyWcsTractCatalog = cT.Input(
79 doc=("Per-tract, per-visit wcs calibrations. These catalogs use the detector "
80 "id for the catalog id, sorted on id for fast lookup."),
81 name="{wcsName}SkyWcsCatalog",
82 storageClass="ExposureCatalog",
83 dimensions=("instrument", "visit", "tract", "skymap"),
84 deferLoad=True,
85 multiple=True,
86 )
87
88 externalSkyWcsGlobalCatalog = cT.Input(
89 doc=("Per-visit wcs calibrations computed globally (with no tract information). "
90 "These catalogs use the detector id for the catalog id, sorted on id for "
91 "fast lookup."),
92 name="{wcsName}SkyWcsCatalog",
93 storageClass="ExposureCatalog",
94 dimensions=("instrument", "visit"),
95 )
96
97 externalPhotoCalibTractCatalog = cT.Input(
98 doc=("Per-tract, per-visit photometric calibrations. These catalogs use the "
99 "detector id for the catalog id, sorted on id for fast lookup."),
100 name="{photoCalibName}PhotoCalibCatalog",
101 storageClass="ExposureCatalog",
102 dimensions=("instrument", "visit", "tract"),
103 deferLoad=True,
104 multiple=True,
105 )
106
107 externalPhotoCalibGlobalCatalog = cT.Input(
108 doc=("Per-visit photometric calibrations. These catalogs use the "
109 "detector id for the catalog id, sorted on id for fast lookup."),
110 name="{photoCalibName}PhotoCalibCatalog",
111 storageClass="ExposureCatalog",
112 dimensions=("instrument", "visit"),
113 )
114
115 icSourceCat = cT.Input(
116 doc="Catalog of calibration sources",
117 name="icSrc",
118 storageClass="SourceCatalog",
119 dimensions=("instrument", "visit", "detector")
120 )
121
122 sfdSourceCat = cT.Input(
123 doc="Catalog of calibration sources",
124 name="src",
125 storageClass="SourceCatalog",
126 dimensions=("instrument", "visit", "detector")
127 )
128
129 outputExposure = cT.Output(
130 doc="Exposure with fake sources added.",
131 name="{fakesType}calexp",
132 storageClass="ExposureF",
133 dimensions=("instrument", "visit", "detector")
134 )
135
136 outputCat = cT.Output(
137 doc="Source catalog produced in calibrate task with fakes also measured.",
138 name="{fakesType}src",
139 storageClass="SourceCatalog",
140 dimensions=("instrument", "visit", "detector"),
141 )
142
143 def __init__(self, *, config=None):
144 super().__init__(config=config)
145
146 if not config.doApplyExternalGlobalPhotoCalib:
147 self.inputs.remove("externalPhotoCalibGlobalCatalog")
148 if not config.doApplyExternalTractPhotoCalib:
149 self.inputs.remove("externalPhotoCalibTractCatalog")
150
151 if not config.doApplyExternalGlobalSkyWcs:
152 self.inputs.remove("externalSkyWcsGlobalCatalog")
153 if not config.doApplyExternalTractSkyWcs:
154 self.inputs.remove("externalSkyWcsTractCatalog")
155
156
157class ProcessCcdWithFakesConfig(PipelineTaskConfig,
158 pipelineConnections=ProcessCcdWithFakesConnections):
159 """Config for inserting fake sources
160
161 Notes
162 -----
163 The default column names are those from the UW sims database.
164 """
165
166 doApplyExternalGlobalPhotoCalib = pexConfig.Field(
167 dtype=bool,
168 default=False,
169 doc="Whether to apply an external photometric calibration via an "
170 "`lsst.afw.image.PhotoCalib` object. Uses the "
171 "`externalPhotoCalibName` config option to determine which "
172 "calibration to use. Uses a global calibration."
173 )
174
175 doApplyExternalTractPhotoCalib = pexConfig.Field(
176 dtype=bool,
177 default=False,
178 doc="Whether to apply an external photometric calibration via an "
179 "`lsst.afw.image.PhotoCalib` object. Uses the "
180 "`externalPhotoCalibName` config option to determine which "
181 "calibration to use. Uses a per tract calibration."
182 )
183
184 externalPhotoCalibName = pexConfig.ChoiceField(
185 doc="What type of external photo calib to use.",
186 dtype=str,
187 default="jointcal",
188 allowed={"jointcal": "Use jointcal_photoCalib",
189 "fgcm": "Use fgcm_photoCalib",
190 "fgcm_tract": "Use fgcm_tract_photoCalib"}
191 )
192
193 doApplyExternalGlobalSkyWcs = pexConfig.Field(
194 dtype=bool,
195 default=False,
196 doc="Whether to apply an external astrometric calibration via an "
197 "`lsst.afw.geom.SkyWcs` object. Uses the "
198 "`externalSkyWcsName` config option to determine which "
199 "calibration to use. Uses a global calibration."
200 )
201
202 doApplyExternalTractSkyWcs = pexConfig.Field(
203 dtype=bool,
204 default=False,
205 doc="Whether to apply an external astrometric calibration via an "
206 "`lsst.afw.geom.SkyWcs` object. Uses the "
207 "`externalSkyWcsName` config option to determine which "
208 "calibration to use. Uses a per tract calibration."
209 )
210
211 externalSkyWcsName = pexConfig.ChoiceField(
212 doc="What type of updated WCS calib to use.",
213 dtype=str,
214 default="jointcal",
215 allowed={"jointcal": "Use jointcal_wcs"}
216 )
217
218 coaddName = pexConfig.Field(
219 doc="The name of the type of coadd used",
220 dtype=str,
221 default="deep",
222 )
223
224 srcFieldsToCopy = pexConfig.ListField(
225 dtype=str,
226 default=("calib_photometry_reserved", "calib_photometry_used", "calib_astrometry_used",
227 "calib_psf_candidate", "calib_psf_used", "calib_psf_reserved"),
228 doc=("Fields to copy from the `src` catalog to the output catalog "
229 "for matching sources Any missing fields will trigger a "
230 "RuntimeError exception.")
231 )
232
233 matchRadiusPix = pexConfig.Field(
234 dtype=float,
235 default=3,
236 doc=("Match radius for matching icSourceCat objects to sourceCat objects (pixels)"),
237 )
238
239 doMatchVisit = pexConfig.Field(
240 dtype=bool,
241 default=False,
242 doc="Match visit to trim the fakeCat"
243 )
244
245 calibrate = pexConfig.ConfigurableField(target=CalibrateTask,
246 doc="The calibration task to use.")
247
248 insertFakes = pexConfig.ConfigurableField(target=InsertFakesTask,
249 doc="Configuration for the fake sources")
250
251 def setDefaults(self):
252 super().setDefaults()
253 self.calibrate.measurement.plugins["base_PixelFlags"].masksFpAnywhere.append("FAKE")
254 self.calibrate.measurement.plugins["base_PixelFlags"].masksFpCenter.append("FAKE")
255 self.calibrate.doAstrometry = False
256 self.calibrate.doWriteMatches = False
257 self.calibrate.doPhotoCal = False
258 self.calibrate.detection.reEstimateBackground = False
259
260
261class ProcessCcdWithFakesTask(PipelineTask, CmdLineTask):
262 """Insert fake objects into calexps.
263
264 Add fake stars and galaxies to the given calexp, specified in the dataRef. Galaxy parameters are read in
265 from the specified file and then modelled using galsim. Re-runs characterize image and calibrate image to
266 give a new background estimation and measurement of the calexp.
267
268 `ProcessFakeSourcesTask` inherits six functions from insertFakesTask that make images of the fake
269 sources and then add them to the calexp.
270
271 `addPixCoords`
272 Use the WCS information to add the pixel coordinates of each source
273 Adds an ``x`` and ``y`` column to the catalog of fake sources.
274 `trimFakeCat`
275 Trim the fake cat to about the size of the input image.
276 `mkFakeGalsimGalaxies`
277 Use Galsim to make fake double sersic galaxies for each set of galaxy parameters in the input file.
278 `mkFakeStars`
279 Use the PSF information from the calexp to make a fake star using the magnitude information from the
280 input file.
281 `cleanCat`
282 Remove rows of the input fake catalog which have half light radius, of either the bulge or the disk,
283 that are 0.
284 `addFakeSources`
285 Add the fake sources to the calexp.
286
287 Notes
288 -----
289 The ``calexp`` with fake souces added to it is written out as the datatype ``calexp_fakes``.
290 """
291
292 _DefaultName = "processCcdWithFakes"
293 ConfigClass = ProcessCcdWithFakesConfig
294
295 def __init__(self, schema=None, butler=None, **kwargs):
296 """Initalize things! This should go above in the class docstring
297 """
298
299 super().__init__(**kwargs)
300
301 if schema is None:
302 schema = SourceTable.makeMinimalSchema()
303 self.schema = schema
304 self.makeSubtask("insertFakes")
305 self.makeSubtask("calibrate")
306
307 def runDataRef(self, dataRef):
308 """Read in/write out the required data products and add fake sources to the calexp.
309
310 Parameters
311 ----------
313 Data reference defining the ccd to have fakes added to it.
314 Used to access the following data products:
315 calexp
316 jointcal_wcs
317 jointcal_photoCalib
318
319 Notes
320 -----
321 Uses the calibration and WCS information attached to the calexp for the posistioning and calibration
322 of the sources unless the config option config.externalPhotoCalibName or config.externalSkyWcsName
323 are set then it uses the specified outputs. The config defualts for the column names in the catalog
324 of fakes are taken from the University of Washington simulations database.
325 Operates on one ccd at a time.
326 """
327 exposureIdInfo = dataRef.get("expIdInfo")
328
329 if self.config.insertFakes.fakeType == "snapshot":
330 fakeCat = dataRef.get("fakeSourceCat").toDataFrame()
331 elif self.config.insertFakes.fakeType == "static":
332 fakeCat = dataRef.get("deepCoadd_fakeSourceCat").toDataFrame()
333 else:
334 fakeCat = Table.read(self.config.insertFakes.fakeType).to_pandas()
335
336 calexp = dataRef.get("calexp")
337 if self.config.doApplyExternalGlobalSkyWcs or self.config.doApplyExternalTractSkyWcs:
338 self.log.info("Using external wcs from %s", self.config.externalSkyWcsName)
339 wcs = dataRef.get(self.config.externalSkyWcsName + "_wcs")
340 else:
341 wcs = calexp.getWcs()
342
343 if self.config.doApplyExternalGlobalPhotoCalib or self.config.doApplyExternalTractPhotoCalib:
344 self.log.info("Using external photocalib from %s", self.config.externalPhotoCalibName)
345 photoCalib = dataRef.get(self.config.externalPhotoCalibName + "_photoCalib")
346 else:
347 photoCalib = calexp.getPhotoCalib()
348
349 icSourceCat = dataRef.get("icSrc", immediate=True)
350 sfdSourceCat = dataRef.get("src", immediate=True)
351
352 resultStruct = self.run(fakeCat, calexp, wcs=wcs, photoCalib=photoCalib,
353 exposureIdInfo=exposureIdInfo, icSourceCat=icSourceCat,
354 sfdSourceCat=sfdSourceCat)
355
356 dataRef.put(resultStruct.outputExposure, "fakes_calexp")
357 dataRef.put(resultStruct.outputCat, "fakes_src")
358 return resultStruct
359
360 def runQuantum(self, butlerQC, inputRefs, outputRefs):
361 inputs = butlerQC.get(inputRefs)
362 detectorId = inputs["exposure"].getInfo().getDetector().getId()
363
364 if 'exposureIdInfo' not in inputs.keys():
365 expId, expBits = butlerQC.quantum.dataId.pack("visit_detector", returnMaxBits=True)
366 inputs['exposureIdInfo'] = ExposureIdInfo(expId, expBits)
367
368 expWcs = inputs["exposure"].getWcs()
369 tractId = inputs["skyMap"].findTract(
370 expWcs.pixelToSky(inputs["exposure"].getBBox().getCenter())).tract_id
371 if not self.config.doApplyExternalGlobalSkyWcs and not self.config.doApplyExternalTractSkyWcs:
372 inputs["wcs"] = expWcs
373 elif self.config.doApplyExternalGlobalSkyWcs:
374 externalSkyWcsCatalog = inputs["externalSkyWcsGlobalCatalog"]
375 row = externalSkyWcsCatalog.find(detectorId)
376 inputs["wcs"] = row.getWcs()
377 elif self.config.doApplyExternalTractSkyWcs:
378 externalSkyWcsCatalogList = inputs["externalSkyWcsTractCatalog"]
379 externalSkyWcsCatalog = None
380 for externalSkyWcsCatalogRef in externalSkyWcsCatalogList:
381 if externalSkyWcsCatalogRef.dataId["tract"] == tractId:
382 externalSkyWcsCatalog = externalSkyWcsCatalogRef.get(
383 datasetType=self.config.connections.externalSkyWcsTractCatalog)
384 break
385 if externalSkyWcsCatalog is None:
386 usedTract = externalSkyWcsCatalogList[-1].dataId["tract"]
387 self.log.warn(
388 f"Warning, external SkyWcs for tract {tractId} not found. Using tract {usedTract} "
389 "instead.")
390 externalSkyWcsCatalog = externalSkyWcsCatalogList[-1].get(
391 datasetType=self.config.connections.externalSkyWcsTractCatalog)
392 row = externalSkyWcsCatalog.find(detectorId)
393 inputs["wcs"] = row.getWcs()
394
395 if not self.config.doApplyExternalGlobalPhotoCalib and not self.config.doApplyExternalTractPhotoCalib:
396 inputs["photoCalib"] = inputs["exposure"].getPhotoCalib()
397 elif self.config.doApplyExternalGlobalPhotoCalib:
398 externalPhotoCalibCatalog = inputs["externalPhotoCalibGlobalCatalog"]
399 row = externalPhotoCalibCatalog.find(detectorId)
400 inputs["photoCalib"] = row.getPhotoCalib()
401 elif self.config.doApplyExternalTractPhotoCalib:
402 externalPhotoCalibCatalogList = inputs["externalPhotoCalibTractCatalog"]
403 externalPhotoCalibCatalog = None
404 for externalPhotoCalibCatalogRef in externalPhotoCalibCatalogList:
405 if externalPhotoCalibCatalogRef.dataId["tract"] == tractId:
406 externalPhotoCalibCatalog = externalPhotoCalibCatalogRef.get(
407 datasetType=self.config.connections.externalPhotoCalibTractCatalog)
408 break
409 if externalPhotoCalibCatalog is None:
410 usedTract = externalPhotoCalibCatalogList[-1].dataId["tract"]
411 self.log.warn(
412 f"Warning, external PhotoCalib for tract {tractId} not found. Using tract {usedTract} "
413 "instead.")
414 externalPhotoCalibCatalog = externalPhotoCalibCatalogList[-1].get(
415 datasetType=self.config.connections.externalPhotoCalibTractCatalog)
416 row = externalPhotoCalibCatalog.find(detectorId)
417 inputs["photoCalib"] = row.getPhotoCalib()
418
419 outputs = self.run(**inputs)
420 butlerQC.put(outputs, outputRefs)
421
422 @classmethod
423 def _makeArgumentParser(cls):
424 parser = pipeBase.ArgumentParser(name=cls._DefaultName)
425 parser.add_id_argument("--id", "fakes_calexp", help="data ID with raw CCD keys [+ tract optionally], "
426 "e.g. --id visit=12345 ccd=1,2 [tract=0]",
427 ContainerClass=PerTractCcdDataIdContainer)
428 return parser
429
430 def run(self, fakeCats, exposure, skyMap, wcs=None, photoCalib=None, exposureIdInfo=None,
431 icSourceCat=None, sfdSourceCat=None, externalSkyWcsGlobalCatalog=None,
432 externalSkyWcsTractCatalog=None, externalPhotoCalibGlobalCatalog=None,
433 externalPhotoCalibTractCatalog=None):
434 """Add fake sources to a calexp and then run detection, deblending and measurement.
435
436 Parameters
437 ----------
438 fakeCats : `list` of `lsst.daf.butler.DeferredDatasetHandle`
439 Set of tract level fake catalogs that potentially cover
440 this detectorVisit.
441 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
442 The exposure to add the fake sources to
443 skyMap : `lsst.skymap.SkyMap`
444 SkyMap defining the tracts and patches the fakes are stored over.
446 WCS to use to add fake sources
447 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
448 Photometric calibration to be used to calibrate the fake sources
449 exposureIdInfo : `lsst.obs.base.ExposureIdInfo`
450 icSourceCat : `lsst.afw.table.SourceCatalog`
451 Default : None
452 Catalog to take the information about which sources were used for calibration from.
453 sfdSourceCat : `lsst.afw.table.SourceCatalog`
454 Default : None
455 Catalog produced by singleFrameDriver, needed to copy some calibration flags from.
456
457 Returns
458 -------
459 resultStruct : `lsst.pipe.base.struct.Struct`
460 contains : outputExposure : `lsst.afw.image.exposure.exposure.ExposureF`
461 outputCat : `lsst.afw.table.source.source.SourceCatalog`
462
463 Notes
464 -----
465 Adds pixel coordinates for each source to the fakeCat and removes objects with bulge or disk half
466 light radius = 0 (if ``config.cleanCat = True``). These columns are called ``x`` and ``y`` and are in
467 pixels.
468
469 Adds the ``Fake`` mask plane to the exposure which is then set by `addFakeSources` to mark where fake
470 sources have been added. Uses the information in the ``fakeCat`` to make fake galaxies (using galsim)
471 and fake stars, using the PSF models from the PSF information for the calexp. These are then added to
472 the calexp and the calexp with fakes included returned.
473
474 The galsim galaxies are made using a double sersic profile, one for the bulge and one for the disk,
475 this is then convolved with the PSF at that point.
476
477 If exposureIdInfo is not provided then the SourceCatalog IDs will not be globally unique.
478 """
479 fakeCat = self.composeFakeCat(fakeCats, skyMap)
480
481 if wcs is None:
482 wcs = exposure.getWcs()
483
484 if photoCalib is None:
485 photoCalib = exposure.getPhotoCalib()
486
487 if self.config.doMatchVisit:
488 fakeCat = self.getVisitMatchedFakeCat(fakeCat, exposure)
489
490 self.insertFakes.run(fakeCat, exposure, wcs, photoCalib)
491
492 # detect, deblend and measure sources
493 if exposureIdInfo is None:
494 exposureIdInfo = ExposureIdInfo()
495 returnedStruct = self.calibrate.run(exposure, exposureIdInfo=exposureIdInfo)
496 sourceCat = returnedStruct.sourceCat
497
498 sourceCat = self.copyCalibrationFields(sfdSourceCat, sourceCat, self.config.srcFieldsToCopy)
499
500 resultStruct = pipeBase.Struct(outputExposure=exposure, outputCat=sourceCat)
501 return resultStruct
502
503 def composeFakeCat(self, fakeCats, skyMap):
504 """Concatenate the fakeCats from tracts that may cover the exposure.
505
506 Parameters
507 ----------
508 fakeCats : `list` of `lst.daf.butler.DeferredDatasetHandle`
509 Set of fake cats to concatenate.
510 skyMap : `lsst.skymap.SkyMap`
511 SkyMap defining the geometry of the tracts and patches.
512
513 Returns
514 -------
515 combinedFakeCat : `pandas.DataFrame`
516 All fakes that cover the inner polygon of the tracts in this
517 quantum.
518 """
519 if len(fakeCats) == 1:
520 return fakeCats[0].get(
521 datasetType=self.config.connections.fakeCats)
522 outputCat = []
523 for fakeCatRef in fakeCats:
524 cat = fakeCatRef.get(
525 datasetType=self.config.connections.fakeCats)
526 tractId = fakeCatRef.dataId["tract"]
527 # Make sure all data is within the inner part of the tract.
528 outputCat.append(cat[
529 skyMap.findTractIdArray(cat[self.config.insertFakes.ra_col],
530 cat[self.config.insertFakes.dec_col],
531 degrees=False)
532 == tractId])
533
534 return pd.concat(outputCat)
535
536 def getVisitMatchedFakeCat(self, fakeCat, exposure):
537 """Trim the fakeCat to select particular visit
538
539 Parameters
540 ----------
541 fakeCat : `pandas.core.frame.DataFrame`
542 The catalog of fake sources to add to the exposure
543 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
544 The exposure to add the fake sources to
545
546 Returns
547 -------
548 movingFakeCat : `pandas.DataFrame`
549 All fakes that belong to the visit
550 """
551 selected = exposure.getInfo().getVisitInfo().getId() == fakeCat["visit"]
552
553 return fakeCat[selected]
554
555 def copyCalibrationFields(self, calibCat, sourceCat, fieldsToCopy):
556 """Match sources in calibCat and sourceCat and copy the specified fields
557
558 Parameters
559 ----------
561 Catalog from which to copy fields.
562 sourceCat : `lsst.afw.table.SourceCatalog`
563 Catalog to which to copy fields.
564 fieldsToCopy : `lsst.pex.config.listField.List`
565 Fields to copy from calibCat to SoourceCat.
566
567 Returns
568 -------
570 Catalog which includes the copied fields.
571
572 The fields copied are those specified by `fieldsToCopy` that actually exist
573 in the schema of `calibCat`.
574
575 This version was based on and adapted from the one in calibrateTask.
576 """
577
578 # Make a new SourceCatalog with the data from sourceCat so that we can add the new columns to it
579 sourceSchemaMapper = afwTable.SchemaMapper(sourceCat.schema)
580 sourceSchemaMapper.addMinimalSchema(sourceCat.schema, True)
581
582 calibSchemaMapper = afwTable.SchemaMapper(calibCat.schema, sourceCat.schema)
583
584 # Add the desired columns from the option fieldsToCopy
585 missingFieldNames = []
586 for fieldName in fieldsToCopy:
587 if fieldName in calibCat.schema:
588 schemaItem = calibCat.schema.find(fieldName)
589 calibSchemaMapper.editOutputSchema().addField(schemaItem.getField())
590 schema = calibSchemaMapper.editOutputSchema()
591 calibSchemaMapper.addMapping(schemaItem.getKey(), schema.find(fieldName).getField())
592 else:
593 missingFieldNames.append(fieldName)
594 if missingFieldNames:
595 raise RuntimeError(f"calibCat is missing fields {missingFieldNames} specified in "
596 "fieldsToCopy")
597
598 if "calib_detected" not in calibSchemaMapper.getOutputSchema():
599 self.calibSourceKey = calibSchemaMapper.addOutputField(afwTable.Field["Flag"]("calib_detected",
600 "Source was detected as an icSource"))
601 else:
602 self.calibSourceKey = None
603
604 schema = calibSchemaMapper.getOutputSchema()
605 newCat = afwTable.SourceCatalog(schema)
606 newCat.reserve(len(sourceCat))
607 newCat.extend(sourceCat, sourceSchemaMapper)
608
609 # Set the aliases so it doesn't complain.
610 for k, v in sourceCat.schema.getAliasMap().items():
611 newCat.schema.getAliasMap().set(k, v)
612
613 select = newCat["deblend_nChild"] == 0
614 matches = afwTable.matchXy(newCat[select], calibCat, self.config.matchRadiusPix)
615 # Check that no sourceCat sources are listed twice (we already know
616 # that each match has a unique calibCat source ID, due to using
617 # that ID as the key in bestMatches)
618 numMatches = len(matches)
619 numUniqueSources = len(set(m[1].getId() for m in matches))
620 if numUniqueSources != numMatches:
621 self.log.warning("%d calibCat sources matched only %d sourceCat sources", numMatches,
622 numUniqueSources)
623
624 self.log.info("Copying flags from calibCat to sourceCat for %s sources", numMatches)
625
626 # For each match: set the calibSourceKey flag and copy the desired
627 # fields
628 for src, calibSrc, d in matches:
629 if self.calibSourceKey:
630 src.setFlag(self.calibSourceKey, True)
631 # src.assign copies the footprint from calibSrc, which we don't want
632 # (DM-407)
633 # so set calibSrc's footprint to src's footprint before src.assign,
634 # then restore it
635 calibSrcFootprint = calibSrc.getFootprint()
636 try:
637 calibSrc.setFootprint(src.getFootprint())
638 src.assign(calibSrc, calibSchemaMapper)
639 finally:
640 calibSrc.setFootprint(calibSrcFootprint)
641
642 return newCat
643
644
645class ProcessCcdWithVariableFakesConnections(ProcessCcdWithFakesConnections):
646 ccdVisitFakeMagnitudes = cT.Output(
647 doc="Catalog of fakes with magnitudes scattered for this ccdVisit.",
648 name="{fakesType}ccdVisitFakeMagnitudes",
649 storageClass="DataFrame",
650 dimensions=("instrument", "visit", "detector"),
651 )
652
653
654class ProcessCcdWithVariableFakesConfig(ProcessCcdWithFakesConfig,
655 pipelineConnections=ProcessCcdWithVariableFakesConnections):
656 scatterSize = pexConfig.RangeField(
657 dtype=float,
658 default=0.4,
659 min=0,
660 max=100,
661 doc="Amount of scatter to add to the visit magnitude for variable "
662 "sources."
663 )
664
665
666class ProcessCcdWithVariableFakesTask(ProcessCcdWithFakesTask):
667 """As ProcessCcdWithFakes except add variablity to the fakes catalog
668 magnitude in the observed band for this ccdVisit.
669
670 Additionally, write out the modified magnitudes to the Butler.
671 """
672
673 _DefaultName = "processCcdWithVariableFakes"
674 ConfigClass = ProcessCcdWithVariableFakesConfig
675
676 def run(self, fakeCats, exposure, skyMap, wcs=None, photoCalib=None, exposureIdInfo=None,
677 icSourceCat=None, sfdSourceCat=None):
678 """Add fake sources to a calexp and then run detection, deblending and measurement.
679
680 Parameters
681 ----------
682 fakeCat : `pandas.core.frame.DataFrame`
683 The catalog of fake sources to add to the exposure
684 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
685 The exposure to add the fake sources to
686 skyMap : `lsst.skymap.SkyMap`
687 SkyMap defining the tracts and patches the fakes are stored over.
689 WCS to use to add fake sources
690 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
691 Photometric calibration to be used to calibrate the fake sources
692 exposureIdInfo : `lsst.obs.base.ExposureIdInfo`
693 icSourceCat : `lsst.afw.table.SourceCatalog`
694 Default : None
695 Catalog to take the information about which sources were used for calibration from.
696 sfdSourceCat : `lsst.afw.table.SourceCatalog`
697 Default : None
698 Catalog produced by singleFrameDriver, needed to copy some calibration flags from.
699
700 Returns
701 -------
702 resultStruct : `lsst.pipe.base.struct.Struct`
703 Results Strcut containing:
704
705 - outputExposure : Exposure with added fakes
706 (`lsst.afw.image.exposure.exposure.ExposureF`)
707 - outputCat : Catalog with detected fakes
708 (`lsst.afw.table.source.source.SourceCatalog`)
709 - ccdVisitFakeMagnitudes : Magnitudes that these fakes were
710 inserted with after being scattered (`pandas.DataFrame`)
711
712 Notes
713 -----
714 Adds pixel coordinates for each source to the fakeCat and removes objects with bulge or disk half
715 light radius = 0 (if ``config.cleanCat = True``). These columns are called ``x`` and ``y`` and are in
716 pixels.
717
718 Adds the ``Fake`` mask plane to the exposure which is then set by `addFakeSources` to mark where fake
719 sources have been added. Uses the information in the ``fakeCat`` to make fake galaxies (using galsim)
720 and fake stars, using the PSF models from the PSF information for the calexp. These are then added to
721 the calexp and the calexp with fakes included returned.
722
723 The galsim galaxies are made using a double sersic profile, one for the bulge and one for the disk,
724 this is then convolved with the PSF at that point.
725
726 If exposureIdInfo is not provided then the SourceCatalog IDs will not be globally unique.
727 """
728 fakeCat = self.composeFakeCat(fakeCats, skyMap)
729
730 if wcs is None:
731 wcs = exposure.getWcs()
732
733 if photoCalib is None:
734 photoCalib = exposure.getPhotoCalib()
735
736 if exposureIdInfo is None:
737 exposureIdInfo = ExposureIdInfo()
738
739 band = exposure.getFilter().bandLabel
740 ccdVisitMagnitudes = self.addVariablity(fakeCat, band, exposure, photoCalib, exposureIdInfo)
741
742 self.insertFakes.run(fakeCat, exposure, wcs, photoCalib)
743
744 # detect, deblend and measure sources
745 returnedStruct = self.calibrate.run(exposure, exposureIdInfo=exposureIdInfo)
746 sourceCat = returnedStruct.sourceCat
747
748 sourceCat = self.copyCalibrationFields(sfdSourceCat, sourceCat, self.config.srcFieldsToCopy)
749
750 resultStruct = pipeBase.Struct(outputExposure=exposure,
751 outputCat=sourceCat,
752 ccdVisitFakeMagnitudes=ccdVisitMagnitudes)
753 return resultStruct
754
755 def addVariablity(self, fakeCat, band, exposure, photoCalib, exposureIdInfo):
756 """Add scatter to the fake catalog visit magnitudes.
757
758 Currently just adds a simple Gaussian scatter around the static fake
759 magnitude. This function could be modified to return any number of
760 fake variability.
761
762 Parameters
763 ----------
764 fakeCat : `pandas.DataFrame`
765 Catalog of fakes to modify magnitudes of.
766 band : `str`
767 Current observing band to modify.
768 exposure : `lsst.afw.image.ExposureF`
769 Exposure fakes will be added to.
770 photoCalib : `lsst.afw.image.PhotoCalib`
771 Photometric calibration object of ``exposure``.
772 exposureIdInfo : `lsst.obs.base.ExposureIdInfo`
773 Exposure id information and metadata.
774
775 Returns
776 -------
777 dataFrame : `pandas.DataFrame`
778 DataFrame containing the values of the magnitudes to that will
779 be inserted into this ccdVisit.
780 """
781 expId = exposureIdInfo.expId
782 rng = np.random.default_rng(expId)
783 magScatter = rng.normal(loc=0,
784 scale=self.config.scatterSize,
785 size=len(fakeCat))
786 visitMagnitudes = fakeCat[self.insertFakes.config.mag_col % band] + magScatter
787 fakeCat.loc[:, self.insertFakes.config.mag_col % band] = visitMagnitudes
788 return pd.DataFrame(data={"variableMag": visitMagnitudes})
std::vector< SchemaItem< Flag > > * items
A 2-dimensional celestial WCS that transform pixels to ICRS RA/Dec, using the LSST standard for pixel...
Definition: SkyWcs.h:117
The photometric calibration of an exposure.
Definition: PhotoCalib.h:114
A mapping between the keys of two Schemas, used to copy data between them.
Definition: SchemaMapper.h:21
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, coaddExposures, bbox, wcs, dataIds, **kwargs)
Definition: getTemplate.py:596
A description of a field in a table.
Definition: Field.h:24