23 Insert fake sources into calexps
25 from astropy.table
import Table
27 import lsst.pex.config
as pexConfig
31 from .insertFakes
import InsertFakesTask
33 from lsst.meas.base import (SingleFrameMeasurementTask, ApplyApCorrTask, CatalogCalculationTask,
34 PerTractCcdDataIdContainer)
38 from lsst.pipe.base import PipelineTask, PipelineTaskConfig, CmdLineTask, PipelineTaskConnections
43 __all__ = [
"ProcessCcdWithFakesConfig",
"ProcessCcdWithFakesTask"]
47 defaultTemplates={
"CoaddName":
"deep"}):
50 doc=
"Exposure into which fakes are to be added.",
52 storageClass=
"ExposureF",
53 dimensions=(
"instrument",
"visit",
"detector")
57 doc=
"Catalog of fake sources to draw inputs from.",
58 name=
"{CoaddName}Coadd_fakeSourceCat",
59 storageClass=
"Parquet",
60 dimensions=(
"tract",
"skymap")
64 doc=
"WCS information for the input exposure.",
67 dimensions=(
"Tract",
"SkyMap",
"Instrument",
"Visit",
"Detector")
70 photoCalib = cT.Input(
71 doc=
"Calib information for the input exposure.",
72 name=
"jointcal_photoCalib",
73 storageClass=
"PhotoCalib",
74 dimensions=(
"Tract",
"SkyMap",
"Instrument",
"Visit",
"Detector")
77 icSourceCat = cT.Input(
78 doc=
"Catalog of calibration sources",
80 storageClass=
"sourceCatalog",
81 dimensions=(
"tract",
"skymap",
"instrument",
"visit",
"detector")
84 sfdSourceCat = cT.Input(
85 doc=
"Catalog of calibration sources",
87 storageClass=
"sourceCatalog",
88 dimensions=(
"tract",
"skymap",
"instrument",
"visit",
"detector")
91 outputExposure = cT.Output(
92 doc=
"Exposure with fake sources added.",
94 storageClass=
"ExposureF",
95 dimensions=(
"instrument",
"visit",
"detector")
98 outputCat = cT.Output(
99 doc=
"Source catalog produced in calibrate task with fakes also measured.",
101 storageClass=
"SourceCatalog",
102 dimensions=(
"instrument",
"visit",
"detector"),
107 pipelineConnections=ProcessCcdWithFakesConnections):
108 """Config for inserting fake sources
112 The default column names are those from the UW sims database.
115 useUpdatedCalibs = pexConfig.Field(
116 doc=
"Use updated calibs and wcs from jointcal?",
121 coaddName = pexConfig.Field(
122 doc=
"The name of the type of coadd used",
127 calibrationFieldsToCopy = pexConfig.ListField(
129 default=(
"calib_psf_candidate",
"calib_psf_used",
"calib_psf_reserved"),
130 doc=(
"Fields to copy from the icSource catalog to the output catalog "
131 "for matching sources Any missing fields will trigger a "
132 "RuntimeError exception.")
135 srcFieldsToCopy = pexConfig.ListField(
137 default=(
"calib_photometry_reserved",
"calib_photometry_used",
"calib_astrometry_used"),
138 doc=(
"Fields to copy from the `src` catalog to the output catalog "
139 "for matching sources Any missing fields will trigger a "
140 "RuntimeError exception.")
143 matchRadiusPix = pexConfig.Field(
146 doc=(
"Match radius for matching icSourceCat objects to sourceCat objects (pixels)"),
149 insertFakes = pexConfig.ConfigurableField(target=InsertFakesTask,
150 doc=
"Configuration for the fake sources")
152 detection = pexConfig.ConfigurableField(target=SourceDetectionTask,
153 doc=
"The detection task to use.")
155 deblend = pexConfig.ConfigurableField(target=SourceDeblendTask, doc=
"The deblending task to use.")
157 measurement = pexConfig.ConfigurableField(target=SingleFrameMeasurementTask,
158 doc=
"The measurement task to use")
160 applyApCorr = pexConfig.ConfigurableField(target=ApplyApCorrTask,
161 doc=
"The apply aperture correction task to use.")
163 catalogCalculation = pexConfig.ConfigurableField(target=CatalogCalculationTask,
164 doc=
"The catalog calculation task to use.")
166 skySources = pexConfig.ConfigurableField(target=SkyObjectsTask, doc=
"Generate sky sources")
169 self.detection.reEstimateBackground =
False
171 self.measurement.plugins[
"base_PixelFlags"].masksFpAnywhere.append(
"FAKE")
172 self.measurement.plugins[
"base_PixelFlags"].masksFpCenter.append(
"FAKE")
173 self.deblend.maxFootprintSize = 2000
174 self.measurement.plugins.names |= [
"base_LocalPhotoCalib",
"base_LocalWcs"]
178 """Insert fake objects into calexps.
180 Add fake stars and galaxies to the given calexp, specified in the dataRef. Galaxy parameters are read in
181 from the specified file and then modelled using galsim. Re-runs characterize image and calibrate image to
182 give a new background estimation and measurement of the calexp.
184 `ProcessFakeSourcesTask` inherits six functions from insertFakesTask that make images of the fake
185 sources and then add them to the calexp.
188 Use the WCS information to add the pixel coordinates of each source
189 Adds an ``x`` and ``y`` column to the catalog of fake sources.
191 Trim the fake cat to about the size of the input image.
192 `mkFakeGalsimGalaxies`
193 Use Galsim to make fake double sersic galaxies for each set of galaxy parameters in the input file.
195 Use the PSF information from the calexp to make a fake star using the magnitude information from the
198 Remove rows of the input fake catalog which have half light radius, of either the bulge or the disk,
201 Add the fake sources to the calexp.
205 The ``calexp`` with fake souces added to it is written out as the datatype ``calexp_fakes``.
208 _DefaultName =
"processCcdWithFakes"
209 ConfigClass = ProcessCcdWithFakesConfig
211 def __init__(self, schema=None, butler=None, **kwargs):
212 """Initalize things! This should go above in the class docstring
215 super().__init__(**kwargs)
218 schema = SourceTable.makeMinimalSchema()
220 self.makeSubtask(
"insertFakes")
222 self.makeSubtask(
"detection", schema=self.schema)
223 self.makeSubtask(
"deblend", schema=self.schema)
224 self.makeSubtask(
"measurement", schema=self.schema, algMetadata=self.algMetadata)
225 self.makeSubtask(
"applyApCorr", schema=self.schema)
226 self.makeSubtask(
"catalogCalculation", schema=self.schema)
227 self.makeSubtask(
"skySources")
228 self.skySourceKey = self.schema.addField(
"sky_source", type=
"Flag", doc=
"Sky objects.")
230 def runDataRef(self, dataRef):
231 """Read in/write out the required data products and add fake sources to the calexp.
235 dataRef : `lsst.daf.persistence.butlerSubset.ButlerDataRef`
236 Data reference defining the ccd to have fakes added to it.
237 Used to access the following data products:
244 Uses the calibration and WCS information attached to the calexp for the posistioning and calibration
245 of the sources unless the config option config.useUpdatedCalibs is set then it uses the
246 meas_mosaic/jointCal outputs. The config defualts for the column names in the catalog of fakes are
247 taken from the University of Washington simulations database. Operates on one ccd at a time.
249 exposureIdInfo = dataRef.get(
"expIdInfo")
251 if self.config.insertFakes.fakeType ==
"snapshot":
252 fakeCat = dataRef.get(
"fakeSourceCat").toDataFrame()
253 elif self.config.insertFakes.fakeType ==
"static":
254 fakeCat = dataRef.get(
"deepCoadd_fakeSourceCat").toDataFrame()
256 fakeCat = Table.read(self.config.insertFakes.fakeType).to_pandas()
258 calexp = dataRef.get(
"calexp")
259 if self.config.useUpdatedCalibs:
260 self.log.
info(
"Using updated calibs from meas_mosaic/jointCal")
261 wcs = dataRef.get(
"jointcal_wcs")
262 photoCalib = dataRef.get(
"jointcal_photoCalib")
264 wcs = calexp.getWcs()
265 photoCalib = calexp.getPhotoCalib()
267 icSourceCat = dataRef.get(
"icSrc", immediate=
True)
268 sfdSourceCat = dataRef.get(
"src", immediate=
True)
270 resultStruct = self.run(fakeCat, calexp, wcs=wcs, photoCalib=photoCalib,
271 exposureIdInfo=exposureIdInfo, icSourceCat=icSourceCat,
272 sfdSourceCat=sfdSourceCat)
274 dataRef.put(resultStruct.outputExposure,
"fakes_calexp")
275 dataRef.put(resultStruct.outputCat,
"fakes_src")
278 def runQuantum(self, butlerQC, inputRefs, outputRefs):
279 inputs = butlerQC.get(inputRefs)
280 if 'exposureIdInfo' not in inputs.keys():
281 expId, expBits = butlerQC.quantum.dataId.pack(
"visit_detector", returnMaxBits=
True)
284 if inputs[
"wcs"]
is None:
285 inputs[
"wcs"] = inputs[
"image"].getWcs()
286 if inputs[
"photoCalib"]
is None:
287 inputs[
"photoCalib"] = inputs[
"image"].getPhotoCalib()
289 outputs = self.run(**inputs)
290 butlerQC.put(outputs, outputRefs)
293 def _makeArgumentParser(cls):
294 parser = pipeBase.ArgumentParser(name=cls._DefaultName)
295 parser.add_id_argument(
"--id",
"fakes_calexp", help=
"data ID with raw CCD keys [+ tract optionally], "
296 "e.g. --id visit=12345 ccd=1,2 [tract=0]",
297 ContainerClass=PerTractCcdDataIdContainer)
300 def run(self, fakeCat, exposure, wcs=None, photoCalib=None, exposureIdInfo=None, icSourceCat=None,
302 """Add fake sources to a calexp and then run detection, deblending and measurement.
306 fakeCat : `pandas.core.frame.DataFrame`
307 The catalog of fake sources to add to the exposure
308 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
309 The exposure to add the fake sources to
310 wcs : `lsst.afw.geom.SkyWcs`
311 WCS to use to add fake sources
312 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
313 Photometric calibration to be used to calibrate the fake sources
314 exposureIdInfo : `lsst.obs.base.ExposureIdInfo`
315 icSourceCat : `lsst.afw.table.SourceCatalog`
317 Catalog to take the information about which sources were used for calibration from.
318 sfdSourceCat : `lsst.afw.table.SourceCatalog`
320 Catalog produced by singleFrameDriver, needed to copy some calibration flags from.
324 resultStruct : `lsst.pipe.base.struct.Struct`
325 contains : outputExposure : `lsst.afw.image.exposure.exposure.ExposureF`
326 outputCat : `lsst.afw.table.source.source.SourceCatalog`
330 Adds pixel coordinates for each source to the fakeCat and removes objects with bulge or disk half
331 light radius = 0 (if ``config.cleanCat = True``). These columns are called ``x`` and ``y`` and are in
334 Adds the ``Fake`` mask plane to the exposure which is then set by `addFakeSources` to mark where fake
335 sources have been added. Uses the information in the ``fakeCat`` to make fake galaxies (using galsim)
336 and fake stars, using the PSF models from the PSF information for the calexp. These are then added to
337 the calexp and the calexp with fakes included returned.
339 The galsim galaxies are made using a double sersic profile, one for the bulge and one for the disk,
340 this is then convolved with the PSF at that point.
342 If exposureIdInfo is not provided then the SourceCatalog IDs will not be globally unique.
346 wcs = exposure.getWcs()
348 if photoCalib
is None:
349 photoCalib = exposure.getPhotoCalib()
351 self.insertFakes.
run(fakeCat, exposure, wcs, photoCalib)
354 if exposureIdInfo
is None:
357 sourceIdFactory = IdFactory.makeSource(exposureIdInfo.expId, exposureIdInfo.unusedBits)
358 table = SourceTable.make(self.schema, sourceIdFactory)
359 table.setMetadata(self.algMetadata)
361 detRes = self.detection.
run(table=table, exposure=exposure, doSmooth=
True)
362 sourceCat = detRes.sources
363 skySourceFootprints = self.skySources.
run(mask=exposure.mask, seed=exposureIdInfo.expId)
364 if skySourceFootprints:
365 for foot
in skySourceFootprints:
366 s = sourceCat.addNew()
368 s.set(self.skySourceKey,
True)
369 self.deblend.
run(exposure=exposure, sources=sourceCat)
370 self.measurement.
run(measCat=sourceCat, exposure=exposure, exposureId=exposureIdInfo.expId)
371 self.applyApCorr.
run(catalog=sourceCat, apCorrMap=exposure.getInfo().getApCorrMap())
372 self.catalogCalculation.
run(sourceCat)
373 sourceCat = self.copyCalibrationFields(icSourceCat, sourceCat, self.config.calibrationFieldsToCopy)
374 sourceCat = self.copyCalibrationFields(sfdSourceCat, sourceCat, self.config.srcFieldsToCopy)
376 resultStruct = pipeBase.Struct(outputExposure=exposure, outputCat=sourceCat)
379 def copyCalibrationFields(self, calibCat, sourceCat, fieldsToCopy):
380 """Match sources in calibCat and sourceCat and copy the specified fields
384 calibCat : `lsst.afw.table.SourceCatalog`
385 Catalog from which to copy fields.
386 sourceCat : `lsst.afw.table.SourceCatalog`
387 Catalog to which to copy fields.
388 fieldsToCopy : `lsst.pex.config.listField.List`
389 Fields to copy from calibCat to SoourceCat.
393 newCat : `lsst.afw.table.SourceCatalog`
394 Catalog which includes the copied fields.
396 The fields copied are those specified by `fieldsToCopy` that actually exist
397 in the schema of `calibCat`.
399 This version was based on and adapted from the one in calibrateTask.
404 sourceSchemaMapper.addMinimalSchema(sourceCat.schema,
True)
409 missingFieldNames = []
410 for fieldName
in fieldsToCopy:
411 if fieldName
in calibCat.schema:
412 schemaItem = calibCat.schema.find(fieldName)
413 calibSchemaMapper.editOutputSchema().addField(schemaItem.getField())
414 schema = calibSchemaMapper.editOutputSchema()
415 calibSchemaMapper.addMapping(schemaItem.getKey(), schema.find(fieldName).getField())
417 missingFieldNames.append(fieldName)
418 if missingFieldNames:
419 raise RuntimeError(f
"calibCat is missing fields {missingFieldNames} specified in "
422 if "calib_detected" not in calibSchemaMapper.getOutputSchema():
423 self.calibSourceKey = calibSchemaMapper.addOutputField(
afwTable.Field[
"Flag"](
"calib_detected",
424 "Source was detected as an icSource"))
426 self.calibSourceKey =
None
428 schema = calibSchemaMapper.getOutputSchema()
430 newCat.reserve(len(sourceCat))
431 newCat.extend(sourceCat, sourceSchemaMapper)
434 for k, v
in sourceCat.schema.getAliasMap().
items():
435 newCat.schema.getAliasMap().
set(k, v)
437 select = newCat[
"deblend_nChild"] == 0
438 matches =
afwTable.matchXy(newCat[select], calibCat, self.config.matchRadiusPix)
442 numMatches = len(matches)
443 numUniqueSources = len(
set(m[1].getId()
for m
in matches))
444 if numUniqueSources != numMatches:
445 self.log.
warn(
"%d calibCat sources matched only %d sourceCat sources", numMatches,
448 self.log.
info(
"Copying flags from calibCat to sourceCat for %s sources", numMatches)
452 for src, calibSrc, d
in matches:
453 if self.calibSourceKey:
454 src.setFlag(self.calibSourceKey,
True)
459 calibSrcFootprint = calibSrc.getFootprint()
461 calibSrc.setFootprint(src.getFootprint())
462 src.assign(calibSrc, calibSchemaMapper)
464 calibSrc.setFootprint(calibSrcFootprint)