261 def run(self, measCat, exposure, refCat, refWcs, exposureId=None, beginOrder=None, endOrder=None):
262 r"""Perform forced measurement.
263
264 Parameters
265 ----------
266 exposure : `lsst.afw.image.exposureF`
267 Image to be measured. Must have at least a `lsst.afw.geom.SkyWcs`
268 attached.
269 measCat : `lsst.afw.table.SourceCatalog`
270 Source catalog for measurement results; must be initialized with
271 empty records already corresponding to those in ``refCat`` (via
272 e.g. `generateMeasCat`).
273 refCat : `lsst.afw.table.SourceCatalog`
274 A sequence of `lsst.afw.table.SourceRecord` objects that provide
275 reference information for the measurement. These will be passed
276 to each plugin in addition to the output
277 `~lsst.afw.table.SourceRecord`.
278 refWcs : `lsst.afw.geom.SkyWcs`
279 Defines the X,Y coordinate system of ``refCat``.
280 exposureId : `int`, optional
281 Optional unique exposureId used to calculate random number
282 generator seed in the NoiseReplacer.
283 beginOrder : `int`, optional
284 Beginning execution order (inclusive). Algorithms with
285 ``executionOrder`` < ``beginOrder`` are not executed. `None` for no limit.
286 endOrder : `int`, optional
287 Ending execution order (exclusive). Algorithms with
288 ``executionOrder`` >= ``endOrder`` are not executed. `None` for no limit.
289
290 Notes
291 -----
292 Fills the initial empty `~lsst.afw.table.SourceCatalog` with forced
293 measurement results. Two steps must occur before `run` can be called:
294
295 - `generateMeasCat` must be called to create the output ``measCat``
296 argument.
297 - `~lsst.afw.detection.Footprint`\ s appropriate for the forced sources
298 must be attached to the ``measCat`` records. The
299 `attachTransformedFootprints` method can be used to do this, but
300 this degrades "heavy" (i.e., including pixel values)
301 `~lsst.afw.detection.Footprint`\s to regular
302 `~lsst.afw.detection.Footprint`\s, leading to non-deblended
303 measurement, so most callers should provide
304 `~lsst.afw.detection.Footprint`\s some other way. Typically, calling
305 code will have access to information that will allow them to provide
306 heavy footprints - for instance, `ForcedPhotCoaddTask` uses the
307 heavy footprints from deblending run in the same band just before
308 non-forced is run measurement in that band.
309 """
310
311
312
313
314
315
316
317
318
319
320 refCatIdDict = {ref.getId(): ref.getParent() for ref in refCat}
321 for ref in refCat:
322 refId = ref.getId()
323 topId = refId
324 while topId > 0:
325 if topId not in refCatIdDict:
326 raise RuntimeError("Reference catalog contains a child for which at least "
327 "one parent in its parent chain is not in the catalog.")
328 topId = refCatIdDict[topId]
329
330
331
332
333 footprints = {ref.getId(): (ref.getParent(), measRecord.getFootprint())
334 for (ref, measRecord) in zip(refCat, measCat)}
335
336 self.log.info("Performing forced measurement on %d source%s", len(refCat),
337 "" if len(refCat) == 1 else "s")
338
339
340 periodicLog = PeriodicLogger(self.log)
341
342 if self.config.doReplaceWithNoise:
343 noiseReplacer = NoiseReplacer(self.config.noiseReplacer, exposure,
344 footprints, log=self.log, exposureId=exposureId)
345 algMetadata = measCat.getTable().getMetadata()
346 if algMetadata is not None:
347 algMetadata.addInt("NOISE_SEED_MULTIPLIER", self.config.noiseReplacer.noiseSeedMultiplier)
348 algMetadata.addString("NOISE_SOURCE", self.config.noiseReplacer.noiseSource)
349 algMetadata.addDouble("NOISE_OFFSET", self.config.noiseReplacer.noiseOffset)
350 if exposureId is not None:
351 algMetadata.addLong("NOISE_EXPOSURE_ID", exposureId)
352 else:
353 noiseReplacer = DummyNoiseReplacer()
354
355
356
357 refParentCat, measParentCat = refCat.getChildren(0, measCat)
358 childrenIter = refCat.getChildren((refParentRecord.getId() for refParentRecord in refCat), measCat)
359 for parentIdx, records in enumerate(zip(refParentCat, measParentCat, childrenIter)):
360
361 refParentRecord, measParentRecord, (refChildCat, measChildCat) = records
362
363
364 for refChildRecord, measChildRecord in zip(refChildCat, measChildCat):
365 noiseReplacer.insertSource(refChildRecord.getId())
366 self.callMeasure(measChildRecord, exposure, refChildRecord, refWcs,
367 beginOrder=beginOrder, endOrder=endOrder)
368 noiseReplacer.removeSource(refChildRecord.getId())
369
370
371 noiseReplacer.insertSource(refParentRecord.getId())
372 self.callMeasure(measParentRecord, exposure, refParentRecord, refWcs,
373 beginOrder=beginOrder, endOrder=endOrder)
374 self.callMeasureN(measParentCat[parentIdx:parentIdx+1], exposure,
375 refParentCat[parentIdx:parentIdx+1],
376 beginOrder=beginOrder, endOrder=endOrder)
377
378 self.callMeasureN(measChildCat, exposure, refChildCat,
379 beginOrder=beginOrder, endOrder=endOrder)
380 noiseReplacer.removeSource(refParentRecord.getId())
381
382 periodicLog.log("Forced measurement complete for %d parents (and their children) out of %d",
383 parentIdx + 1, len(refParentCat))
384 noiseReplacer.end()
385
386
387 if endOrder is None:
388 for recordIndex, (measRecord, refRecord) in enumerate(zip(measCat, refCat)):
389 for plugin in self.undeblendedPlugins.iter():
390 self.doMeasurement(plugin, measRecord, exposure, refRecord, refWcs)
391 periodicLog.log("Undeblended forced measurement complete for %d sources out of %d",
392 recordIndex + 1, len(refCat))
393