332 schema = SourceTable.makeMinimalSchema()
334 self.makeSubtask(
"background")
335 self.makeSubtask(
"installSimplePsf")
336 self.makeSubtask(
"repair")
338 if self.config.doMaskStreaks:
339 self.makeSubtask(
"maskStreaks")
340 self.makeSubtask(
"measurePsf", schema=self.
schema)
342 self.makeSubtask(
'detection', schema=self.
schema)
343 if self.config.doDeblend:
344 self.makeSubtask(
"deblend", schema=self.
schema)
345 self.makeSubtask(
'measurement', schema=self.
schema, algMetadata=self.
algMetadata)
346 if self.config.doNormalizedCalibration:
347 self.makeSubtask(
'normalizedCalibrationFlux', schema=self.
schema)
348 if self.config.doApCorr:
349 self.makeSubtask(
'measureApCorr', schema=self.
schema)
350 self.makeSubtask(
'applyApCorr', schema=self.
schema)
351 self.makeSubtask(
'catalogCalculation', schema=self.
schema)
354 self.
schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
355 afwTable.CoordKey.addErrorFields(self.
schema)
366 def run(self, exposure, background=None, idGenerator=None):
367 """Characterize a science image.
369 Peforms the following operations:
370 - Iterate the following config.psfIterations times, or once if config.doMeasurePsf false:
371 - detect and measure sources and estimate PSF (see detectMeasureAndEstimatePsf for details)
372 - interpolate over cosmic rays
373 - perform final measurement
377 exposure : `lsst.afw.image.ExposureF`
378 Exposure to characterize.
379 background : `lsst.afw.math.BackgroundList`, optional
380 Initial model of background already subtracted from exposure.
381 idGenerator : `lsst.meas.base.IdGenerator`, optional
382 Object that generates source IDs and provides RNG seeds.
386 result : `lsst.pipe.base.Struct`
387 Results as a struct with attributes:
390 Characterized exposure (`lsst.afw.image.ExposureF`).
392 Detected sources (`lsst.afw.table.SourceCatalog`).
394 Model of subtracted background (`lsst.afw.math.BackgroundList`).
396 Spatial cells of PSF candidates (`lsst.afw.math.SpatialCellSet`).
398 Another reference to ``exposure`` for compatibility.
400 Another reference to ``background`` for compatibility.
405 Raised if PSF sigma is NaN.
409 if not self.config.doMeasurePsf
and not exposure.hasPsf():
410 self.log.info(
"CharacterizeImageTask initialized with 'simple' PSF.")
411 self.installSimplePsf.run(exposure=exposure)
413 if idGenerator
is None:
414 idGenerator = IdGenerator()
417 background = self.background.run(exposure).background
419 psfIterations = self.config.psfIterations
if self.config.doMeasurePsf
else 1
420 for i
in range(psfIterations):
423 idGenerator=idGenerator,
424 background=background,
427 psf = dmeRes.exposure.getPsf()
429 psfAvgPos = psf.getAveragePosition()
430 psfSigma = psf.computeShape(psfAvgPos).getDeterminantRadius()
431 psfDimensions = psf.computeImage(psfAvgPos).getDimensions()
432 medBackground = np.median(dmeRes.background.getImage().getArray())
433 self.log.info(
"iter %s; PSF sigma=%0.4f, dimensions=%s; median background=%0.2f",
434 i + 1, psfSigma, psfDimensions, medBackground)
435 if np.isnan(psfSigma):
436 raise RuntimeError(
"PSF sigma is NaN, cannot continue PSF determination.")
438 self.
display(
"psf", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
441 self.repair.run(exposure=dmeRes.exposure)
442 self.
display(
"repair", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
446 if self.config.doMaskStreaks:
447 _ = self.maskStreaks.run(dmeRes.exposure)
451 self.measurement.run(measCat=dmeRes.sourceCat, exposure=dmeRes.exposure,
452 exposureId=idGenerator.catalog_id)
454 if self.config.doNormalizedCalibration:
455 normApCorrMap = self.normalizedCalibrationFlux.run(
456 exposure=dmeRes.exposure,
457 catalog=dmeRes.sourceCat,
459 dmeRes.exposure.info.setApCorrMap(normApCorrMap)
463 if self.config.doApCorr:
468 apCorrMap = self.measureApCorr.run(
469 exposure=dmeRes.exposure,
470 catalog=dmeRes.sourceCat,
472 except MeasureApCorrError:
476 dmeRes.exposure.info.setApCorrMap(
None)
480 for key
in normApCorrMap:
481 apCorrMap[key] = normApCorrMap[key]
482 dmeRes.exposure.info.setApCorrMap(apCorrMap)
483 self.applyApCorr.run(catalog=dmeRes.sourceCat, apCorrMap=exposure.getInfo().getApCorrMap())
485 self.catalogCalculation.run(dmeRes.sourceCat)
487 self.
display(
"measure", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
489 return pipeBase.Struct(
490 exposure=dmeRes.exposure,
491 sourceCat=dmeRes.sourceCat,
492 background=dmeRes.background,
493 psfCellSet=dmeRes.psfCellSet,
495 characterized=dmeRes.exposure,
496 backgroundModel=dmeRes.background
501 """Perform one iteration of detect, measure, and estimate PSF.
503 Performs the following operations:
505 - if config.doMeasurePsf or not exposure.hasPsf():
507 - install a simple PSF model (replacing the existing one, if need be)
509 - interpolate over cosmic rays with keepCRs=True
510 - estimate background and subtract it from the exposure
511 - detect, deblend and measure sources, and subtract a refined background model;
512 - if config.doMeasurePsf:
517 exposure : `lsst.afw.image.ExposureF`
518 Exposure to characterize.
519 idGenerator : `lsst.meas.base.IdGenerator`
520 Object that generates source IDs and provides RNG seeds.
521 background : `lsst.afw.math.BackgroundList`, optional
522 Initial model of background already subtracted from exposure.
526 result : `lsst.pipe.base.Struct`
527 Results as a struct with attributes:
530 Characterized exposure (`lsst.afw.image.ExposureF`).
532 Detected sources (`lsst.afw.table.SourceCatalog`).
534 Model of subtracted background (`lsst.afw.math.BackgroundList`).
536 Spatial cells of PSF candidates (`lsst.afw.math.SpatialCellSet`).
541 Raised if there are too many CR pixels.
544 if not exposure.hasPsf()
or (self.config.doMeasurePsf
and self.config.useSimplePsf):
545 self.log.info(
"PSF estimation initialized with 'simple' PSF")
546 self.installSimplePsf.run(exposure=exposure)
549 if self.config.requireCrForPsf:
550 self.repair.run(exposure=exposure, keepCRs=
True)
553 self.repair.run(exposure=exposure, keepCRs=
True)
555 self.log.warning(
"Skipping cosmic ray detection: Too many CR pixels (max %0.f)",
556 self.config.repair.cosmicray.nCrPixelMax)
558 self.
display(
"repair_iter", exposure=exposure)
560 if background
is None:
561 background = BackgroundList()
565 doc=
"PSF max value.",
568 sourceIdFactory = idGenerator.make_table_id_factory()
569 table = SourceTable.make(self.
schema, sourceIdFactory)
572 detRes = self.detection.run(table=table, exposure=exposure, doSmooth=
True)
573 sourceCat = detRes.sources
574 if detRes.background:
575 for bg
in detRes.background:
576 background.append(bg)
578 if self.config.doDeblend:
579 self.deblend.run(exposure=exposure, sources=sourceCat)
581 if not sourceCat.isContiguous():
582 sourceCat = sourceCat.copy(deep=
True)
584 self.measurement.run(measCat=sourceCat, exposure=exposure, exposureId=idGenerator.catalog_id)
586 measPsfRes = pipeBase.Struct(cellSet=
None)
587 if self.config.doMeasurePsf:
588 measPsfRes = self.measurePsf.run(exposure=exposure, sources=sourceCat,
589 expId=idGenerator.catalog_id)
590 self.
display(
"measure_iter", exposure=exposure, sourceCat=sourceCat)
592 return pipeBase.Struct(
595 background=background,
596 psfCellSet=measPsfRes.cellSet,