164 def run(self, exposure, sources, background):
165 """Measure exposure statistics from the exposure, sources, and
170 exposure : `lsst.afw.image.ExposureF`
171 sources : `lsst.afw.table.SourceCatalog`
172 background : `lsst.afw.math.BackgroundList`
176 summary : `lsst.afw.image.ExposureSummary`
178 self.log.info(
"Measuring exposure statistics")
180 summary = afwImage.ExposureSummaryStats()
182 bbox = exposure.getBBox()
184 psf = exposure.getPsf()
185 self.
update_psf_stats(summary, psf, bbox, sources, image_mask=exposure.mask)
187 wcs = exposure.getWcs()
188 visitInfo = exposure.getInfo().getVisitInfo()
191 photoCalib = exposure.getPhotoCalib()
198 md = exposure.getMetadata()
199 if 'SFM_ASTROM_OFFSET_MEAN' in md:
200 summary.astromOffsetMean = md[
'SFM_ASTROM_OFFSET_MEAN']
201 summary.astromOffsetStd = md[
'SFM_ASTROM_OFFSET_STD']
205 def update_psf_stats(self, summary, psf, bbox, sources=None, image_mask=None, sources_is_astropy=False):
206 """Compute all summary-statistic fields that depend on the PSF model.
210 summary : `lsst.afw.image.ExposureSummaryStats`
211 Summary object to update in-place.
212 psf : `lsst.afw.detection.Psf` or `None`
213 Point spread function model. If `None`, all fields that depend on
214 the PSF will be reset (generally to NaN).
215 bbox : `lsst.geom.Box2I`
216 Bounding box of the image for which summary stats are being
218 sources : `lsst.afw.table.SourceCatalog` or `astropy.table.Table`
219 Catalog for quantities that are computed from source table columns.
220 If `None`, these quantities will be reset (generally to NaN).
221 The type of this table must correspond to the
222 ``sources_is_astropy`` argument.
223 image_mask : `lsst.afw.image.Mask`, optional
224 Mask image that may be used to compute distance-to-nearest-star
226 sources_is_astropy : `bool`, optional
227 Whether ``sources`` is an `astropy.table.Table` instance instead
228 of an `lsst.afw.table.Catalog` instance. Default is `False` (the
232 summary.psfSigma = nan
236 summary.psfArea = nan
238 summary.psfStarDeltaE1Median = nan
239 summary.psfStarDeltaE2Median = nan
240 summary.psfStarDeltaE1Scatter = nan
241 summary.psfStarDeltaE2Scatter = nan
242 summary.psfStarDeltaSizeMedian = nan
243 summary.psfStarDeltaSizeScatter = nan
244 summary.psfStarScaledDeltaSizeScatter = nan
245 summary.maxDistToNearestPsf = nan
246 summary.psfTraceRadiusDelta = nan
250 shape = psf.computeShape(bbox.getCenter())
251 summary.psfSigma = shape.getDeterminantRadius()
252 summary.psfIxx = shape.getIxx()
253 summary.psfIyy = shape.getIyy()
254 summary.psfIxy = shape.getIxy()
255 im = psf.computeKernelImage(bbox.getCenter())
260 summary.psfArea = float(np.sum(im.array)/np.sum(im.array**2.))
262 if image_mask
is not None:
266 sampling=self.config.psfGridSampling,
267 bad_mask_bits=self.config.psfBadMaskPlanes
269 summary.psfTraceRadiusDelta = float(psfTraceRadiusDelta)
278 nPsfStar = sources[self.config.starSelection].sum()
279 summary.nPsfStar = int(nPsfStar)
281 psf_mask = self.starSelector.run(sources).selected
282 nPsfStarsUsedInStats = psf_mask.sum()
284 if nPsfStarsUsedInStats == 0:
289 if sources_is_astropy:
290 psf_cat = sources[psf_mask]
292 psf_cat = sources[psf_mask].copy(deep=
True)
294 starXX = psf_cat[self.config.starShape +
'_xx']
295 starYY = psf_cat[self.config.starShape +
'_yy']
296 starXY = psf_cat[self.config.starShape +
'_xy']
297 psfXX = psf_cat[self.config.psfShape +
'_xx']
298 psfYY = psf_cat[self.config.psfShape +
'_yy']
299 psfXY = psf_cat[self.config.psfShape +
'_xy']
302 starSize = np.sqrt(starXX/2. + starYY/2.)
304 starE1 = (starXX - starYY)/(starXX + starYY)
305 starE2 = 2*starXY/(starXX + starYY)
306 starSizeMedian = np.median(starSize)
309 psfSize = np.sqrt(psfXX/2. + psfYY/2.)
310 psfE1 = (psfXX - psfYY)/(psfXX + psfYY)
311 psfE2 = 2*psfXY/(psfXX + psfYY)
313 psfStarDeltaE1Median = np.median(starE1 - psfE1)
314 psfStarDeltaE1Scatter = sigmaMad(starE1 - psfE1, scale=
'normal')
315 psfStarDeltaE2Median = np.median(starE2 - psfE2)
316 psfStarDeltaE2Scatter = sigmaMad(starE2 - psfE2, scale=
'normal')
318 psfStarDeltaSizeMedian = np.median(starSize - psfSize)
319 psfStarDeltaSizeScatter = sigmaMad(starSize - psfSize, scale=
'normal')
320 psfStarScaledDeltaSizeScatter = psfStarDeltaSizeScatter/starSizeMedian
322 summary.psfStarDeltaE1Median = float(psfStarDeltaE1Median)
323 summary.psfStarDeltaE2Median = float(psfStarDeltaE2Median)
324 summary.psfStarDeltaE1Scatter = float(psfStarDeltaE1Scatter)
325 summary.psfStarDeltaE2Scatter = float(psfStarDeltaE2Scatter)
326 summary.psfStarDeltaSizeMedian = float(psfStarDeltaSizeMedian)
327 summary.psfStarDeltaSizeScatter = float(psfStarDeltaSizeScatter)
328 summary.psfStarScaledDeltaSizeScatter = float(psfStarScaledDeltaSizeScatter)
330 if image_mask
is not None:
334 sampling=self.config.psfSampling,
335 bad_mask_bits=self.config.psfBadMaskPlanes
337 summary.maxDistToNearestPsf = float(maxDistToNearestPsf)
340 """Compute all summary-statistic fields that depend on the WCS model.
344 summary : `lsst.afw.image.ExposureSummaryStats`
345 Summary object to update in-place.
346 wcs : `lsst.afw.geom.SkyWcs` or `None`
347 Astrometric calibration model. If `None`, all fields that depend
348 on the WCS will be reset (generally to NaN).
349 bbox : `lsst.geom.Box2I`
350 Bounding box of the image for which summary stats are being
352 visitInfo : `lsst.afw.image.VisitInfo`
353 Observation information used in together with ``wcs`` to compute
357 summary.raCorners = [nan]*4
358 summary.decCorners = [nan]*4
361 summary.zenithDistance = nan
366 sph_pts = wcs.pixelToSky(
geom.Box2D(bbox).getCorners())
367 summary.raCorners = [float(sph.getRa().asDegrees())
for sph
in sph_pts]
368 summary.decCorners = [float(sph.getDec().asDegrees())
for sph
in sph_pts]
370 sph_pt = wcs.pixelToSky(bbox.getCenter())
371 summary.ra = sph_pt.getRa().asDegrees()
372 summary.dec = sph_pt.getDec().asDegrees()
374 date = visitInfo.getDate()
381 observatory = visitInfo.getObservatory()
382 loc = EarthLocation(lat=observatory.getLatitude().asDegrees()*units.deg,
383 lon=observatory.getLongitude().asDegrees()*units.deg,
384 height=observatory.getElevation()*units.m)
385 obstime = Time(visitInfo.getDate().get(system=DateTime.MJD),
386 location=loc, format=
'mjd')
388 summary.ra*units.degree,
389 summary.dec*units.degree,
393 with warnings.catch_warnings():
394 warnings.simplefilter(
'ignore')
395 altaz = coord.transform_to(AltAz)
397 summary.zenithDistance = float(90.0 - altaz.alt.degree)
443 """Compute summary-statistic fields that depend on the masked image
448 summary : `lsst.afw.image.ExposureSummaryStats`
449 Summary object to update in-place.
450 masked_image : `lsst.afw.image.MaskedImage` or `None`
451 Masked image. If `None`, all fields that depend
452 on the masked image will be reset (generally to NaN).
455 if masked_image
is None:
456 summary.skyNoise = nan
457 summary.meanVar = nan
459 statsCtrl = afwMath.StatisticsControl()
460 statsCtrl.setNumSigmaClip(self.config.sigmaClip)
461 statsCtrl.setNumIter(self.config.clipIter)
462 statsCtrl.setAndMask(afwImage.Mask.getPlaneBitMask(self.config.badMaskPlanes))
463 statsCtrl.setNanSafe(
True)
465 statObj = afwMath.makeStatistics(masked_image, afwMath.STDEVCLIP, statsCtrl)
466 skyNoise, _ = statObj.getResult(afwMath.STDEVCLIP)
467 summary.skyNoise = skyNoise
469 statObj = afwMath.makeStatistics(masked_image.variance, masked_image.mask, afwMath.MEANCLIP,
471 meanVar, _ = statObj.getResult(afwMath.MEANCLIP)
472 summary.meanVar = meanVar