6 from lsst.pipe.base import Task, CmdLineTask, ArgumentParser, timeMethod
16 """Configuration for reference source catalog retrieval
18 This is bare, but will be extended by subclasses
19 to support getting the list of reference sources.
21 correct = Field(dtype=bool, default=
False, doc=
"Correct references for astrometric offsets?")
22 minFlux = Field(dtype=float, default=3000, doc=
"Minimum flux for calculating offsets")
23 radius = Field(dtype=float, default=0.5, doc=
"Association radius for matching, arcsec")
26 """Task to generate a reference source catalog for forced photometry
28 This is a base class, as it is not clear how to generate the
29 reference sources in the generic case (different projects will
30 want to do this differently: perhaps from measuring a coadd, or
31 perhaps from a database, or ...) and so this class MUST be
32 overridden to properly define the getReferences() method.
35 ConfigClass = ReferencesConfig
37 def run(self, dataRef, exposure):
39 self.log.info(
"Retrieved %d reference sources" % len(references))
41 self.log.info(
"Subset to %d reference sources" % len(references))
42 if self.config.correct:
47 """Get reference sources on (or close to) exposure.
49 This method must be overridden by subclasses to return
50 a lsst.afw.table.SourceCatalog.
52 @param dataRef Data reference from butler
53 @param exposure Exposure that has been read
54 @return Catalog (lsst.afw.table.SourceCatalog) of reference sources
58 """Calling base class implementation of ReferencesTask.getReferences()!
59 You need to configure a subclass of ReferencesTask. Put in your configuration
60 override file something like:
61 from some.namespace import SubclassReferencesTask
62 root.references.retarget(SubclassReferencesTask)
64 raise NotImplementedError(
"Don't know how to get reference sources in the generic case")
67 """Generate a subset of reference sources to ensure all are in the exposure
69 @param references Reference source catalog
70 @param exposure Exposure of interest
71 @return Reference catalog with subset
74 wcs = exposure.getWcs()
76 for ref
in references:
77 coord = ref.getCoord()
78 if box.contains(wcs.skyToPixel(coord)):
83 self.log.info(
"Correcting reference positions...")
84 sources = dataRef.get(
"src")
87 self.log.info(
"%d matches between source and reference catalogs" % num)
90 dra, ddec = afwMath.vectorF(), afwMath.vectorF()
93 units = afwGeom.arcseconds
97 if src.getPsfFlux() < self.config.minFlux:
100 offset = ref.getCoord().getTangentPlaneOffset(src.getCoord())
101 dra.push_back(offset[0].asAngularUnits(units))
102 ddec.push_back(offset[1].asAngularUnits(units))
106 draMean = draStats.getValue(afwMath.MEANCLIP)
107 ddecMean = ddecStats.getValue(afwMath.MEANCLIP)
108 self.log.info(
"Offset from %d sources is dRA = %f +/- %f arcsec, dDec = %f +/- %f arcsec" %
109 (num, draMean, draStats.getValue(afwMath.STDEVCLIP), ddecMean,
110 ddecStats.getValue(afwMath.STDEVCLIP)))
111 angle = math.atan2(ddecMean, draMean)*afwGeom.radians
112 distance = math.hypot(draMean, ddecMean)*units
113 for ref
in references:
114 coord = ref.getCoord()
115 coord.offset(angle, distance)
120 """Configuration for forced photometry.
123 references = ConfigurableField(target=ReferencesTask, doc=
"Retrieve reference source catalog")
124 measurement = ConfigurableField(target=measAlg.SourceMeasurementTask, doc=
"Forced measurement")
125 copyColumns = DictField(keytype=str, itemtype=str, doc=
"Mapping of reference columns to source columns",
126 default={
"id":
"objectId"})
130 """Task to perform forced photometry.
132 "Forced photometry" is measurement on an image using the
133 position from another source as the centroid, and without
137 ConfigClass = ForcedPhotConfig
138 _DefaultName =
"forcedPhot"
141 super(ForcedPhotTask, self).
__init__(*args, **kwargs)
142 self.
schema = afwTable.SourceTable.makeMinimalSchema()
144 self.makeSubtask(
"references")
145 self.makeSubtask(
"measurement", schema=self.
schema, algMetadata=self.
algMetadata)
149 """Overriding CmdLineTask._makeArgumentParser to set dataset type"""
150 parser = ArgumentParser(name=cls._DefaultName)
151 parser.add_id_argument(
"--id",
"calexp", help=
"data ID, e.g. --id visit=12345 ccd=1,2")
156 exposure = dataRef.get(
"calexp")
158 expBits = dataRef.get(
"ccdExposureId_bits")
159 expId = long(dataRef.get(
"ccdExposureId"))
160 idFactory = afwTable.IdFactory.makeSource(expId, 64 - expBits)
162 references = self.references.run(dataRef, exposure)
163 self.log.info(
"Performing forced measurement on %d sources" % len(references))
165 self.measurement.run(exposure, sources, references=references)
169 """Generate sources to be measured
171 @param references Reference source catalog
172 @param idFactory Factory to generate unique ids for forced sources
173 @return Source catalog ready for measurement
178 for fromCol, toCol
in self.config.copyColumns.items():
179 item = references.schema.find(fromCol)
180 schema.addField(toCol, item.field.getTypeString(), item.field.getDoc(), item.field.getUnits())
181 keys = (item.key, schema.find(toCol).key)
182 copyKeys.append(keys)
184 table = afwTable.SourceTable.make(schema, idFactory)
186 table = sources.table
188 sources.preallocate(len(references))
189 for ref
in references:
190 src = table.makeRecord()
191 for fromKey, toKey
in copyKeys:
192 src.set(toKey, ref.get(fromKey))
197 """Write sources out.
199 @param outName Name of forced sources in butler
201 dataRef.put(sources, outName)
Defines the fields and offsets for a table.
Class for storing ordered metadata with comments.
Pass parameters to a Statistics objectA class to pass parameters which control how the stats are calc...
Custom catalog class for record/table subclasses that are guaranteed to have an ID, and should generally be sorted by that ID.
Statistics makeStatistics(afwImage::Mask< afwImage::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl)
Specialization to handle Masks.
std::vector< Match< typename Cat::Record, typename Cat::Record > > matchRaDec(Cat const &cat, Angle radius, bool symmetric=true)
A floating-point coordinate rectangle geometry.