313 schema = SourceTable.makeMinimalSchema()
315 self.makeSubtask(
"background")
316 self.makeSubtask(
"installSimplePsf")
317 self.makeSubtask(
"repair")
318 if self.config.doMaskStreaks:
319 self.makeSubtask(
"maskStreaks")
320 self.makeSubtask(
"measurePsf", schema=self.
schema)
322 self.makeSubtask(
'detection', schema=self.
schema)
323 if self.config.doDeblend:
324 self.makeSubtask(
"deblend", schema=self.
schema)
325 self.makeSubtask(
'measurement', schema=self.
schema, algMetadata=self.
algMetadata)
326 if self.config.doApCorr:
327 self.makeSubtask(
'measureApCorr', schema=self.
schema)
328 self.makeSubtask(
'applyApCorr', schema=self.
schema)
329 self.makeSubtask(
'catalogCalculation', schema=self.
schema)
332 self.
schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
336 inputs = butlerQC.get(inputRefs)
337 if 'idGenerator' not in inputs.keys():
338 inputs[
'idGenerator'] = self.config.idGenerator.apply(butlerQC.quantum.dataId)
339 outputs = self.
run(**inputs)
340 butlerQC.put(outputs, outputRefs)
343 def run(self, exposure, exposureIdInfo=None, background=None, idGenerator=None):
344 """Characterize a science image.
346 Peforms the following operations:
347 - Iterate the following config.psfIterations times, or once if config.doMeasurePsf false:
348 - detect and measure sources and estimate PSF (see detectMeasureAndEstimatePsf for details)
349 - interpolate over cosmic rays
350 - perform final measurement
354 exposure : `lsst.afw.image.ExposureF`
355 Exposure to characterize.
356 exposureIdInfo : `lsst.obs.base.ExposureIdInfo`, optional
357 Exposure ID info. Deprecated in favor of ``idGenerator``, and
358 ignored if that is provided.
359 background : `lsst.afw.math.BackgroundList`, optional
360 Initial model of background already subtracted from exposure.
361 idGenerator : `lsst.meas.base.IdGenerator`, optional
362 Object that generates source IDs and provides RNG seeds.
366 result : `lsst.pipe.base.Struct`
367 Results as a struct with attributes:
370 Characterized exposure (`lsst.afw.image.ExposureF`).
372 Detected sources (`lsst.afw.table.SourceCatalog`).
374 Model of subtracted background (`lsst.afw.math.BackgroundList`).
376 Spatial cells of PSF candidates (`lsst.afw.math.SpatialCellSet`).
378 Another reference to ``exposure`` for compatibility.
380 Another reference to ``background`` for compatibility.
385 Raised if PSF sigma is NaN.
389 if not self.config.doMeasurePsf
and not exposure.hasPsf():
390 self.log.info(
"CharacterizeImageTask initialized with 'simple' PSF.")
391 self.installSimplePsf.run(exposure=exposure)
393 if idGenerator
is None:
394 if exposureIdInfo
is not None:
395 idGenerator = IdGenerator._from_exposure_id_info(exposureIdInfo)
397 idGenerator = IdGenerator()
402 background = self.background.run(exposure).background
404 psfIterations = self.config.psfIterations
if self.config.doMeasurePsf
else 1
405 for i
in range(psfIterations):
408 idGenerator=idGenerator,
409 background=background,
412 psf = dmeRes.exposure.getPsf()
414 psfAvgPos = psf.getAveragePosition()
415 psfSigma = psf.computeShape(psfAvgPos).getDeterminantRadius()
416 psfDimensions = psf.computeImage(psfAvgPos).getDimensions()
417 medBackground = np.median(dmeRes.background.getImage().getArray())
418 self.log.info(
"iter %s; PSF sigma=%0.4f, dimensions=%s; median background=%0.2f",
419 i + 1, psfSigma, psfDimensions, medBackground)
420 if np.isnan(psfSigma):
421 raise RuntimeError(
"PSF sigma is NaN, cannot continue PSF determination.")
423 self.
display(
"psf", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
426 self.repair.run(exposure=dmeRes.exposure)
427 self.
display(
"repair", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
430 if self.config.doMaskStreaks:
431 _ = self.maskStreaks.run(dmeRes.exposure)
435 self.measurement.run(measCat=dmeRes.sourceCat, exposure=dmeRes.exposure,
436 exposureId=idGenerator.catalog_id)
437 if self.config.doApCorr:
439 apCorrMap = self.measureApCorr.run(
440 exposure=dmeRes.exposure,
441 catalog=dmeRes.sourceCat,
443 except MeasureApCorrError:
447 dmeRes.exposure.info.setApCorrMap(
None)
449 dmeRes.exposure.info.setApCorrMap(apCorrMap)
450 self.applyApCorr.run(catalog=dmeRes.sourceCat, apCorrMap=exposure.getInfo().getApCorrMap())
452 self.catalogCalculation.run(dmeRes.sourceCat)
454 self.
display(
"measure", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
456 return pipeBase.Struct(
457 exposure=dmeRes.exposure,
458 sourceCat=dmeRes.sourceCat,
459 background=dmeRes.background,
460 psfCellSet=dmeRes.psfCellSet,
462 characterized=dmeRes.exposure,
463 backgroundModel=dmeRes.background
468 """Perform one iteration of detect, measure, and estimate PSF.
470 Performs the following operations:
472 - if config.doMeasurePsf or not exposure.hasPsf():
474 - install a simple PSF model (replacing the existing one, if need be)
476 - interpolate over cosmic rays with keepCRs=True
477 - estimate background and subtract it from the exposure
478 - detect, deblend and measure sources, and subtract a refined background model;
479 - if config.doMeasurePsf:
484 exposure : `lsst.afw.image.ExposureF`
485 Exposure to characterize.
486 idGenerator : `lsst.meas.base.IdGenerator`
487 Object that generates source IDs and provides RNG seeds.
488 background : `lsst.afw.math.BackgroundList`, optional
489 Initial model of background already subtracted from exposure.
493 result : `lsst.pipe.base.Struct`
494 Results as a struct with attributes:
497 Characterized exposure (`lsst.afw.image.ExposureF`).
499 Detected sources (`lsst.afw.table.SourceCatalog`).
501 Model of subtracted background (`lsst.afw.math.BackgroundList`).
503 Spatial cells of PSF candidates (`lsst.afw.math.SpatialCellSet`).
508 Raised if there are too many CR pixels.
511 if not exposure.hasPsf()
or (self.config.doMeasurePsf
and self.config.useSimplePsf):
512 self.log.info(
"PSF estimation initialized with 'simple' PSF")
513 self.installSimplePsf.run(exposure=exposure)
516 if self.config.requireCrForPsf:
517 self.repair.run(exposure=exposure, keepCRs=
True)
520 self.repair.run(exposure=exposure, keepCRs=
True)
522 self.log.warning(
"Skipping cosmic ray detection: Too many CR pixels (max %0.f)",
523 self.config.repair.cosmicray.nCrPixelMax)
525 self.
display(
"repair_iter", exposure=exposure)
527 if background
is None:
528 background = BackgroundList()
530 sourceIdFactory = idGenerator.make_table_id_factory()
531 table = SourceTable.make(self.
schema, sourceIdFactory)
534 detRes = self.detection.run(table=table, exposure=exposure, doSmooth=
True)
535 sourceCat = detRes.sources
536 if detRes.background:
537 for bg
in detRes.background:
538 background.append(bg)
540 if self.config.doDeblend:
541 self.deblend.run(exposure=exposure, sources=sourceCat)
543 if not sourceCat.isContiguous():
544 sourceCat = sourceCat.copy(deep=
True)
546 self.measurement.run(measCat=sourceCat, exposure=exposure, exposureId=idGenerator.catalog_id)
548 measPsfRes = pipeBase.Struct(cellSet=
None)
549 if self.config.doMeasurePsf:
550 measPsfRes = self.measurePsf.run(exposure=exposure, sources=sourceCat,
551 expId=idGenerator.catalog_id)
552 self.
display(
"measure_iter", exposure=exposure, sourceCat=sourceCat)
554 return pipeBase.Struct(
557 background=background,
558 psfCellSet=measPsfRes.cellSet,
561 def display(self, itemName, exposure, sourceCat=None):
562 """Display exposure and sources on next frame (for debugging).
567 Name of item in ``debugInfo``.
568 exposure : `lsst.afw.image.ExposureF`
570 sourceCat : `lsst.afw.table.SourceCatalog`, optional
571 Catalog of sources detected on the exposure.
573 val = getDebugFrame(self._display, itemName)
577 displayAstrometry(exposure=exposure, sourceCat=sourceCat, frame=self.
_frame, pause=
False)