344 def run(self, exposure, background=None, idGenerator=None):
345 """Characterize a science image.
346
347 Peforms the following operations:
348 - Iterate the following config.psfIterations times, or once if config.doMeasurePsf false:
349 - detect and measure sources and estimate PSF (see detectMeasureAndEstimatePsf for details)
350 - interpolate over cosmic rays
351 - perform final measurement
352
353 Parameters
354 ----------
355 exposure : `lsst.afw.image.ExposureF`
356 Exposure to characterize.
357 background : `lsst.afw.math.BackgroundList`, optional
358 Initial model of background already subtracted from exposure.
359 idGenerator : `lsst.meas.base.IdGenerator`, optional
360 Object that generates source IDs and provides RNG seeds.
361
362 Returns
363 -------
364 result : `lsst.pipe.base.Struct`
365 Results as a struct with attributes:
366
367 ``exposure``
368 Characterized exposure (`lsst.afw.image.ExposureF`).
369 ``sourceCat``
370 Detected sources (`lsst.afw.table.SourceCatalog`).
371 ``background``
372 Model of subtracted background (`lsst.afw.math.BackgroundList`).
373 ``psfCellSet``
374 Spatial cells of PSF candidates (`lsst.afw.math.SpatialCellSet`).
375 ``characterized``
376 Another reference to ``exposure`` for compatibility.
377 ``backgroundModel``
378 Another reference to ``background`` for compatibility.
379
380 Raises
381 ------
382 RuntimeError
383 Raised if PSF sigma is NaN.
384 """
385 self._frame = self._initialFrame
386
387 if not self.config.doMeasurePsf and not exposure.hasPsf():
388 self.log.info("CharacterizeImageTask initialized with 'simple' PSF.")
389 self.installSimplePsf.run(exposure=exposure)
390
391 if idGenerator is None:
392 idGenerator = IdGenerator()
393
394
395 background = self.background.run(exposure).background
396
397 psfIterations = self.config.psfIterations if self.config.doMeasurePsf else 1
398 for i in range(psfIterations):
399 dmeRes = self.detectMeasureAndEstimatePsf(
400 exposure=exposure,
401 idGenerator=idGenerator,
402 background=background,
403 )
404
405 psf = dmeRes.exposure.getPsf()
406
407 psfAvgPos = psf.getAveragePosition()
408 psfSigma = psf.computeShape(psfAvgPos).getDeterminantRadius()
409 psfDimensions = psf.computeImage(psfAvgPos).getDimensions()
410 medBackground = np.median(dmeRes.background.getImage().getArray())
411 self.log.info("iter %s; PSF sigma=%0.4f, dimensions=%s; median background=%0.2f",
412 i + 1, psfSigma, psfDimensions, medBackground)
413 if np.isnan(psfSigma):
414 raise RuntimeError("PSF sigma is NaN, cannot continue PSF determination.")
415
416 self.display("psf", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
417
418
419 self.repair.run(exposure=dmeRes.exposure)
420 self.display("repair", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
421
422
423 if self.config.doMaskStreaks:
424 _ = self.maskStreaks.run(dmeRes.exposure)
425
426
427
428 self.measurement.run(measCat=dmeRes.sourceCat, exposure=dmeRes.exposure,
429 exposureId=idGenerator.catalog_id)
430 if self.config.doApCorr:
431 try:
432 apCorrMap = self.measureApCorr.run(
433 exposure=dmeRes.exposure,
434 catalog=dmeRes.sourceCat,
435 ).apCorrMap
436 except MeasureApCorrError:
437
438
439
440 dmeRes.exposure.info.setApCorrMap(None)
441 else:
442 dmeRes.exposure.info.setApCorrMap(apCorrMap)
443 self.applyApCorr.run(catalog=dmeRes.sourceCat, apCorrMap=exposure.getInfo().getApCorrMap())
444
445 self.catalogCalculation.run(dmeRes.sourceCat)
446
447 self.display("measure", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
448
449 return pipeBase.Struct(
450 exposure=dmeRes.exposure,
451 sourceCat=dmeRes.sourceCat,
452 background=dmeRes.background,
453 psfCellSet=dmeRes.psfCellSet,
454
455 characterized=dmeRes.exposure,
456 backgroundModel=dmeRes.background
457 )
458