35 """Config for ProcessImage"""
36 doCalibrate = pexConfig.Field(dtype=bool, default=
True, doc =
"Perform calibration?")
37 doDetection = pexConfig.Field(dtype=bool, default=
True, doc =
"Detect sources?")
39 doDeblend = pexConfig.Field(dtype=bool, default=
False, doc =
"Deblend sources?")
40 doMeasurement = pexConfig.Field(dtype=bool, default=
True, doc =
"Measure sources?")
41 doWriteCalibrate = pexConfig.Field(dtype=bool, default=
True, doc =
"Write calibration results?")
42 persistBackgroundModel = pexConfig.Field(dtype=bool, default=
True, doc =
"If True persist background model with background subtracted calexp. \
43 If False persist calexp with the background included.")
44 doWriteCalibrateMatches = pexConfig.Field(dtype=bool, default=
True,
45 doc =
"Write icSrc to reference matches?")
46 doWriteSources = pexConfig.Field(dtype=bool, default=
True, doc =
"Write sources?")
47 doWriteSourceMatches = pexConfig.Field(dtype=bool, default=
False,
48 doc =
"Compute and write src to reference matches?")
49 doWriteHeavyFootprintsInSources = pexConfig.Field(dtype=bool, default=
False,
50 doc =
"Include HeavyFootprint data in source table?")
51 calibrate = pexConfig.ConfigurableField(
52 target = CalibrateTask,
53 doc =
"Calibration (inc. high-threshold detection and measurement)",
55 detection = pexConfig.ConfigurableField(
56 target = SourceDetectionTask,
57 doc =
"Low-threshold detection for final measurement",
59 deblend = pexConfig.ConfigurableField(
60 target = SourceDeblendTask,
61 doc =
"Split blended sources into their components",
63 measurement = pexConfig.ConfigurableField(
64 target = SingleFrameMeasurementTask,
65 doc =
"Final source measurement on low-threshold detections",
69 pexConfig.Config.validate(self)
72 raise ValueError(
"Cannot run source measurement without source detection.")
73 if (
"skycoord" not in self.measurement.algorithms.names
74 and "base_SkyCoord" not in self.measurement.algorithms.names):
75 raise ValueError(
"If you run source measurement you must let it run the skycoord algorithm.")
76 if self.measurement.doApplyApCorr.startswith(
"yes")
and not self.
doCalibrate:
77 raise ValueError(
"Cannot apply aperture correction in the final measurement"
78 " without calibration.")
79 if self.measurement.doApplyApCorr.startswith(
"yes")
and not self.calibrate.doMeasureApCorr:
80 raise ValueError(
"Cannot apply aperture correction in the final measurement"
81 " without measuring it in calibration.")
83 raise ValueError(
"Cannot run source deblending without source detection.")
85 raise ValueError(
"Cannot write HeavyFootprints (doWriteHeavyFootprintsInSources)"
86 " without doWriteSources")
89 self.measurement.doApplyApCorr =
"yes"
92 """An abstract base class for tasks do simple calibration, detection, deblending, and measurement
95 Other command-line Process***Tasks (such as ProcessCcdTask and ProcessCoaddTask) rely on
96 ProcessImageTask for their main algorithmic code, and only need to add pre- and post- processing
99 Subclasses are responsible for meeting the requirements of CmdLineTask.
101 ConfigClass = ProcessImageConfig
105 pipeBase.CmdLineTask.__init__(self, **kwargs)
107 self.makeSubtask(
"calibrate")
110 calibSchema = self.calibrate.schema
112 self.schemaMapper.addMinimalSchema(afwTable.SourceTable.makeMinimalSchema(),
False)
116 afwTable.Field[
"Flag"](
"calib_detected",
"Source was detected as an icSrc")
119 for key
in self.calibrate.getCalibKeys():
120 self.schemaMapper.addMapping(key)
121 self.
schema = self.schemaMapper.getOutputSchema()
123 if self.config.doDetection:
124 self.makeSubtask(
"detection", schema=self.
schema)
125 if self.config.doDeblend:
126 self.makeSubtask(
"deblend", schema=self.
schema)
127 if self.config.doMeasurement:
128 self.makeSubtask(
"measurement", schema=self.
schema, algMetadata=self.
algMetadata)
131 raise NotImplementedError()
134 raise NotImplementedError()
137 def process(self, dataRef, inputExposure, enableWriteSources=True):
140 @param dataRef: data reference that corresponds to the input image
141 @param inputExposure: exposure to process
142 @param enableWriteSources: if True then writing sources is allowed.
143 Set False if you need to postprocess sources before writing them.
145 @return pipe_base Struct containing these fields:
146 - postIsrExposure: exposure after ISR performed if calib.doIsr or config.doCalibrate, else None
147 - exposure: calibrated exposure (calexp): as computed if config.doCalibrate,
148 else as upersisted and updated if config.doDetection, else None
149 - calib: object returned by calibration process if config.doCalibrate, else None
150 - sources: detected source if config.doPhotometry, else None
155 calExposure = inputExposure
158 backgrounds = afwMath.BackgroundList()
159 if self.config.doCalibrate:
160 calib = self.calibrate.run(inputExposure, idFactory=idFactory)
161 calExposure = calib.exposure
162 if self.config.doWriteCalibrate:
163 dataRef.put(calib.sources, self.
dataPrefix +
"icSrc")
164 if calib.matches
is not None and self.config.doWriteCalibrateMatches:
166 normalizedMatches.table.setMetadata(calib.matchMeta)
167 dataRef.put(normalizedMatches, self.
dataPrefix +
"icMatch")
169 for bg
in calib.backgrounds:
170 backgrounds.append(bg)
172 backgrounds.append(calib.backgrounds)
173 except AttributeError:
174 self.log.warn(
"The calibration task did not return any backgrounds. " +
175 "Any background subtracted in the calibration process cannot be persisted.")
179 if self.config.doDetection:
180 table = afwTable.SourceTable.make(self.
schema, idFactory)
182 detections = self.detection.run(table, calExposure)
183 sources = detections.sources
184 fpSets = detections.fpSets
185 if fpSets.background:
186 backgrounds.append(fpSets.background)
188 if self.config.doDeblend:
189 self.deblend.run(calExposure, sources, calExposure.getPsf())
191 if self.config.doMeasurement:
192 self.measurement.run(calExposure, sources, exposureId=self.
getExposureId(dataRef))
194 if self.config.doWriteCalibrate:
199 if calExposure
is None:
200 self.log.warn(
"calibrated exposure is None; cannot save it")
202 if self.config.persistBackgroundModel:
206 dataRef.put(calExposure, self.
dataPrefix +
"calexp")
208 if calib
is not None:
211 if sources
is not None and self.config.doWriteSources:
212 sourceWriteFlags = (0
if self.config.doWriteHeavyFootprintsInSources
213 else afwTable.SOURCE_IO_NO_HEAVY_FOOTPRINTS)
214 if enableWriteSources:
215 dataRef.put(sources, self.
dataPrefix +
'src', flags=sourceWriteFlags)
217 if self.config.doMeasurement
and self.config.doWriteSourceMatches:
218 self.log.info(
"Matching src to reference catalogue" % (dataRef.dataId))
219 srcMatches, srcMatchMeta = self.
matchSources(calExposure, sources)
222 normalizedSrcMatches.table.setMetadata(srcMatchMeta)
223 dataRef.put(normalizedSrcMatches, self.
dataPrefix +
"srcMatch")
225 srcMatches =
None; srcMatchMeta =
None
227 return pipeBase.Struct(
228 inputExposure = inputExposure,
229 exposure = calExposure,
232 matches = srcMatches,
233 matchMeta = srcMatchMeta,
234 backgrounds = backgrounds,
238 """Match the sources to the reference object loaded by the calibrate task
241 - matches list of reference object/source matches (an lsst.afw.table.ReferenceMatchVector)
242 - matchMeta metadata about the field (an lsst.daf.base.PropertyList)
245 astrometry = self.calibrate.astrometry
247 self.log.warn(
"Failed to find an astrometry solver in the calibrate task")
250 astromRet = astrometry.loadAndMatch(exposure=exposure, sourceCat=sources)
251 return astromRet.matches, astromRet.matchMeta
254 """Match the icSources and sources, and propagate Interesting Flags (e.g. PSF star) to the sources
256 self.log.info(
"Matching icSource and Source catalogs to propagate flags.")
257 if icSources
is None or sources
is None:
262 if self.config.doDeblend:
263 matched = [m
for m
in matched
if m[1].get(
"deblend_nChild") == 0]
269 for m0, m1, d
in matched:
271 if bestMatches.has_key(id0):
272 if d > bestMatches[id0][2]:
275 bestMatches[id0] = (m0, m1, d)
277 matched = bestMatches.values()
281 if len(set(m[0].getId()
for m
in matched)) != len(matched):
282 self.log.warn(
"At least one icSource is matched to more than one Source")
286 for ics, s, d
in matched:
293 """Return a dict of empty catalogs for each catalog dataset produced by this task."""
300 icSrc.getTable().setMetadata(self.calibrate.algMetadata)
301 except AttributeError:
303 if icSrc
is not None:
308 """Backgrounds are persisted via the butler
310 @param dataRef: Data reference
311 @param backgrounds: List of background models
313 self.log.warn(
"Persisting background models")
315 dataRef.put(backgrounds, self.
dataPrefix+
"calexpBackground")
318 """Add backgrounds back in to an exposure
320 @param exp: Exposure to which to add backgrounds
321 @param backgrounds: List of background models
323 mi = exp.getMaskedImage()
324 mi += backgrounds.getImage()
Class for storing ordered metadata with comments.
A mapping between the keys of two Schemas, used to copy data between them.
Custom catalog class for record/table subclasses that are guaranteed to have an ID, and should generally be sorted by that ID.
tuple doWriteHeavyFootprintsInSources
A description of a field in a table.
BaseCatalog packMatches(std::vector< Match< Record1, Record2 > > const &matches)
Return a table representation of a MatchVector that can be used to persist it.
std::vector< Match< typename Cat::Record, typename Cat::Record > > matchRaDec(Cat const &cat, Angle radius, bool symmetric=true)
tuple doDeblend
NOTE, default this to False until it is fully vetted; #2138.