313 def __init__(self, refObjLoader=None, schema=None, **kwargs):
317 schema = SourceTable.makeMinimalSchema()
319 self.makeSubtask(
"background")
320 self.makeSubtask(
"installSimplePsf")
321 self.makeSubtask(
"repair")
322 self.makeSubtask(
"measurePsf", schema=self.
schema)
324 if self.config.doMeasurePsf
and self.measurePsf.usesMatches:
325 self.makeSubtask(
"ref_match", refObjLoader=refObjLoader)
327 self.makeSubtask(
'detection', schema=self.
schema)
328 if self.config.doDeblend:
329 self.makeSubtask(
"deblend", schema=self.
schema)
330 self.makeSubtask(
'measurement', schema=self.
schema, algMetadata=self.
algMetadata)
331 if self.config.doApCorr:
332 self.makeSubtask(
'measureApCorr', schema=self.
schema)
333 self.makeSubtask(
'applyApCorr', schema=self.
schema)
334 self.makeSubtask(
'catalogCalculation', schema=self.
schema)
337 self.
schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
341 inputs = butlerQC.get(inputRefs)
342 if 'idGenerator' not in inputs.keys():
343 inputs[
'idGenerator'] = self.config.idGenerator.apply(butlerQC.quantum.dataId)
344 outputs = self.
run(**inputs)
345 butlerQC.put(outputs, outputRefs)
348 def run(self, exposure, exposureIdInfo=None, background=None, idGenerator=None):
349 """Characterize a science image.
351 Peforms the following operations:
352 - Iterate the following config.psfIterations times, or once if config.doMeasurePsf false:
353 - detect and measure sources and estimate PSF (see detectMeasureAndEstimatePsf for details)
354 - interpolate over cosmic rays
355 - perform final measurement
359 exposure : `lsst.afw.image.ExposureF`
360 Exposure to characterize.
361 exposureIdInfo : `lsst.obs.base.ExposureIdInfo`, optional
362 Exposure ID info. Deprecated in favor of ``idGenerator``, and
363 ignored if that is provided.
364 background : `lsst.afw.math.BackgroundList`, optional
365 Initial model of background already subtracted from exposure.
366 idGenerator : `lsst.meas.base.IdGenerator`, optional
367 Object that generates source IDs and provides RNG seeds.
371 result : `lsst.pipe.base.Struct`
372 Results as a struct with attributes:
375 Characterized exposure (`lsst.afw.image.ExposureF`).
377 Detected sources (`lsst.afw.table.SourceCatalog`).
379 Model of subtracted background (`lsst.afw.math.BackgroundList`).
381 Spatial cells of PSF candidates (`lsst.afw.math.SpatialCellSet`).
383 Another reference to ``exposure`` for compatibility.
385 Another reference to ``background`` for compatibility.
390 Raised if PSF sigma is NaN.
394 if not self.config.doMeasurePsf
and not exposure.hasPsf():
395 self.log.info(
"CharacterizeImageTask initialized with 'simple' PSF.")
396 self.installSimplePsf.run(exposure=exposure)
398 if idGenerator
is None:
399 if exposureIdInfo
is not None:
400 idGenerator = IdGenerator._from_exposure_id_info(exposureIdInfo)
402 idGenerator = IdGenerator()
407 background = self.background.run(exposure).background
409 psfIterations = self.config.psfIterations
if self.config.doMeasurePsf
else 1
410 for i
in range(psfIterations):
413 idGenerator=idGenerator,
414 background=background,
417 psf = dmeRes.exposure.getPsf()
419 psfAvgPos = psf.getAveragePosition()
420 psfSigma = psf.computeShape(psfAvgPos).getDeterminantRadius()
421 psfDimensions = psf.computeImage(psfAvgPos).getDimensions()
422 medBackground = np.median(dmeRes.background.getImage().getArray())
423 self.log.info(
"iter %s; PSF sigma=%0.2f, dimensions=%s; median background=%0.2f",
424 i + 1, psfSigma, psfDimensions, medBackground)
425 if np.isnan(psfSigma):
426 raise RuntimeError(
"PSF sigma is NaN, cannot continue PSF determination.")
428 self.
display(
"psf", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
431 self.repair.run(exposure=dmeRes.exposure)
432 self.
display(
"repair", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
436 self.measurement.run(measCat=dmeRes.sourceCat, exposure=dmeRes.exposure,
437 exposureId=idGenerator.catalog_id)
438 if self.config.doApCorr:
440 apCorrMap = self.measureApCorr.run(
441 exposure=dmeRes.exposure,
442 catalog=dmeRes.sourceCat,
444 except MeasureApCorrError:
448 dmeRes.exposure.info.setApCorrMap(
None)
450 dmeRes.exposure.info.setApCorrMap(apCorrMap)
451 self.applyApCorr.run(catalog=dmeRes.sourceCat, apCorrMap=exposure.getInfo().getApCorrMap())
453 self.catalogCalculation.run(dmeRes.sourceCat)
455 self.
display(
"measure", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
457 return pipeBase.Struct(
458 exposure=dmeRes.exposure,
459 sourceCat=dmeRes.sourceCat,
460 background=dmeRes.background,
461 psfCellSet=dmeRes.psfCellSet,
463 characterized=dmeRes.exposure,
464 backgroundModel=dmeRes.background
469 """Perform one iteration of detect, measure, and estimate PSF.
471 Performs the following operations:
473 - if config.doMeasurePsf or not exposure.hasPsf():
475 - install a simple PSF model (replacing the existing one, if need be)
477 - interpolate over cosmic rays with keepCRs=True
478 - estimate background and subtract it from the exposure
479 - detect, deblend and measure sources, and subtract a refined background model;
480 - if config.doMeasurePsf:
485 exposure : `lsst.afw.image.ExposureF`
486 Exposure to characterize.
487 idGenerator : `lsst.meas.base.IdGenerator`
488 Object that generates source IDs and provides RNG seeds.
489 background : `lsst.afw.math.BackgroundList`, optional
490 Initial model of background already subtracted from exposure.
494 result : `lsst.pipe.base.Struct`
495 Results as a struct with attributes:
498 Characterized exposure (`lsst.afw.image.ExposureF`).
500 Detected sources (`lsst.afw.table.SourceCatalog`).
502 Model of subtracted background (`lsst.afw.math.BackgroundList`).
504 Spatial cells of PSF candidates (`lsst.afw.math.SpatialCellSet`).
509 Raised if there are too many CR pixels.
512 if not exposure.hasPsf()
or (self.config.doMeasurePsf
and self.config.useSimplePsf):
513 self.log.info(
"PSF estimation initialized with 'simple' PSF")
514 self.installSimplePsf.run(exposure=exposure)
517 if self.config.requireCrForPsf:
518 self.repair.run(exposure=exposure, keepCRs=
True)
521 self.repair.run(exposure=exposure, keepCRs=
True)
523 self.log.warning(
"Skipping cosmic ray detection: Too many CR pixels (max %0.f)",
524 self.config.repair.cosmicray.nCrPixelMax)
526 self.
display(
"repair_iter", exposure=exposure)
528 if background
is None:
529 background = BackgroundList()
531 sourceIdFactory = idGenerator.make_table_id_factory()
532 table = SourceTable.make(self.
schema, sourceIdFactory)
535 detRes = self.detection.run(table=table, exposure=exposure, doSmooth=
True)
536 sourceCat = detRes.sources
537 if detRes.background:
538 for bg
in detRes.background:
539 background.append(bg)
541 if self.config.doDeblend:
542 self.deblend.run(exposure=exposure, sources=sourceCat)
544 if not sourceCat.isContiguous():
545 sourceCat = sourceCat.copy(deep=
True)
547 self.measurement.run(measCat=sourceCat, exposure=exposure, exposureId=idGenerator.catalog_id)
549 measPsfRes = pipeBase.Struct(cellSet=
None)
550 if self.config.doMeasurePsf:
552 if self.measurePsf.usesMatches:
553 matches = self.ref_match.loadAndMatch(exposure=exposure, sourceCat=sourceCat).matches
556 measPsfRes = self.measurePsf.run(exposure=exposure, sources=sourceCat, matches=matches,
557 expId=idGenerator.catalog_id)
558 self.
display(
"measure_iter", exposure=exposure, sourceCat=sourceCat)
560 return pipeBase.Struct(
563 background=background,
564 psfCellSet=measPsfRes.cellSet,
567 def display(self, itemName, exposure, sourceCat=None):
568 """Display exposure and sources on next frame (for debugging).
573 Name of item in ``debugInfo``.
574 exposure : `lsst.afw.image.ExposureF`
576 sourceCat : `lsst.afw.table.SourceCatalog`, optional
577 Catalog of sources detected on the exposure.
579 val = getDebugFrame(self._display, itemName)
583 displayAstrometry(exposure=exposure, sourceCat=sourceCat, frame=self.
_frame, pause=
False)