23 Insert fake sources into calexps
25 from astropy.table
import Table
30 from .insertFakes
import InsertFakesTask
34 from lsst.pipe.base import PipelineTask, PipelineTaskConfig, CmdLineTask, PipelineTaskConnections
39 __all__ = [
"ProcessCcdWithFakesConfig",
"ProcessCcdWithFakesTask"]
43 dimensions=(
"skymap",
"tract",
"instrument",
"visit",
"detector"),
44 defaultTemplates={
"CoaddName":
"deep",
"wcsName":
"jointcal",
45 "photoCalibName":
"jointcal"}):
48 doc=
"Exposure into which fakes are to be added.",
50 storageClass=
"ExposureF",
51 dimensions=(
"instrument",
"visit",
"detector")
55 doc=
"Catalog of fake sources to draw inputs from.",
56 name=
"{CoaddName}Coadd_fakeSourceCat",
57 storageClass=
"DataFrame",
58 dimensions=(
"tract",
"skymap")
62 doc=
"WCS information for the input exposure.",
65 dimensions=(
"tract",
"skymap",
"instrument",
"visit",
"detector")
68 photoCalib = cT.Input(
69 doc=
"Calib information for the input exposure.",
70 name=
"{photoCalibName}_photoCalib",
71 storageClass=
"PhotoCalib",
72 dimensions=(
"tract",
"skymap",
"instrument",
"visit",
"detector")
75 icSourceCat = cT.Input(
76 doc=
"Catalog of calibration sources",
78 storageClass=
"SourceCatalog",
79 dimensions=(
"instrument",
"visit",
"detector")
82 sfdSourceCat = cT.Input(
83 doc=
"Catalog of calibration sources",
85 storageClass=
"SourceCatalog",
86 dimensions=(
"instrument",
"visit",
"detector")
89 outputExposure = cT.Output(
90 doc=
"Exposure with fake sources added.",
92 storageClass=
"ExposureF",
93 dimensions=(
"instrument",
"visit",
"detector")
96 outputCat = cT.Output(
97 doc=
"Source catalog produced in calibrate task with fakes also measured.",
99 storageClass=
"SourceCatalog",
100 dimensions=(
"instrument",
"visit",
"detector"),
106 if config.doApplyExternalSkyWcs
is False:
108 if config.doApplyExternalPhotoCalib
is False:
109 self.
inputs.remove(
"photoCalib")
113 pipelineConnections=ProcessCcdWithFakesConnections):
114 """Config for inserting fake sources
118 The default column names are those from the UW sims database.
121 doApplyExternalPhotoCalib = pexConfig.Field(
124 doc=
"Whether to apply an external photometric calibration via an "
125 "`lsst.afw.image.PhotoCalib` object. Uses the "
126 "`externalPhotoCalibName` config option to determine which "
127 "calibration to use."
130 externalPhotoCalibName = pexConfig.ChoiceField(
131 doc=
"What type of external photo calib to use.",
134 allowed={
"jointcal":
"Use jointcal_photoCalib",
135 "fgcm":
"Use fgcm_photoCalib",
136 "fgcm_tract":
"Use fgcm_tract_photoCalib"}
139 doApplyExternalSkyWcs = pexConfig.Field(
142 doc=
"Whether to apply an external astrometric calibration via an "
143 "`lsst.afw.geom.SkyWcs` object. Uses the "
144 "`externalSkyWcsName` config option to determine which "
145 "calibration to use."
148 externalSkyWcsName = pexConfig.ChoiceField(
149 doc=
"What type of updated WCS calib to use.",
152 allowed={
"jointcal":
"Use jointcal_wcs"}
155 coaddName = pexConfig.Field(
156 doc=
"The name of the type of coadd used",
161 srcFieldsToCopy = pexConfig.ListField(
163 default=(
"calib_photometry_reserved",
"calib_photometry_used",
"calib_astrometry_used",
164 "calib_psf_candidate",
"calib_psf_used",
"calib_psf_reserved"),
165 doc=(
"Fields to copy from the `src` catalog to the output catalog "
166 "for matching sources Any missing fields will trigger a "
167 "RuntimeError exception.")
170 matchRadiusPix = pexConfig.Field(
173 doc=(
"Match radius for matching icSourceCat objects to sourceCat objects (pixels)"),
176 calibrate = pexConfig.ConfigurableField(target=CalibrateTask,
177 doc=
"The calibration task to use.")
179 insertFakes = pexConfig.ConfigurableField(target=InsertFakesTask,
180 doc=
"Configuration for the fake sources")
184 self.calibrate.measurement.plugins[
"base_PixelFlags"].masksFpAnywhere.append(
"FAKE")
185 self.calibrate.measurement.plugins[
"base_PixelFlags"].masksFpCenter.append(
"FAKE")
186 self.calibrate.doAstrometry =
False
187 self.calibrate.doWriteMatches =
False
188 self.calibrate.doPhotoCal =
False
189 self.calibrate.detection.reEstimateBackground =
False
193 """Insert fake objects into calexps.
195 Add fake stars and galaxies to the given calexp, specified in the dataRef. Galaxy parameters are read in
196 from the specified file and then modelled using galsim. Re-runs characterize image and calibrate image to
197 give a new background estimation and measurement of the calexp.
199 `ProcessFakeSourcesTask` inherits six functions from insertFakesTask that make images of the fake
200 sources and then add them to the calexp.
203 Use the WCS information to add the pixel coordinates of each source
204 Adds an ``x`` and ``y`` column to the catalog of fake sources.
206 Trim the fake cat to about the size of the input image.
207 `mkFakeGalsimGalaxies`
208 Use Galsim to make fake double sersic galaxies for each set of galaxy parameters in the input file.
210 Use the PSF information from the calexp to make a fake star using the magnitude information from the
213 Remove rows of the input fake catalog which have half light radius, of either the bulge or the disk,
216 Add the fake sources to the calexp.
220 The ``calexp`` with fake souces added to it is written out as the datatype ``calexp_fakes``.
223 _DefaultName =
"processCcdWithFakes"
224 ConfigClass = ProcessCcdWithFakesConfig
226 def __init__(self, schema=None, butler=None, **kwargs):
227 """Initalize things! This should go above in the class docstring
230 super().__init__(**kwargs)
233 schema = SourceTable.makeMinimalSchema()
235 self.makeSubtask(
"insertFakes")
236 self.makeSubtask(
"calibrate")
238 def runDataRef(self, dataRef):
239 """Read in/write out the required data products and add fake sources to the calexp.
243 dataRef : `lsst.daf.persistence.butlerSubset.ButlerDataRef`
244 Data reference defining the ccd to have fakes added to it.
245 Used to access the following data products:
252 Uses the calibration and WCS information attached to the calexp for the posistioning and calibration
253 of the sources unless the config option config.externalPhotoCalibName or config.externalSkyWcsName
254 are set then it uses the specified outputs. The config defualts for the column names in the catalog
255 of fakes are taken from the University of Washington simulations database.
256 Operates on one ccd at a time.
258 exposureIdInfo = dataRef.get(
"expIdInfo")
260 if self.config.insertFakes.fakeType ==
"snapshot":
261 fakeCat = dataRef.get(
"fakeSourceCat").toDataFrame()
262 elif self.config.insertFakes.fakeType ==
"static":
263 fakeCat = dataRef.get(
"deepCoadd_fakeSourceCat").toDataFrame()
265 fakeCat = Table.read(self.config.insertFakes.fakeType).to_pandas()
267 calexp = dataRef.get(
"calexp")
268 if self.config.doApplyExternalSkyWcs:
269 self.log.
info(
"Using external wcs from " + self.config.externalSkyWcsName)
270 wcs = dataRef.get(self.config.externalSkyWcsName +
"_wcs")
272 wcs = calexp.getWcs()
274 if self.config.doApplyExternalPhotoCalib:
275 self.log.
info(
"Using external photocalib from " + self.config.externalPhotoCalibName)
276 photoCalib = dataRef.get(self.config.externalPhotoCalibName +
"_photoCalib")
278 photoCalib = calexp.getPhotoCalib()
280 icSourceCat = dataRef.get(
"icSrc", immediate=
True)
281 sfdSourceCat = dataRef.get(
"src", immediate=
True)
283 resultStruct = self.run(fakeCat, calexp, wcs=wcs, photoCalib=photoCalib,
284 exposureIdInfo=exposureIdInfo, icSourceCat=icSourceCat,
285 sfdSourceCat=sfdSourceCat)
287 dataRef.put(resultStruct.outputExposure,
"fakes_calexp")
288 dataRef.put(resultStruct.outputCat,
"fakes_src")
291 def runQuantum(self, butlerQC, inputRefs, outputRefs):
292 inputs = butlerQC.get(inputRefs)
293 if 'exposureIdInfo' not in inputs.keys():
294 expId, expBits = butlerQC.quantum.dataId.pack(
"visit_detector", returnMaxBits=
True)
297 if not self.config.doApplyExternalSkyWcs:
298 inputs[
"wcs"] = inputs[
"image"].getWcs()
300 if not self.config.doApplyExternalPhotoCalib:
301 inputs[
"photoCalib"] = inputs[
"image"].getPhotoCalib()
303 outputs = self.run(**inputs)
304 butlerQC.put(outputs, outputRefs)
307 def _makeArgumentParser(cls):
308 parser = pipeBase.ArgumentParser(name=cls._DefaultName)
309 parser.add_id_argument(
"--id",
"fakes_calexp", help=
"data ID with raw CCD keys [+ tract optionally], "
310 "e.g. --id visit=12345 ccd=1,2 [tract=0]",
311 ContainerClass=PerTractCcdDataIdContainer)
314 def run(self, fakeCat, exposure, wcs=None, photoCalib=None, exposureIdInfo=None, icSourceCat=None,
316 """Add fake sources to a calexp and then run detection, deblending and measurement.
320 fakeCat : `pandas.core.frame.DataFrame`
321 The catalog of fake sources to add to the exposure
322 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
323 The exposure to add the fake sources to
324 wcs : `lsst.afw.geom.SkyWcs`
325 WCS to use to add fake sources
326 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
327 Photometric calibration to be used to calibrate the fake sources
328 exposureIdInfo : `lsst.obs.base.ExposureIdInfo`
329 icSourceCat : `lsst.afw.table.SourceCatalog`
331 Catalog to take the information about which sources were used for calibration from.
332 sfdSourceCat : `lsst.afw.table.SourceCatalog`
334 Catalog produced by singleFrameDriver, needed to copy some calibration flags from.
338 resultStruct : `lsst.pipe.base.struct.Struct`
339 contains : outputExposure : `lsst.afw.image.exposure.exposure.ExposureF`
340 outputCat : `lsst.afw.table.source.source.SourceCatalog`
344 Adds pixel coordinates for each source to the fakeCat and removes objects with bulge or disk half
345 light radius = 0 (if ``config.cleanCat = True``). These columns are called ``x`` and ``y`` and are in
348 Adds the ``Fake`` mask plane to the exposure which is then set by `addFakeSources` to mark where fake
349 sources have been added. Uses the information in the ``fakeCat`` to make fake galaxies (using galsim)
350 and fake stars, using the PSF models from the PSF information for the calexp. These are then added to
351 the calexp and the calexp with fakes included returned.
353 The galsim galaxies are made using a double sersic profile, one for the bulge and one for the disk,
354 this is then convolved with the PSF at that point.
356 If exposureIdInfo is not provided then the SourceCatalog IDs will not be globally unique.
360 wcs = exposure.getWcs()
362 if photoCalib
is None:
363 photoCalib = exposure.getPhotoCalib()
365 self.insertFakes.
run(fakeCat, exposure, wcs, photoCalib)
368 if exposureIdInfo
is None:
370 returnedStruct = self.calibrate.
run(exposure, exposureIdInfo=exposureIdInfo)
371 sourceCat = returnedStruct.sourceCat
373 sourceCat = self.copyCalibrationFields(sfdSourceCat, sourceCat, self.config.srcFieldsToCopy)
375 resultStruct = pipeBase.Struct(outputExposure=exposure, outputCat=sourceCat)
378 def copyCalibrationFields(self, calibCat, sourceCat, fieldsToCopy):
379 """Match sources in calibCat and sourceCat and copy the specified fields
383 calibCat : `lsst.afw.table.SourceCatalog`
384 Catalog from which to copy fields.
385 sourceCat : `lsst.afw.table.SourceCatalog`
386 Catalog to which to copy fields.
387 fieldsToCopy : `lsst.pex.config.listField.List`
388 Fields to copy from calibCat to SoourceCat.
392 newCat : `lsst.afw.table.SourceCatalog`
393 Catalog which includes the copied fields.
395 The fields copied are those specified by `fieldsToCopy` that actually exist
396 in the schema of `calibCat`.
398 This version was based on and adapted from the one in calibrateTask.
403 sourceSchemaMapper.addMinimalSchema(sourceCat.schema,
True)
408 missingFieldNames = []
409 for fieldName
in fieldsToCopy:
410 if fieldName
in calibCat.schema:
411 schemaItem = calibCat.schema.find(fieldName)
412 calibSchemaMapper.editOutputSchema().addField(schemaItem.getField())
413 schema = calibSchemaMapper.editOutputSchema()
414 calibSchemaMapper.addMapping(schemaItem.getKey(), schema.find(fieldName).getField())
416 missingFieldNames.append(fieldName)
417 if missingFieldNames:
418 raise RuntimeError(f
"calibCat is missing fields {missingFieldNames} specified in "
421 if "calib_detected" not in calibSchemaMapper.getOutputSchema():
422 self.calibSourceKey = calibSchemaMapper.addOutputField(
afwTable.Field[
"Flag"](
"calib_detected",
423 "Source was detected as an icSource"))
425 self.calibSourceKey =
None
427 schema = calibSchemaMapper.getOutputSchema()
429 newCat.reserve(len(sourceCat))
430 newCat.extend(sourceCat, sourceSchemaMapper)
433 for k, v
in sourceCat.schema.getAliasMap().
items():
434 newCat.schema.getAliasMap().
set(k, v)
436 select = newCat[
"deblend_nChild"] == 0
437 matches =
afwTable.matchXy(newCat[select], calibCat, self.config.matchRadiusPix)
441 numMatches = len(matches)
442 numUniqueSources = len(
set(m[1].getId()
for m
in matches))
443 if numUniqueSources != numMatches:
444 self.log.
warn(
"%d calibCat sources matched only %d sourceCat sources", numMatches,
447 self.log.
info(
"Copying flags from calibCat to sourceCat for %s sources", numMatches)
451 for src, calibSrc, d
in matches:
452 if self.calibSourceKey:
453 src.setFlag(self.calibSourceKey,
True)
458 calibSrcFootprint = calibSrc.getFootprint()
460 calibSrc.setFootprint(src.getFootprint())
461 src.assign(calibSrc, calibSchemaMapper)
463 calibSrc.setFootprint(calibSrcFootprint)