37 """Config for ProcessImage"""
38 doCalibrate = pexConfig.Field(dtype=bool, default=
True, doc =
"Perform calibration?")
39 doDetection = pexConfig.Field(dtype=bool, default=
True, doc =
"Detect sources?")
41 doDeblend = pexConfig.Field(dtype=bool, default=
False, doc =
"Deblend sources?")
42 doMeasurement = pexConfig.Field(dtype=bool, default=
True, doc =
"Measure sources?")
43 doWriteCalibrate = pexConfig.Field(dtype=bool, default=
True, doc =
"Write calibration results?")
44 persistBackgroundModel = pexConfig.Field(dtype=bool, default=
True, doc =
"If True persist background model with background subtracted calexp. \
45 If False persist calexp with the background included.")
46 doWriteCalibrateMatches = pexConfig.Field(dtype=bool, default=
True,
47 doc =
"Write icSrc to reference matches?")
48 doWriteSources = pexConfig.Field(dtype=bool, default=
True, doc =
"Write sources?")
49 doWriteSourceMatches = pexConfig.Field(dtype=bool, default=
False,
50 doc =
"Compute and write src to reference matches?")
51 doWriteHeavyFootprintsInSources = pexConfig.Field(dtype=bool, default=
False,
52 doc =
"Include HeavyFootprint data in source table?")
53 calibrate = pexConfig.ConfigurableField(
54 target = CalibrateTask,
55 doc =
"Calibration (inc. high-threshold detection and measurement)",
57 detection = pexConfig.ConfigurableField(
58 target = SourceDetectionTask,
59 doc =
"Low-threshold detection for final measurement",
61 deblend = pexConfig.ConfigurableField(
62 target = SourceDeblendTask,
63 doc =
"Split blended sources into their components",
65 measurement = pexConfig.ConfigurableField(
66 target = SingleFrameMeasurementTask,
67 doc =
"Final source measurement on low-threshold detections",
71 pexConfig.Config.validate(self)
74 raise ValueError(
"Cannot run source measurement without source detection.")
75 if (
"skycoord" not in self.measurement.algorithms.names
76 and "base_SkyCoord" not in self.measurement.algorithms.names):
77 raise ValueError(
"If you run source measurement you must let it run the skycoord algorithm.")
78 if self.measurement.target.tableVersion != self.calibrate.measurement.target.tableVersion:
79 raise ValueError(
"measurement subtask tableVersion must match those in calibrate subtask")
81 raise ValueError(
"Cannot run source deblending without source detection.")
83 raise ValueError(
"Cannot write HeavyFootprints (doWriteHeavyFootprintsInSources) without doWriteSources")
86 """An abstract base class for tasks do simple calibration, detection, deblending, and measurement
89 Other command-line Process***Tasks (such as ProcessCcdTask and ProcessCoaddTask) rely on
90 ProcessImageTask for their main algorithmic code, and only need to add pre- and post- processing
93 Subclasses are responsible for meeting the requirements of CmdLineTask.
95 ConfigClass = ProcessImageConfig
99 pipeBase.CmdLineTask.__init__(self, **kwargs)
101 tableVersion = self.config.measurement.target.tableVersion
102 self.makeSubtask(
"calibrate")
105 calibSchema = self.calibrate.schema
107 self.schemaMapper.addMinimalSchema(afwTable.SourceTable.makeMinimalSchema(),
False)
110 if tableVersion == 0:
112 afwTable.Field[
"Flag"](
"calib.detected",
"Source was detected as an icSrc")
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()
122 self.schema.setVersion(tableVersion)
124 if self.config.doDetection:
125 self.makeSubtask(
"detection", schema=self.
schema)
126 if self.config.doDeblend:
127 self.makeSubtask(
"deblend", schema=self.
schema)
128 if self.config.doMeasurement:
129 self.makeSubtask(
"measurement", schema=self.
schema, algMetadata=self.
algMetadata)
132 raise NotImplementedError()
135 def process(self, dataRef, inputExposure, enableWriteSources=True):
138 @param dataRef: data reference that corresponds to the input image
139 @param inputExposure: exposure to process
140 @param enableWriteSources: if True then writing sources is allowed.
141 Set False if you need to postprocess sources before writing them.
143 @return pipe_base Struct containing these fields:
144 - postIsrExposure: exposure after ISR performed if calib.doIsr or config.doCalibrate, else None
145 - exposure: calibrated exposure (calexp): as computed if config.doCalibrate,
146 else as upersisted and updated if config.doDetection, else None
147 - calib: object returned by calibration process if config.doCalibrate, else None
148 - sources: detected source if config.doPhotometry, else None
153 calExposure = inputExposure
156 backgrounds = afwMath.BackgroundList()
157 if self.config.doCalibrate:
158 calib = self.calibrate.run(inputExposure, idFactory=idFactory)
159 calExposure = calib.exposure
160 if self.config.doWriteCalibrate:
161 dataRef.put(calib.sources, self.
dataPrefix +
"icSrc")
162 if calib.matches
is not None and self.config.doWriteCalibrateMatches:
164 normalizedMatches.table.setMetadata(calib.matchMeta)
165 dataRef.put(normalizedMatches, self.
dataPrefix +
"icMatch")
167 for bg
in calib.backgrounds:
168 backgrounds.append(bg)
170 backgrounds.append(calib.backgrounds)
171 except AttributeError:
172 self.log.warn(
"The calibration task did not return any backgrounds. " +
173 "Any background subtracted in the calibration process cannot be persisted.")
177 if self.config.doDetection:
178 table = afwTable.SourceTable.make(self.
schema, idFactory)
180 detections = self.detection.run(table, calExposure)
181 sources = detections.sources
182 fpSets = detections.fpSets
183 if fpSets.background:
184 backgrounds.append(fpSets.background)
186 if self.config.doDeblend:
187 self.deblend.run(calExposure, sources, calExposure.getPsf())
189 if self.config.doMeasurement:
190 self.measurement.run(calExposure, sources)
192 if self.config.doWriteCalibrate:
197 if calExposure
is None:
198 self.log.warn(
"calibrated exposure is None; cannot save it")
200 if self.config.persistBackgroundModel:
204 dataRef.put(calExposure, self.
dataPrefix +
"calexp")
206 if calib
is not None:
209 if sources
is not None and self.config.doWriteSources:
210 sourceWriteFlags = (0
if self.config.doWriteHeavyFootprintsInSources
211 else afwTable.SOURCE_IO_NO_HEAVY_FOOTPRINTS)
212 if enableWriteSources:
213 dataRef.put(sources, self.
dataPrefix +
'src', flags=sourceWriteFlags)
215 if self.config.doMeasurement
and self.config.doWriteSourceMatches:
216 self.log.info(
"Matching src to reference catalogue" % (dataRef.dataId))
217 srcMatches, srcMatchMeta = self.
matchSources(calExposure, sources)
220 normalizedSrcMatches.table.setMetadata(srcMatchMeta)
221 dataRef.put(normalizedSrcMatches, self.
dataPrefix +
"srcMatch")
223 srcMatches =
None; srcMatchMeta =
None
225 return pipeBase.Struct(
226 inputExposure = inputExposure,
227 exposure = calExposure,
230 matches = srcMatches,
231 matchMeta = srcMatchMeta,
232 backgrounds = backgrounds,
236 """Match the sources to the reference object loaded by the calibrate task"""
238 astrometer = self.calibrate.astrometry.astrometer
239 if astrometer
is None:
240 raise AttributeError(
"No astrometer")
241 except AttributeError:
242 self.log.warn(
"Failed to find an astrometer in calibrate's astronomy task")
245 astromRet = astrometer.useKnownWcs(sources, exposure=exposure)
247 return astromRet.matches, astromRet.matchMetadata
250 """Match the icSources and sources, and propagate Interesting Flags (e.g. PSF star) to the sources
252 self.log.info(
"Matching icSource and Source catalogs to propagate flags.")
253 if icSources
is None or sources
is None:
258 if self.config.doDeblend:
259 if self.config.measurement.target.tableVersion == 0:
260 matched = [m
for m
in matched
if m[1].get(
"deblend.nchild") == 0]
262 matched = [m
for m
in matched
if m[1].get(
"deblend_nChild") == 0]
268 for m0, m1, d
in matched:
270 if bestMatches.has_key(id0):
271 if d > bestMatches[id0][2]:
274 bestMatches[id0] = (m0, m1, d)
276 matched = bestMatches.values()
280 if len(set(m[0].getId()
for m
in matched)) != len(matched):
281 self.log.warn(
"At least one icSource is matched to more than one Source")
285 for ics, s, d
in matched:
292 """Return a dict of empty catalogs for each catalog dataset produced by this task."""
299 icSrc.getTable().setMetadata(self.calibrate.algMetadata)
300 except AttributeError:
302 if icSrc
is not None:
307 """Backgrounds are persisted via the butler
309 @param dataRef: Data reference
310 @param backgrounds: List of background models
312 self.log.warn(
"Persisting background models")
314 dataRef.put(backgrounds, self.
dataPrefix+
"calexpBackground")
317 """Add backgrounds back in to an exposure
319 @param exp: Exposure to which to add backgrounds
320 @param backgrounds: List of background models
322 mi = exp.getMaskedImage()
323 mi += backgrounds.getImage()
Class for storing ordered metadata with comments.
A mapping between the keys of two Schemas, used to copy data between them.
tuple doWriteHeavyFootprintsInSources
Custom catalog class for record/table subclasses that are guaranteed to have an ID, and should generally be sorted by that ID.
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.