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",
45 "wcsName":
"jointcal",
46 "photoCalibName":
"jointcal",
47 "fakesType":
"fakes_"}):
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=
"{fakesType}fakeSourceCat",
59 storageClass=
"DataFrame",
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=
"{photoCalibName}_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=(
"instrument",
"visit",
"detector")
84 sfdSourceCat = cT.Input(
85 doc=
"Catalog of calibration sources",
87 storageClass=
"SourceCatalog",
88 dimensions=(
"instrument",
"visit",
"detector")
91 outputExposure = cT.Output(
92 doc=
"Exposure with fake sources added.",
93 name=
"{fakesType}calexp",
94 storageClass=
"ExposureF",
95 dimensions=(
"instrument",
"visit",
"detector")
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"),
108 if config.doApplyExternalSkyWcs
is False:
109 self.
inputsinputs.remove(
"wcs")
110 if config.doApplyExternalPhotoCalib
is False:
111 self.
inputsinputs.remove(
"photoCalib")
115 pipelineConnections=ProcessCcdWithFakesConnections):
116 """Config for inserting fake sources
120 The default column names are those from the UW sims database.
123 doApplyExternalPhotoCalib = pexConfig.Field(
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."
132 externalPhotoCalibName = pexConfig.ChoiceField(
133 doc=
"What type of external photo calib to use.",
136 allowed={
"jointcal":
"Use jointcal_photoCalib",
137 "fgcm":
"Use fgcm_photoCalib",
138 "fgcm_tract":
"Use fgcm_tract_photoCalib"}
141 doApplyExternalSkyWcs = pexConfig.Field(
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."
150 externalSkyWcsName = pexConfig.ChoiceField(
151 doc=
"What type of updated WCS calib to use.",
154 allowed={
"jointcal":
"Use jointcal_wcs"}
157 coaddName = pexConfig.Field(
158 doc=
"The name of the type of coadd used",
163 srcFieldsToCopy = pexConfig.ListField(
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.")
172 matchRadiusPix = pexConfig.Field(
175 doc=(
"Match radius for matching icSourceCat objects to sourceCat objects (pixels)"),
178 calibrate = pexConfig.ConfigurableField(target=CalibrateTask,
179 doc=
"The calibration task to use.")
181 insertFakes = pexConfig.ConfigurableField(target=InsertFakesTask,
182 doc=
"Configuration for the fake sources")
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
195 """Insert fake objects into calexps.
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.
201 `ProcessFakeSourcesTask` inherits six functions from insertFakesTask that make images of the fake
202 sources and then add them to the calexp.
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.
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.
212 Use the PSF information from the calexp to make a fake star using the magnitude information from the
215 Remove rows of the input fake catalog which have half light radius, of either the bulge or the disk,
218 Add the fake sources to the calexp.
222 The ``calexp`` with fake souces added to it is written out as the datatype ``calexp_fakes``.
225 _DefaultName =
"processCcdWithFakes"
226 ConfigClass = ProcessCcdWithFakesConfig
228 def __init__(self, schema=None, butler=None, **kwargs):
229 """Initalize things! This should go above in the class docstring
232 super().__init__(**kwargs)
235 schema = SourceTable.makeMinimalSchema()
237 self.makeSubtask(
"insertFakes")
238 self.makeSubtask(
"calibrate")
240 def runDataRef(self, dataRef):
241 """Read in/write out the required data products and add fake sources to the calexp.
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:
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.
260 exposureIdInfo = dataRef.get(
"expIdInfo")
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()
267 fakeCat = Table.read(self.config.insertFakes.fakeType).to_pandas()
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")
274 wcs = calexp.getWcs()
276 if self.config.doApplyExternalPhotoCalib:
277 self.log.
info(
"Using external photocalib from " + self.config.externalPhotoCalibName)
278 photoCalib = dataRef.get(self.config.externalPhotoCalibName +
"_photoCalib")
280 photoCalib = calexp.getPhotoCalib()
282 icSourceCat = dataRef.get(
"icSrc", immediate=
True)
283 sfdSourceCat = dataRef.get(
"src", immediate=
True)
285 resultStruct = self.run(fakeCat, calexp, wcs=wcs, photoCalib=photoCalib,
286 exposureIdInfo=exposureIdInfo, icSourceCat=icSourceCat,
287 sfdSourceCat=sfdSourceCat)
289 dataRef.put(resultStruct.outputExposure,
"fakes_calexp")
290 dataRef.put(resultStruct.outputCat,
"fakes_src")
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)
299 if not self.config.doApplyExternalSkyWcs:
300 inputs[
"wcs"] = inputs[
"exposure"].getWcs()
302 if not self.config.doApplyExternalPhotoCalib:
303 inputs[
"photoCalib"] = inputs[
"exposure"].getPhotoCalib()
305 outputs = self.run(**inputs)
306 butlerQC.put(outputs, outputRefs)
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)
316 def run(self, fakeCat, exposure, wcs=None, photoCalib=None, exposureIdInfo=None, icSourceCat=None,
318 """Add fake sources to a calexp and then run detection, deblending and measurement.
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`
333 Catalog to take the information about which sources were used for calibration from.
334 sfdSourceCat : `lsst.afw.table.SourceCatalog`
336 Catalog produced by singleFrameDriver, needed to copy some calibration flags from.
340 resultStruct : `lsst.pipe.base.struct.Struct`
341 contains : outputExposure : `lsst.afw.image.exposure.exposure.ExposureF`
342 outputCat : `lsst.afw.table.source.source.SourceCatalog`
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
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.
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.
358 If exposureIdInfo is not provided then the SourceCatalog IDs will not be globally unique.
362 wcs = exposure.getWcs()
364 if photoCalib
is None:
365 photoCalib = exposure.getPhotoCalib()
367 self.insertFakes.
run(fakeCat, exposure, wcs, photoCalib)
370 if exposureIdInfo
is None:
371 exposureIdInfo = ExposureIdInfo()
372 returnedStruct = self.calibrate.
run(exposure, exposureIdInfo=exposureIdInfo)
373 sourceCat = returnedStruct.sourceCat
375 sourceCat = self.copyCalibrationFields(sfdSourceCat, sourceCat, self.config.srcFieldsToCopy)
377 resultStruct = pipeBase.Struct(outputExposure=exposure, outputCat=sourceCat)
380 def copyCalibrationFields(self, calibCat, sourceCat, fieldsToCopy):
381 """Match sources in calibCat and sourceCat and copy the specified fields
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.
394 newCat : `lsst.afw.table.SourceCatalog`
395 Catalog which includes the copied fields.
397 The fields copied are those specified by `fieldsToCopy` that actually exist
398 in the schema of `calibCat`.
400 This version was based on and adapted from the one in calibrateTask.
405 sourceSchemaMapper.addMinimalSchema(sourceCat.schema,
True)
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())
418 missingFieldNames.append(fieldName)
419 if missingFieldNames:
420 raise RuntimeError(f
"calibCat is missing fields {missingFieldNames} specified in "
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"))
427 self.calibSourceKey =
None
429 schema = calibSchemaMapper.getOutputSchema()
431 newCat.reserve(len(sourceCat))
432 newCat.extend(sourceCat, sourceSchemaMapper)
435 for k, v
in sourceCat.schema.getAliasMap().
items():
436 newCat.schema.getAliasMap().
set(k, v)
438 select = newCat[
"deblend_nChild"] == 0
439 matches =
afwTable.matchXy(newCat[select], calibCat, self.config.matchRadiusPix)
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,
449 self.log.
info(
"Copying flags from calibCat to sourceCat for %s sources", numMatches)
453 for src, calibSrc, d
in matches:
454 if self.calibSourceKey:
455 src.setFlag(self.calibSourceKey,
True)
460 calibSrcFootprint = calibSrc.getFootprint()
462 calibSrc.setFootprint(src.getFootprint())
463 src.assign(calibSrc, calibSchemaMapper)
465 calibSrc.setFootprint(calibSrcFootprint)
std::vector< SchemaItem< Flag > > * items
A mapping between the keys of two Schemas, used to copy data between them.
def __init__(self, *'PipelineTaskConfig' config=None)
daf::base::PropertySet * set
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...
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, altMaskList=None, mask=None, supplementaryData=None)
A description of a field in a table.