324 def __init__(self, refObjLoader=None, schema=None, **kwargs):
328 schema = SourceTable.makeMinimalSchema()
330 self.makeSubtask(
"background")
331 self.makeSubtask(
"installSimplePsf")
332 self.makeSubtask(
"repair")
333 if self.config.doMaskStreaks:
334 self.makeSubtask(
"maskStreaks")
335 self.makeSubtask(
"measurePsf", schema=self.
schema)
337 if self.config.doMeasurePsf
and self.measurePsf.usesMatches:
338 self.makeSubtask(
"ref_match", refObjLoader=refObjLoader)
340 self.makeSubtask(
'detection', schema=self.
schema)
341 if self.config.doDeblend:
342 self.makeSubtask(
"deblend", schema=self.
schema)
343 self.makeSubtask(
'measurement', schema=self.
schema, algMetadata=self.
algMetadata)
344 if self.config.doApCorr:
345 self.makeSubtask(
'measureApCorr', schema=self.
schema)
346 self.makeSubtask(
'applyApCorr', schema=self.
schema)
347 self.makeSubtask(
'catalogCalculation', schema=self.
schema)
350 self.
schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
354 inputs = butlerQC.get(inputRefs)
355 if 'idGenerator' not in inputs.keys():
356 inputs[
'idGenerator'] = self.config.idGenerator.apply(butlerQC.quantum.dataId)
357 outputs = self.
run(**inputs)
358 butlerQC.put(outputs, outputRefs)
361 def run(self, exposure, exposureIdInfo=None, background=None, idGenerator=None):
362 """Characterize a science image.
364 Peforms the following operations:
365 - Iterate the following config.psfIterations times, or once if config.doMeasurePsf false:
366 - detect and measure sources and estimate PSF (see detectMeasureAndEstimatePsf for details)
367 - interpolate over cosmic rays
368 - perform final measurement
372 exposure : `lsst.afw.image.ExposureF`
373 Exposure to characterize.
374 exposureIdInfo : `lsst.obs.base.ExposureIdInfo`, optional
375 Exposure ID info. Deprecated in favor of ``idGenerator``, and
376 ignored if that is provided.
377 background : `lsst.afw.math.BackgroundList`, optional
378 Initial model of background already subtracted from exposure.
379 idGenerator : `lsst.meas.base.IdGenerator`, optional
380 Object that generates source IDs and provides RNG seeds.
384 result : `lsst.pipe.base.Struct`
385 Results as a struct with attributes:
388 Characterized exposure (`lsst.afw.image.ExposureF`).
390 Detected sources (`lsst.afw.table.SourceCatalog`).
392 Model of subtracted background (`lsst.afw.math.BackgroundList`).
394 Spatial cells of PSF candidates (`lsst.afw.math.SpatialCellSet`).
396 Another reference to ``exposure`` for compatibility.
398 Another reference to ``background`` for compatibility.
403 Raised if PSF sigma is NaN.
407 if not self.config.doMeasurePsf
and not exposure.hasPsf():
408 self.log.info(
"CharacterizeImageTask initialized with 'simple' PSF.")
409 self.installSimplePsf.run(exposure=exposure)
411 if idGenerator
is None:
412 if exposureIdInfo
is not None:
413 idGenerator = IdGenerator._from_exposure_id_info(exposureIdInfo)
415 idGenerator = IdGenerator()
420 background = self.background.run(exposure).background
422 psfIterations = self.config.psfIterations
if self.config.doMeasurePsf
else 1
423 for i
in range(psfIterations):
426 idGenerator=idGenerator,
427 background=background,
430 psf = dmeRes.exposure.getPsf()
432 psfAvgPos = psf.getAveragePosition()
433 psfSigma = psf.computeShape(psfAvgPos).getDeterminantRadius()
434 psfDimensions = psf.computeImage(psfAvgPos).getDimensions()
435 medBackground = np.median(dmeRes.background.getImage().getArray())
436 self.log.info(
"iter %s; PSF sigma=%0.4f, dimensions=%s; median background=%0.2f",
437 i + 1, psfSigma, psfDimensions, medBackground)
438 if np.isnan(psfSigma):
439 raise RuntimeError(
"PSF sigma is NaN, cannot continue PSF determination.")
441 self.
display(
"psf", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
444 self.repair.run(exposure=dmeRes.exposure)
445 self.
display(
"repair", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
448 if self.config.doMaskStreaks:
449 _ = self.maskStreaks.run(dmeRes.exposure)
453 self.measurement.run(measCat=dmeRes.sourceCat, exposure=dmeRes.exposure,
454 exposureId=idGenerator.catalog_id)
455 if self.config.doApCorr:
457 apCorrMap = self.measureApCorr.run(
458 exposure=dmeRes.exposure,
459 catalog=dmeRes.sourceCat,
461 except MeasureApCorrError:
465 dmeRes.exposure.info.setApCorrMap(
None)
467 dmeRes.exposure.info.setApCorrMap(apCorrMap)
468 self.applyApCorr.run(catalog=dmeRes.sourceCat, apCorrMap=exposure.getInfo().getApCorrMap())
470 self.catalogCalculation.run(dmeRes.sourceCat)
472 self.
display(
"measure", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
474 return pipeBase.Struct(
475 exposure=dmeRes.exposure,
476 sourceCat=dmeRes.sourceCat,
477 background=dmeRes.background,
478 psfCellSet=dmeRes.psfCellSet,
480 characterized=dmeRes.exposure,
481 backgroundModel=dmeRes.background
486 """Perform one iteration of detect, measure, and estimate PSF.
488 Performs the following operations:
490 - if config.doMeasurePsf or not exposure.hasPsf():
492 - install a simple PSF model (replacing the existing one, if need be)
494 - interpolate over cosmic rays with keepCRs=True
495 - estimate background and subtract it from the exposure
496 - detect, deblend and measure sources, and subtract a refined background model;
497 - if config.doMeasurePsf:
502 exposure : `lsst.afw.image.ExposureF`
503 Exposure to characterize.
504 idGenerator : `lsst.meas.base.IdGenerator`
505 Object that generates source IDs and provides RNG seeds.
506 background : `lsst.afw.math.BackgroundList`, optional
507 Initial model of background already subtracted from exposure.
511 result : `lsst.pipe.base.Struct`
512 Results as a struct with attributes:
515 Characterized exposure (`lsst.afw.image.ExposureF`).
517 Detected sources (`lsst.afw.table.SourceCatalog`).
519 Model of subtracted background (`lsst.afw.math.BackgroundList`).
521 Spatial cells of PSF candidates (`lsst.afw.math.SpatialCellSet`).
526 Raised if there are too many CR pixels.
529 if not exposure.hasPsf()
or (self.config.doMeasurePsf
and self.config.useSimplePsf):
530 self.log.info(
"PSF estimation initialized with 'simple' PSF")
531 self.installSimplePsf.run(exposure=exposure)
534 if self.config.requireCrForPsf:
535 self.repair.run(exposure=exposure, keepCRs=
True)
538 self.repair.run(exposure=exposure, keepCRs=
True)
540 self.log.warning(
"Skipping cosmic ray detection: Too many CR pixels (max %0.f)",
541 self.config.repair.cosmicray.nCrPixelMax)
543 self.
display(
"repair_iter", exposure=exposure)
545 if background
is None:
546 background = BackgroundList()
548 sourceIdFactory = idGenerator.make_table_id_factory()
549 table = SourceTable.make(self.
schema, sourceIdFactory)
552 detRes = self.detection.run(table=table, exposure=exposure, doSmooth=
True)
553 sourceCat = detRes.sources
554 if detRes.background:
555 for bg
in detRes.background:
556 background.append(bg)
558 if self.config.doDeblend:
559 self.deblend.run(exposure=exposure, sources=sourceCat)
561 if not sourceCat.isContiguous():
562 sourceCat = sourceCat.copy(deep=
True)
564 self.measurement.run(measCat=sourceCat, exposure=exposure, exposureId=idGenerator.catalog_id)
566 measPsfRes = pipeBase.Struct(cellSet=
None)
567 if self.config.doMeasurePsf:
569 if self.measurePsf.usesMatches:
570 matches = self.ref_match.loadAndMatch(exposure=exposure, sourceCat=sourceCat).matches
573 measPsfRes = self.measurePsf.run(exposure=exposure, sources=sourceCat, matches=matches,
574 expId=idGenerator.catalog_id)
575 self.
display(
"measure_iter", exposure=exposure, sourceCat=sourceCat)
577 return pipeBase.Struct(
580 background=background,
581 psfCellSet=measPsfRes.cellSet,
584 def display(self, itemName, exposure, sourceCat=None):
585 """Display exposure and sources on next frame (for debugging).
590 Name of item in ``debugInfo``.
591 exposure : `lsst.afw.image.ExposureF`
593 sourceCat : `lsst.afw.table.SourceCatalog`, optional
594 Catalog of sources detected on the exposure.
596 val = getDebugFrame(self._display, itemName)
600 displayAstrometry(exposure=exposure, sourceCat=sourceCat, frame=self.
_frame, pause=
False)