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 defaultTemplates={
"CoaddName":
"deep"}):
46 doc=
"Exposure into which fakes are to be added.",
48 storageClass=
"ExposureF",
49 dimensions=(
"instrument",
"visit",
"detector")
53 doc=
"Catalog of fake sources to draw inputs from.",
54 name=
"{CoaddName}Coadd_fakeSourceCat",
55 storageClass=
"Parquet",
56 dimensions=(
"tract",
"skymap")
60 doc=
"WCS information for the input exposure.",
63 dimensions=(
"Tract",
"SkyMap",
"Instrument",
"Visit",
"Detector")
66 photoCalib = cT.Input(
67 doc=
"Calib information for the input exposure.",
68 name=
"jointcal_photoCalib",
69 storageClass=
"PhotoCalib",
70 dimensions=(
"Tract",
"SkyMap",
"Instrument",
"Visit",
"Detector")
73 icSourceCat = cT.Input(
74 doc=
"Catalog of calibration sources",
76 storageClass=
"sourceCatalog",
77 dimensions=(
"tract",
"skymap",
"instrument",
"visit",
"detector")
80 sfdSourceCat = cT.Input(
81 doc=
"Catalog of calibration sources",
83 storageClass=
"sourceCatalog",
84 dimensions=(
"tract",
"skymap",
"instrument",
"visit",
"detector")
87 outputExposure = cT.Output(
88 doc=
"Exposure with fake sources added.",
90 storageClass=
"ExposureF",
91 dimensions=(
"instrument",
"visit",
"detector")
94 outputCat = cT.Output(
95 doc=
"Source catalog produced in calibrate task with fakes also measured.",
97 storageClass=
"SourceCatalog",
98 dimensions=(
"instrument",
"visit",
"detector"),
102 class ProcessCcdWithFakesConfig(PipelineTaskConfig,
103 pipelineConnections=ProcessCcdWithFakesConnections):
104 """Config for inserting fake sources
108 The default column names are those from the UW sims database.
111 useUpdatedCalibs = pexConfig.Field(
112 doc=
"Use updated calibs and wcs from jointcal?",
117 coaddName = pexConfig.Field(
118 doc=
"The name of the type of coadd used",
123 srcFieldsToCopy = pexConfig.ListField(
125 default=(
"calib_photometry_reserved",
"calib_photometry_used",
"calib_astrometry_used",
126 "calib_psf_candidate",
"calib_psf_used",
"calib_psf_reserved"),
127 doc=(
"Fields to copy from the `src` catalog to the output catalog "
128 "for matching sources Any missing fields will trigger a "
129 "RuntimeError exception.")
132 matchRadiusPix = pexConfig.Field(
135 doc=(
"Match radius for matching icSourceCat objects to sourceCat objects (pixels)"),
138 calibrate = pexConfig.ConfigurableField(target=CalibrateTask,
139 doc=
"The calibration task to use.")
141 insertFakes = pexConfig.ConfigurableField(target=InsertFakesTask,
142 doc=
"Configuration for the fake sources")
144 def setDefaults(self):
145 super().setDefaults()
146 self.calibrate.measurement.plugins[
"base_PixelFlags"].masksFpAnywhere.append(
"FAKE")
147 self.calibrate.measurement.plugins[
"base_PixelFlags"].masksFpCenter.append(
"FAKE")
148 self.calibrate.doAstrometry =
False
149 self.calibrate.doWriteMatches =
False
150 self.calibrate.doPhotoCal =
False
151 self.calibrate.detection.reEstimateBackground =
False
154 class ProcessCcdWithFakesTask(PipelineTask, CmdLineTask):
155 """Insert fake objects into calexps.
157 Add fake stars and galaxies to the given calexp, specified in the dataRef. Galaxy parameters are read in
158 from the specified file and then modelled using galsim. Re-runs characterize image and calibrate image to
159 give a new background estimation and measurement of the calexp.
161 `ProcessFakeSourcesTask` inherits six functions from insertFakesTask that make images of the fake
162 sources and then add them to the calexp.
165 Use the WCS information to add the pixel coordinates of each source
166 Adds an ``x`` and ``y`` column to the catalog of fake sources.
168 Trim the fake cat to about the size of the input image.
169 `mkFakeGalsimGalaxies`
170 Use Galsim to make fake double sersic galaxies for each set of galaxy parameters in the input file.
172 Use the PSF information from the calexp to make a fake star using the magnitude information from the
175 Remove rows of the input fake catalog which have half light radius, of either the bulge or the disk,
178 Add the fake sources to the calexp.
182 The ``calexp`` with fake souces added to it is written out as the datatype ``calexp_fakes``.
185 _DefaultName =
"processCcdWithFakes"
186 ConfigClass = ProcessCcdWithFakesConfig
188 def __init__(self, schema=None, butler=None, **kwargs):
189 """Initalize things! This should go above in the class docstring
192 super().__init__(**kwargs)
195 schema = SourceTable.makeMinimalSchema()
197 self.makeSubtask(
"insertFakes")
198 self.makeSubtask(
"calibrate")
200 def runDataRef(self, dataRef):
201 """Read in/write out the required data products and add fake sources to the calexp.
205 dataRef : `lsst.daf.persistence.butlerSubset.ButlerDataRef`
206 Data reference defining the ccd to have fakes added to it.
207 Used to access the following data products:
214 Uses the calibration and WCS information attached to the calexp for the posistioning and calibration
215 of the sources unless the config option config.useUpdatedCalibs is set then it uses the
216 meas_mosaic/jointCal outputs. The config defualts for the column names in the catalog of fakes are
217 taken from the University of Washington simulations database. Operates on one ccd at a time.
219 exposureIdInfo = dataRef.get(
"expIdInfo")
221 if self.config.insertFakes.fakeType ==
"snapshot":
222 fakeCat = dataRef.get(
"fakeSourceCat").toDataFrame()
223 elif self.config.insertFakes.fakeType ==
"static":
224 fakeCat = dataRef.get(
"deepCoadd_fakeSourceCat").toDataFrame()
226 fakeCat = Table.read(self.config.insertFakes.fakeType).to_pandas()
228 calexp = dataRef.get(
"calexp")
229 if self.config.useUpdatedCalibs:
230 self.log.info(
"Using updated calibs from meas_mosaic/jointCal")
231 wcs = dataRef.get(
"jointcal_wcs")
232 photoCalib = dataRef.get(
"jointcal_photoCalib")
234 wcs = calexp.getWcs()
235 photoCalib = calexp.getPhotoCalib()
237 icSourceCat = dataRef.get(
"icSrc", immediate=
True)
238 sfdSourceCat = dataRef.get(
"src", immediate=
True)
240 resultStruct = self.run(fakeCat, calexp, wcs=wcs, photoCalib=photoCalib,
241 exposureIdInfo=exposureIdInfo, icSourceCat=icSourceCat,
242 sfdSourceCat=sfdSourceCat)
244 dataRef.put(resultStruct.outputExposure,
"fakes_calexp")
245 dataRef.put(resultStruct.outputCat,
"fakes_src")
248 def runQuantum(self, butlerQC, inputRefs, outputRefs):
249 inputs = butlerQC.get(inputRefs)
250 if 'exposureIdInfo' not in inputs.keys():
251 expId, expBits = butlerQC.quantum.dataId.pack(
"visit_detector", returnMaxBits=
True)
252 inputs[
'exposureIdInfo'] = ExposureIdInfo(expId, expBits)
254 if inputs[
"wcs"]
is None:
255 inputs[
"wcs"] = inputs[
"image"].getWcs()
256 if inputs[
"photoCalib"]
is None:
257 inputs[
"photoCalib"] = inputs[
"image"].getPhotoCalib()
259 outputs = self.run(**inputs)
260 butlerQC.put(outputs, outputRefs)
263 def _makeArgumentParser(cls):
264 parser = pipeBase.ArgumentParser(name=cls._DefaultName)
265 parser.add_id_argument(
"--id",
"fakes_calexp", help=
"data ID with raw CCD keys [+ tract optionally], "
266 "e.g. --id visit=12345 ccd=1,2 [tract=0]",
267 ContainerClass=PerTractCcdDataIdContainer)
270 def run(self, fakeCat, exposure, wcs=None, photoCalib=None, exposureIdInfo=None, icSourceCat=None,
272 """Add fake sources to a calexp and then run detection, deblending and measurement.
276 fakeCat : `pandas.core.frame.DataFrame`
277 The catalog of fake sources to add to the exposure
278 exposure : `lsst.afw.image.exposure.exposure.ExposureF`
279 The exposure to add the fake sources to
280 wcs : `lsst.afw.geom.SkyWcs`
281 WCS to use to add fake sources
282 photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
283 Photometric calibration to be used to calibrate the fake sources
284 exposureIdInfo : `lsst.obs.base.ExposureIdInfo`
285 icSourceCat : `lsst.afw.table.SourceCatalog`
287 Catalog to take the information about which sources were used for calibration from.
288 sfdSourceCat : `lsst.afw.table.SourceCatalog`
290 Catalog produced by singleFrameDriver, needed to copy some calibration flags from.
294 resultStruct : `lsst.pipe.base.struct.Struct`
295 contains : outputExposure : `lsst.afw.image.exposure.exposure.ExposureF`
296 outputCat : `lsst.afw.table.source.source.SourceCatalog`
300 Adds pixel coordinates for each source to the fakeCat and removes objects with bulge or disk half
301 light radius = 0 (if ``config.cleanCat = True``). These columns are called ``x`` and ``y`` and are in
304 Adds the ``Fake`` mask plane to the exposure which is then set by `addFakeSources` to mark where fake
305 sources have been added. Uses the information in the ``fakeCat`` to make fake galaxies (using galsim)
306 and fake stars, using the PSF models from the PSF information for the calexp. These are then added to
307 the calexp and the calexp with fakes included returned.
309 The galsim galaxies are made using a double sersic profile, one for the bulge and one for the disk,
310 this is then convolved with the PSF at that point.
312 If exposureIdInfo is not provided then the SourceCatalog IDs will not be globally unique.
316 wcs = exposure.getWcs()
318 if photoCalib
is None:
319 photoCalib = exposure.getPhotoCalib()
321 self.insertFakes.
run(fakeCat, exposure, wcs, photoCalib)
324 if exposureIdInfo
is None:
325 exposureIdInfo = ExposureIdInfo()
326 returnedStruct = self.calibrate.
run(exposure, exposureIdInfo=exposureIdInfo)
327 sourceCat = returnedStruct.sourceCat
329 sourceCat = self.copyCalibrationFields(sfdSourceCat, sourceCat, self.config.srcFieldsToCopy)
331 resultStruct = pipeBase.Struct(outputExposure=exposure, outputCat=sourceCat)
334 def copyCalibrationFields(self, calibCat, sourceCat, fieldsToCopy):
335 """Match sources in calibCat and sourceCat and copy the specified fields
339 calibCat : `lsst.afw.table.SourceCatalog`
340 Catalog from which to copy fields.
341 sourceCat : `lsst.afw.table.SourceCatalog`
342 Catalog to which to copy fields.
343 fieldsToCopy : `lsst.pex.config.listField.List`
344 Fields to copy from calibCat to SoourceCat.
348 newCat : `lsst.afw.table.SourceCatalog`
349 Catalog which includes the copied fields.
351 The fields copied are those specified by `fieldsToCopy` that actually exist
352 in the schema of `calibCat`.
354 This version was based on and adapted from the one in calibrateTask.
358 sourceSchemaMapper = afwTable.SchemaMapper(sourceCat.schema)
359 sourceSchemaMapper.addMinimalSchema(sourceCat.schema,
True)
361 calibSchemaMapper = afwTable.SchemaMapper(calibCat.schema, sourceCat.schema)
364 missingFieldNames = []
365 for fieldName
in fieldsToCopy:
366 if fieldName
in calibCat.schema:
367 schemaItem = calibCat.schema.find(fieldName)
368 calibSchemaMapper.editOutputSchema().addField(schemaItem.getField())
369 schema = calibSchemaMapper.editOutputSchema()
370 calibSchemaMapper.addMapping(schemaItem.getKey(), schema.find(fieldName).getField())
372 missingFieldNames.append(fieldName)
373 if missingFieldNames:
374 raise RuntimeError(f
"calibCat is missing fields {missingFieldNames} specified in "
377 if "calib_detected" not in calibSchemaMapper.getOutputSchema():
378 self.calibSourceKey = calibSchemaMapper.addOutputField(afwTable.Field[
"Flag"](
"calib_detected",
379 "Source was detected as an icSource"))
381 self.calibSourceKey =
None
383 schema = calibSchemaMapper.getOutputSchema()
384 newCat = afwTable.SourceCatalog(schema)
385 newCat.reserve(len(sourceCat))
386 newCat.extend(sourceCat, sourceSchemaMapper)
389 for k, v
in sourceCat.schema.getAliasMap().items():
390 newCat.schema.getAliasMap().set(k, v)
392 select = newCat[
"deblend_nChild"] == 0
393 matches = afwTable.matchXy(newCat[select], calibCat, self.config.matchRadiusPix)
397 numMatches = len(matches)
398 numUniqueSources = len(set(m[1].getId()
for m
in matches))
399 if numUniqueSources != numMatches:
400 self.log.warn(
"%d calibCat sources matched only %d sourceCat sources", numMatches,
403 self.log.info(
"Copying flags from calibCat to sourceCat for %s sources", numMatches)
407 for src, calibSrc, d
in matches:
408 if self.calibSourceKey:
409 src.setFlag(self.calibSourceKey,
True)
414 calibSrcFootprint = calibSrc.getFootprint()
416 calibSrc.setFootprint(src.getFootprint())
417 src.assign(calibSrc, calibSchemaMapper)
419 calibSrc.setFootprint(calibSrcFootprint)