Coverage for python/lsst/pipe/tasks/calibrateImage.py: 34%
173 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-20 11:05 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-20 11:05 +0000
1# This file is part of pipe_tasks.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
22import lsst.afw.table as afwTable
23import lsst.afw.image as afwImage
24import lsst.meas.algorithms
25import lsst.meas.algorithms.installGaussianPsf
26import lsst.meas.algorithms.measureApCorr
27from lsst.meas.algorithms import sourceSelector
28import lsst.meas.astrom
29import lsst.meas.deblender
30import lsst.meas.extensions.shapeHSM
31import lsst.pex.config as pexConfig
32import lsst.pipe.base as pipeBase
33from lsst.pipe.base import connectionTypes
34from lsst.utils.timer import timeMethod
36from . import measurePsf, repair, setPrimaryFlags, photoCal, computeExposureSummaryStats
39class CalibrateImageConnections(pipeBase.PipelineTaskConnections,
40 dimensions=("instrument", "visit", "detector")):
42 astrometry_ref_cat = connectionTypes.PrerequisiteInput(
43 doc="Reference catalog to use for astrometric calibration.",
44 name="gaia_dr3_20230707",
45 storageClass="SimpleCatalog",
46 dimensions=("skypix",),
47 deferLoad=True,
48 multiple=True,
49 )
50 photometry_ref_cat = connectionTypes.PrerequisiteInput(
51 doc="Reference catalog to use for photometric calibration.",
52 name="ps1_pv3_3pi_20170110",
53 storageClass="SimpleCatalog",
54 dimensions=("skypix",),
55 deferLoad=True,
56 multiple=True
57 )
59 exposure = connectionTypes.Input(
60 doc="Exposure to be calibrated, and detected and measured on.",
61 name="postISRCCD",
62 storageClass="Exposure",
63 dimensions=["instrument", "exposure", "detector"],
64 )
66 # outputs
67 initial_stars_schema = connectionTypes.InitOutput(
68 doc="Schema of the output initial stars catalog.",
69 name="initial_stars_schema",
70 storageClass="SourceCatalog",
71 )
73 # TODO: We want some kind of flag on Exposures/Catalogs to make it obvious
74 # which components had failed to be computed/persisted
75 output_exposure = connectionTypes.Output(
76 doc="Photometrically calibrated exposure with fitted calibrations and summary statistics.",
77 name="initial_pvi",
78 storageClass="ExposureF",
79 dimensions=("instrument", "visit", "detector"),
80 )
81 # TODO DM-40061: persist a parquet version of this!
82 stars = connectionTypes.Output(
83 doc="Catalog of unresolved sources detected on the calibrated exposure; "
84 "includes source footprints.",
85 name="initial_stars_footprints_detector",
86 storageClass="SourceCatalog",
87 dimensions=["instrument", "visit", "detector"],
88 )
89 applied_photo_calib = connectionTypes.Output(
90 doc="Photometric calibration that was applied to exposure.",
91 name="initial_photoCalib_detector",
92 storageClass="PhotoCalib",
93 dimensions=("instrument", "visit", "detector"),
94 )
95 background = connectionTypes.Output(
96 doc="Background models estimated during calibration task.",
97 name="initial_pvi_background",
98 storageClass="Background",
99 dimensions=("instrument", "visit", "detector"),
100 )
102 # Optional outputs
104 # TODO: We need to decide on what intermediate outputs we want to save,
105 # and which to save by default.
106 # TODO DM-40061: persist a parquet version of this!
107 psf_stars = connectionTypes.Output(
108 doc="Catalog of bright unresolved sources detected on the exposure used for PSF determination; "
109 "includes source footprints.",
110 name="initial_psf_stars_footprints",
111 storageClass="SourceCatalog",
112 dimensions=["instrument", "visit", "detector"],
113 )
114 astrometry_matches = connectionTypes.Output(
115 doc="Source to reference catalog matches from the astrometry solver.",
116 name="initial_astrometry_match_detector",
117 storageClass="Catalog",
118 dimensions=("instrument", "visit", "detector"),
119 )
120 photometry_matches = connectionTypes.Output(
121 doc="Source to reference catalog matches from the photometry solver.",
122 name="initial_photometry_match_detector",
123 storageClass="Catalog",
124 dimensions=("instrument", "visit", "detector"),
125 )
127 def __init__(self, *, config=None):
128 super().__init__(config=config)
129 if not config.optional_outputs:
130 self.outputs.remove("psf_stars")
131 self.outputs.remove("astrometry_matches")
132 self.outputs.remove("photometry_matches")
135class CalibrateImageConfig(pipeBase.PipelineTaskConfig, pipelineConnections=CalibrateImageConnections):
136 optional_outputs = pexConfig.ListField(
137 doc="Which optional outputs to save (as their connection name)?",
138 dtype=str,
139 # TODO: note somewhere to disable this for benchmarking, but should
140 # we always have it on for production runs?
141 default=["psf_stars", "astrometry_matches", "photometry_matches"],
142 optional=True
143 )
145 # subtasks used during psf characterization
146 install_simple_psf = pexConfig.ConfigurableField(
147 target=lsst.meas.algorithms.installGaussianPsf.InstallGaussianPsfTask,
148 doc="Task to install a simple PSF model into the input exposure to use "
149 "when detecting bright sources for PSF estimation.",
150 )
151 psf_repair = pexConfig.ConfigurableField(
152 target=repair.RepairTask,
153 doc="Task to repair cosmic rays on the exposure before PSF determination.",
154 )
155 psf_subtract_background = pexConfig.ConfigurableField(
156 target=lsst.meas.algorithms.SubtractBackgroundTask,
157 doc="Task to perform intial background subtraction, before first detection pass.",
158 )
159 psf_detection = pexConfig.ConfigurableField(
160 target=lsst.meas.algorithms.SourceDetectionTask,
161 doc="Task to detect sources for PSF determination."
162 )
163 psf_source_measurement = pexConfig.ConfigurableField(
164 target=lsst.meas.base.SingleFrameMeasurementTask,
165 doc="Task to measure sources to be used for psf estimation."
166 )
167 psf_measure_psf = pexConfig.ConfigurableField(
168 target=measurePsf.MeasurePsfTask,
169 doc="Task to measure the psf on bright sources."
170 )
172 # TODO DM-39203: we can remove aperture correction from this task once we are
173 # using the shape-based star/galaxy code.
174 measure_aperture_correction = pexConfig.ConfigurableField(
175 target=lsst.meas.algorithms.measureApCorr.MeasureApCorrTask,
176 doc="Task to compute the aperture correction from the bright stars."
177 )
179 # subtasks used during star measurement
180 star_detection = pexConfig.ConfigurableField(
181 target=lsst.meas.algorithms.SourceDetectionTask,
182 doc="Task to detect stars to return in the output catalog."
183 )
184 star_deblend = pexConfig.ConfigurableField(
185 target=lsst.meas.deblender.SourceDeblendTask,
186 doc="Split blended sources into their components"
187 )
188 star_measurement = pexConfig.ConfigurableField(
189 target=lsst.meas.base.SingleFrameMeasurementTask,
190 doc="Task to measure stars to return in the output catalog."
191 )
192 star_apply_aperture_correction = pexConfig.ConfigurableField(
193 target=lsst.meas.base.ApplyApCorrTask,
194 doc="Task to apply aperture corrections to the selected stars."
195 )
196 star_catalog_calculation = pexConfig.ConfigurableField(
197 target=lsst.meas.base.CatalogCalculationTask,
198 doc="Task to compute extendedness values on the star catalog, "
199 "for the star selector to remove extended sources."
200 )
201 star_set_primary_flags = pexConfig.ConfigurableField(
202 target=setPrimaryFlags.SetPrimaryFlagsTask,
203 doc="Task to add isPrimary to the catalog."
204 )
205 star_selector = lsst.meas.algorithms.sourceSelectorRegistry.makeField(
206 default="science",
207 doc="Task to select isolated stars to use for calibration."
208 )
210 # final calibrations and statistics
211 astrometry = pexConfig.ConfigurableField(
212 target=lsst.meas.astrom.AstrometryTask,
213 doc="Task to perform astrometric calibration to fit a WCS.",
214 )
215 astrometry_ref_loader = pexConfig.ConfigField(
216 dtype=lsst.meas.algorithms.LoadReferenceObjectsConfig,
217 doc="Configuration of reference object loader for astrometric fit.",
218 )
219 photometry = pexConfig.ConfigurableField(
220 target=photoCal.PhotoCalTask,
221 doc="Task to perform photometric calibration to fit a PhotoCalib.",
222 )
223 photometry_ref_loader = pexConfig.ConfigField(
224 dtype=lsst.meas.algorithms.LoadReferenceObjectsConfig,
225 doc="Configuration of reference object loader for photometric fit.",
226 )
228 compute_summary_stats = pexConfig.ConfigurableField(
229 target=computeExposureSummaryStats.ComputeExposureSummaryStatsTask,
230 doc="Task to to compute summary statistics on the calibrated exposure."
231 )
233 def setDefaults(self):
234 super().setDefaults()
236 # Use a very broad PSF here, to throughly reject CRs.
237 # TODO investigation: a large initial psf guess may make stars look
238 # like CRs for very good seeing images.
239 self.install_simple_psf.fwhm = 4
241 # Only use high S/N sources for PSF determination.
242 self.psf_detection.thresholdValue = 50.0
243 # TODO investigation: Probably want False here, but that may require
244 # tweaking the background spatial scale, to make it small enough to
245 # prevent extra peaks in the wings of bright objects.
246 self.psf_detection.doTempLocalBackground = False
247 # NOTE: we do want reEstimateBackground=True in psf_detection, so that
248 # each measurement step is done with the best background available.
250 # Minimal measurement plugins for PSF determination.
251 # TODO DM-39203: We can drop GaussianFlux and PsfFlux, if we use
252 # shapeHSM/moments for star/galaxy separation.
253 # TODO DM-39203: we can remove aperture correction from this task once
254 # we are using the shape-based star/galaxy code.
255 self.psf_source_measurement.plugins = ["base_PixelFlags",
256 "base_SdssCentroid",
257 "ext_shapeHSM_HsmSourceMoments",
258 "base_CircularApertureFlux",
259 "base_GaussianFlux",
260 "base_PsfFlux",
261 ]
262 self.psf_source_measurement.slots.shape = "ext_shapeHSM_HsmSourceMoments"
263 # Only measure apertures we need for PSF measurement.
264 # TODO DM-40064: psfex has a hard-coded value of 9 in a psfex-config
265 # file: make that configurable and/or change it to 12 to be consistent
266 # with our other uses?
267 # https://github.com/lsst/meas_extensions_psfex/blob/main/config/default-lsst.psfex#L14
268 self.psf_source_measurement.plugins["base_CircularApertureFlux"].radii = [9.0, 12.0]
270 # No extendeness information available: we need the aperture
271 # corrections to determine that.
272 self.measure_aperture_correction.sourceSelector["science"].doUnresolved = False
273 self.measure_aperture_correction.sourceSelector["science"].flags.good = ["calib_psf_used"]
274 self.measure_aperture_correction.sourceSelector["science"].flags.bad = []
276 # TODO investigation: how faint do we have to detect, to be able to
277 # deblend, etc? We may need star_selector to have a separate value,
278 # and do initial detection at S/N>5.0?
279 # Detection for good S/N for astrometry/photometry and other
280 # downstream tasks.
281 self.star_detection.thresholdValue = 10.0
282 self.star_measurement.plugins = ["base_PixelFlags",
283 "base_SdssCentroid",
284 "ext_shapeHSM_HsmSourceMoments",
285 'ext_shapeHSM_HsmPsfMoments',
286 "base_GaussianFlux",
287 "base_PsfFlux",
288 "base_CircularApertureFlux",
289 ]
290 self.star_measurement.slots.psfShape = "ext_shapeHSM_HsmPsfMoments"
291 self.star_measurement.slots.shape = "ext_shapeHSM_HsmSourceMoments"
292 # Only measure the apertures we need for star selection.
293 self.star_measurement.plugins["base_CircularApertureFlux"].radii = [12.0]
295 # Select isolated stars with reliable measurements and no bad flags.
296 self.star_selector["science"].doFlags = True
297 self.star_selector["science"].doUnresolved = True
298 self.star_selector["science"].doSignalToNoise = True
299 self.star_selector["science"].doIsolated = True
300 self.star_selector["science"].signalToNoise.minimum = 10.0
302 # Use the affine WCS fitter (assumes we have a good camera geometry).
303 self.astrometry.wcsFitter.retarget(lsst.meas.astrom.FitAffineWcsTask)
304 # phot_g_mean is the primary Gaia band for all input bands.
305 self.astrometry_ref_loader.anyFilterMapsToThis = "phot_g_mean"
307 # Do not subselect during fitting; we already selected good stars.
308 self.astrometry.sourceSelector = "null"
309 self.photometry.match.sourceSelection.retarget(sourceSelector.NullSourceSelectorTask)
311 # All sources should be good for PSF summary statistics.
312 self.compute_summary_stats.starSelection = "calib_photometry_used"
315class CalibrateImageTask(pipeBase.PipelineTask):
316 """Compute the PSF, aperture corrections, astrometric and photometric
317 calibrations, and summary statistics for a single science exposure, and
318 produce a catalog of brighter stars that were used to calibrate it.
320 Parameters
321 ----------
322 initial_stars_schema : `lsst.afw.table.Schema`
323 Schema of the initial_stars output catalog.
324 """
325 _DefaultName = "calibrateImage"
326 ConfigClass = CalibrateImageConfig
328 def __init__(self, initial_stars_schema=None, **kwargs):
329 super().__init__(**kwargs)
331 # PSF determination subtasks
332 self.makeSubtask("install_simple_psf")
333 self.makeSubtask("psf_repair")
334 self.makeSubtask("psf_subtract_background")
335 self.psf_schema = afwTable.SourceTable.makeMinimalSchema()
336 self.makeSubtask("psf_detection", schema=self.psf_schema)
337 self.makeSubtask("psf_source_measurement", schema=self.psf_schema)
338 self.makeSubtask("psf_measure_psf", schema=self.psf_schema)
340 self.makeSubtask("measure_aperture_correction", schema=self.psf_schema)
342 # star measurement subtasks
343 if initial_stars_schema is None:
344 initial_stars_schema = afwTable.SourceTable.makeMinimalSchema()
345 afwTable.CoordKey.addErrorFields(initial_stars_schema)
346 self.makeSubtask("star_detection", schema=initial_stars_schema)
347 self.makeSubtask("star_deblend", schema=initial_stars_schema)
348 self.makeSubtask("star_measurement", schema=initial_stars_schema)
349 self.makeSubtask("star_apply_aperture_correction", schema=initial_stars_schema)
350 self.makeSubtask("star_catalog_calculation", schema=initial_stars_schema)
351 self.makeSubtask("star_set_primary_flags", schema=initial_stars_schema, isSingleFrame=True)
352 self.makeSubtask("star_selector")
354 self.makeSubtask("astrometry", schema=initial_stars_schema)
355 self.makeSubtask("photometry", schema=initial_stars_schema)
357 self.makeSubtask("compute_summary_stats")
359 # For the butler to persist it.
360 self.initial_stars_schema = afwTable.SourceCatalog(initial_stars_schema)
362 def runQuantum(self, butlerQC, inputRefs, outputRefs):
363 inputs = butlerQC.get(inputRefs)
365 astrometry_loader = lsst.meas.algorithms.ReferenceObjectLoader(
366 dataIds=[ref.datasetRef.dataId for ref in inputRefs.astrometry_ref_cat],
367 refCats=inputs.pop("astrometry_ref_cat"),
368 name=self.config.connections.astrometry_ref_cat,
369 config=self.config.astrometry_ref_loader, log=self.log)
370 self.astrometry.setRefObjLoader(astrometry_loader)
372 photometry_loader = lsst.meas.algorithms.ReferenceObjectLoader(
373 dataIds=[ref.datasetRef.dataId for ref in inputRefs.photometry_ref_cat],
374 refCats=inputs.pop("photometry_ref_cat"),
375 name=self.config.connections.photometry_ref_cat,
376 config=self.config.photometry_ref_loader, log=self.log)
377 self.photometry.match.setRefObjLoader(photometry_loader)
379 outputs = self.run(**inputs)
381 butlerQC.put(outputs, outputRefs)
383 @timeMethod
384 def run(self, *, exposure):
385 """Find stars and perform psf measurement, then do a deeper detection
386 and measurement and calibrate astrometry and photometry from that.
388 Parameters
389 ----------
390 exposure : `lsst.afw.image.Exposure`
391 Post-ISR exposure, with an initial WCS, VisitInfo, and Filter.
392 Modified in-place during processing.
394 Returns
395 -------
396 result : `lsst.pipe.base.Struct`
397 Results as a struct with attributes:
399 ``output_exposure``
400 Calibrated exposure, with pixels in nJy units.
401 (`lsst.afw.image.Exposure`)
402 ``stars``
403 Stars that were used to calibrate the exposure, with
404 calibrated fluxes and magnitudes.
405 (`lsst.afw.table.SourceCatalog`)
406 ``psf_stars``
407 Stars that were used to determine the image PSF.
408 (`lsst.afw.table.SourceCatalog`)
409 ``background``
410 Background that was fit to the exposure when detecting
411 ``stars``. (`lsst.afw.math.BackgroundList`)
412 ``applied_photo_calib``
413 Photometric calibration that was fit to the star catalog and
414 applied to the exposure. (`lsst.afw.image.PhotoCalib`)
415 ``astrometry_matches``
416 Reference catalog stars matches used in the astrometric fit.
417 (`list` [`lsst.afw.table.ReferenceMatch`] or `lsst.afw.table.BaseCatalog`)
418 ``photometry_matches``
419 Reference catalog stars matches used in the photometric fit.
420 (`list` [`lsst.afw.table.ReferenceMatch`] or `lsst.afw.table.BaseCatalog`)
421 """
422 psf_stars, background, candidates = self._compute_psf(exposure)
424 self._measure_aperture_correction(exposure, psf_stars)
426 stars = self._find_stars(exposure, background)
428 astrometry_matches, astrometry_meta = self._fit_astrometry(exposure, stars)
429 stars, photometry_matches, photometry_meta, photo_calib = self._fit_photometry(exposure, stars)
431 self._summarize(exposure, stars, background)
433 if self.config.optional_outputs:
434 astrometry_matches = lsst.meas.astrom.denormalizeMatches(astrometry_matches, astrometry_meta)
435 photometry_matches = lsst.meas.astrom.denormalizeMatches(photometry_matches, photometry_meta)
437 return pipeBase.Struct(output_exposure=exposure,
438 stars=stars,
439 psf_stars=psf_stars,
440 background=background,
441 applied_photo_calib=photo_calib,
442 astrometry_matches=astrometry_matches,
443 photometry_matches=photometry_matches)
445 def _compute_psf(self, exposure, guess_psf=True):
446 """Find bright sources detected on an exposure and fit a PSF model to
447 them, repairing likely cosmic rays before detection.
449 Repair, detect, measure, and compute PSF twice, to ensure the PSF
450 model does not include contributions from cosmic rays.
452 Parameters
453 ----------
454 exposure : `lsst.afw.image.Exposure`
455 Exposure to detect and measure bright stars on.
457 Returns
458 -------
459 sources : `lsst.afw.table.SourceCatalog`
460 Catalog of detected bright sources.
461 background : `lsst.afw.math.BackgroundList`
462 Background that was fit to the exposure during detection.
463 cell_set : `lsst.afw.math.SpatialCellSet`
464 PSF candidates returned by the psf determiner.
465 """
466 self.log.info("First pass detection with Guassian PSF FWHM=%s", self.config.install_simple_psf.fwhm)
467 self.install_simple_psf.run(exposure=exposure)
469 background = self.psf_subtract_background.run(exposure=exposure).background
470 self.psf_repair.run(exposure=exposure, keepCRs=True)
472 table = afwTable.SourceTable.make(self.psf_schema)
473 # Re-estimate the background during this detection step, so that
474 # measurement uses the most accurate background-subtraction.
475 detections = self.psf_detection.run(table=table, exposure=exposure, background=background)
476 self.psf_source_measurement.run(detections.sources, exposure)
477 psf_result = self.psf_measure_psf.run(exposure=exposure, sources=detections.sources)
478 # Replace the initial PSF with something simpler for the second
479 # repair/detect/measure/measure_psf step: this can help it converge.
480 self.install_simple_psf.run(exposure=exposure)
482 self.log.info("Re-running repair, detection, and PSF measurement using new simple PSF.")
483 # TODO investigation: Should we only re-run repair here, to use the
484 # new PSF? Maybe we *do* need to re-run measurement with PsfFlux, to
485 # use the fitted PSF?
486 # TODO investigation: do we need a separate measurement task here
487 # for the post-psf_measure_psf step, since we only want to do PsfFlux
488 # and GaussianFlux *after* we have a PSF? Maybe that's not relevant
489 # once DM-39203 is merged?
490 self.psf_repair.run(exposure=exposure, keepCRs=True)
491 # Re-estimate the background during this detection step, so that
492 # measurement uses the most accurate background-subtraction.
493 detections = self.psf_detection.run(table=table, exposure=exposure, background=background)
494 self.psf_source_measurement.run(detections.sources, exposure)
495 psf_result = self.psf_measure_psf.run(exposure=exposure, sources=detections.sources)
497 # PSF is set on exposure; only return candidates for optional saving.
498 return detections.sources, background, psf_result.cellSet
500 def _measure_aperture_correction(self, exposure, bright_sources):
501 """Measure and set the ApCorrMap on the Exposure, using
502 previously-measured bright sources.
504 Parameters
505 ----------
506 exposure : `lsst.afw.image.Exposure`
507 Exposure to set the ApCorrMap on.
508 bright_sources : `lsst.afw.table.SourceCatalog`
509 Catalog of detected bright sources; modified to include columns
510 necessary for point source determination for the aperture correction
511 calculation.
512 """
513 result = self.measure_aperture_correction.run(exposure, bright_sources)
514 exposure.setApCorrMap(result.apCorrMap)
516 def _find_stars(self, exposure, background):
517 """Detect stars on an exposure that has a PSF model, and measure their
518 PSF, circular aperture, compensated gaussian fluxes.
520 Parameters
521 ----------
522 exposure : `lsst.afw.image.Exposure`
523 Exposure to set the ApCorrMap on.
524 background : `lsst.afw.math.BackgroundList`
525 Background that was fit to the exposure during detection;
526 modified in-place during subsequent detection.
528 Returns
529 -------
530 stars : `SourceCatalog`
531 Sources that are very likely to be stars, with a limited set of
532 measurements performed on them.
533 """
534 table = afwTable.SourceTable.make(self.initial_stars_schema.schema)
535 # Re-estimate the background during this detection step, so that
536 # measurement uses the most accurate background-subtraction.
537 detections = self.star_detection.run(table=table, exposure=exposure, background=background)
538 sources = detections.sources
539 # TODO investigation: Could this deblender throw away blends of non-PSF sources?
540 self.star_deblend.run(exposure=exposure, sources=sources)
541 # The deblender may not produce a contiguous catalog; ensure
542 # contiguity for subsequent tasks.
543 if not sources.isContiguous():
544 sources = sources.copy(deep=True)
546 # Measure everything, and use those results to select only stars.
547 self.star_measurement.run(sources, exposure)
548 self.star_apply_aperture_correction.run(sources, exposure.info.getApCorrMap())
549 self.star_catalog_calculation.run(sources)
550 self.star_set_primary_flags.run(sources)
552 result = self.star_selector.run(sources)
553 # The star selector may not produce a contiguous catalog.
554 if not result.sourceCat.isContiguous():
555 return result.sourceCat.copy(deep=True)
556 else:
557 return result.sourceCat
559 def _fit_astrometry(self, exposure, stars):
560 """Fit an astrometric model to the data and return the reference
561 matches used in the fit, and the fitted WCS.
563 Parameters
564 ----------
565 exposure : `lsst.afw.image.Exposure`
566 Exposure that is being fit, to get PSF and other metadata from.
567 Modified to add the fitted skyWcs.
568 stars : `SourceCatalog`
569 Good stars selected for use in calibration, with RA/Dec coordinates
570 computed from the pixel positions and fitted WCS.
572 Returns
573 -------
574 matches : `list` [`lsst.afw.table.ReferenceMatch`]
575 Reference/stars matches used in the fit.
576 """
577 result = self.astrometry.run(stars, exposure)
578 return result.matches, result.matchMeta
580 def _fit_photometry(self, exposure, stars):
581 """Fit a photometric model to the data and return the reference
582 matches used in the fit, and the fitted PhotoCalib.
584 Parameters
585 ----------
586 exposure : `lsst.afw.image.Exposure`
587 Exposure that is being fit, to get PSF and other metadata from.
588 Modified to be in nanojanksy units, with an assigned photoCalib
589 identically 1.
590 stars : `lsst.afw.table.SourceCatalog`
591 Good stars selected for use in calibration.
593 Returns
594 -------
595 calibrated_stars : `lsst.afw.table.SourceCatalog`
596 Star catalog with flux/magnitude columns computed from the fitted
597 photoCalib.
598 matches : `list` [`lsst.afw.table.ReferenceMatch`]
599 Reference/stars matches used in the fit.
600 photoCalib : `lsst.afw.image.PhotoCalib`
601 Photometric calibration that was fit to the star catalog.
602 """
603 result = self.photometry.run(exposure, stars)
604 calibrated_stars = result.photoCalib.calibrateCatalog(stars)
605 exposure.maskedImage = result.photoCalib.calibrateImage(exposure.maskedImage)
606 identity = afwImage.PhotoCalib(1.0,
607 result.photoCalib.getCalibrationErr(),
608 bbox=exposure.getBBox())
609 exposure.setPhotoCalib(identity)
611 return calibrated_stars, result.matches, result.matchMeta, result.photoCalib
613 def _summarize(self, exposure, stars, background):
614 """Compute summary statistics on the exposure and update in-place the
615 calibrations attached to it.
617 Parameters
618 ----------
619 exposure : `lsst.afw.image.Exposure`
620 Exposure that was calibrated, to get PSF and other metadata from.
621 Modified to contain the computed summary statistics.
622 stars : `SourceCatalog`
623 Good stars selected used in calibration.
624 background : `lsst.afw.math.BackgroundList`
625 Background that was fit to the exposure during detection of the
626 above stars.
627 """
628 # TODO investigation: because this takes the photoCalib from the
629 # exposure, photometric summary values may be "incorrect" (i.e. they
630 # will reflect the ==1 nJy calibration on the exposure, not the
631 # applied calibration). This needs to be checked.
632 summary = self.compute_summary_stats.run(exposure, stars, background)
633 exposure.info.setSummaryStats(summary)