133 def run(self, exposure, sources, background):
134 """Measure exposure statistics from the exposure, sources, and
139 exposure : `lsst.afw.image.ExposureF`
140 sources : `lsst.afw.table.SourceCatalog`
141 background : `lsst.afw.math.BackgroundList`
145 summary : `lsst.afw.image.ExposureSummary`
147 self.log.info(
"Measuring exposure statistics")
149 summary = afwImage.ExposureSummaryStats()
151 bbox = exposure.getBBox()
153 psf = exposure.getPsf()
154 self.
update_psf_stats(summary, psf, bbox, sources, image_mask=exposure.mask)
156 wcs = exposure.getWcs()
157 visitInfo = exposure.getInfo().getVisitInfo()
160 photoCalib = exposure.getPhotoCalib()
167 md = exposure.getMetadata()
168 if 'SFM_ASTROM_OFFSET_MEAN' in md:
169 summary.astromOffsetMean = md[
'SFM_ASTROM_OFFSET_MEAN']
170 summary.astromOffsetStd = md[
'SFM_ASTROM_OFFSET_STD']
174 def update_psf_stats(self, summary, psf, bbox, sources=None, image_mask=None, sources_is_astropy=False):
175 """Compute all summary-statistic fields that depend on the PSF model.
179 summary : `lsst.afw.image.ExposureSummaryStats`
180 Summary object to update in-place.
181 psf : `lsst.afw.detection.Psf` or `None`
182 Point spread function model. If `None`, all fields that depend on
183 the PSF will be reset (generally to NaN).
184 bbox : `lsst.geom.Box2I`
185 Bounding box of the image for which summary stats are being
187 sources : `lsst.afw.table.SourceCatalog` or `astropy.table.Table`
188 Catalog for quantities that are computed from source table columns.
189 If `None`, these quantities will be reset (generally to NaN).
190 The type of this table must correspond to the
191 ``sources_is_astropy`` argument.
192 image_mask : `lsst.afw.image.Mask`, optional
193 Mask image that may be used to compute distance-to-nearest-star
195 sources_is_astropy : `bool`, optional
196 Whether ``sources`` is an `astropy.table.Table` instance instead
197 of an `lsst.afw.table.Catalog` instance. Default is `False` (the
201 summary.psfSigma = nan
205 summary.psfArea = nan
207 summary.psfStarDeltaE1Median = nan
208 summary.psfStarDeltaE2Median = nan
209 summary.psfStarDeltaE1Scatter = nan
210 summary.psfStarDeltaE2Scatter = nan
211 summary.psfStarDeltaSizeMedian = nan
212 summary.psfStarDeltaSizeScatter = nan
213 summary.psfStarScaledDeltaSizeScatter = nan
214 summary.maxDistToNearestPsf = nan
215 summary.psfTraceRadiusDelta = nan
219 shape = psf.computeShape(bbox.getCenter())
220 summary.psfSigma = shape.getDeterminantRadius()
221 summary.psfIxx = shape.getIxx()
222 summary.psfIyy = shape.getIyy()
223 summary.psfIxy = shape.getIxy()
224 im = psf.computeKernelImage(bbox.getCenter())
229 summary.psfArea = float(np.sum(im.array)/np.sum(im.array**2.))
231 if image_mask
is not None:
235 sampling=self.config.psfGridSampling,
236 bad_mask_bits=self.config.psfBadMaskPlanes
238 summary.psfTraceRadiusDelta = float(psfTraceRadiusDelta)
246 psf_mask = sources[self.config.starSelection] & (~sources[self.config.starShape +
'_flag'])
247 nPsfStar = psf_mask.sum()
254 if sources_is_astropy:
255 psf_cat = sources[psf_mask]
257 psf_cat = sources[psf_mask].copy(deep=
True)
259 starXX = psf_cat[self.config.starShape +
'_xx']
260 starYY = psf_cat[self.config.starShape +
'_yy']
261 starXY = psf_cat[self.config.starShape +
'_xy']
262 psfXX = psf_cat[self.config.psfShape +
'_xx']
263 psfYY = psf_cat[self.config.psfShape +
'_yy']
264 psfXY = psf_cat[self.config.psfShape +
'_xy']
266 starSize = (starXX*starYY - starXY**2.)**0.25
267 starE1 = (starXX - starYY)/(starXX + starYY)
268 starE2 = 2*starXY/(starXX + starYY)
269 starSizeMedian = np.median(starSize)
271 psfSize = (psfXX*psfYY - psfXY**2)**0.25
272 psfE1 = (psfXX - psfYY)/(psfXX + psfYY)
273 psfE2 = 2*psfXY/(psfXX + psfYY)
275 psfStarDeltaE1Median = np.median(starE1 - psfE1)
276 psfStarDeltaE1Scatter = sigmaMad(starE1 - psfE1, scale=
'normal')
277 psfStarDeltaE2Median = np.median(starE2 - psfE2)
278 psfStarDeltaE2Scatter = sigmaMad(starE2 - psfE2, scale=
'normal')
280 psfStarDeltaSizeMedian = np.median(starSize - psfSize)
281 psfStarDeltaSizeScatter = sigmaMad(starSize - psfSize, scale=
'normal')
282 psfStarScaledDeltaSizeScatter = psfStarDeltaSizeScatter/starSizeMedian**2.
284 summary.nPsfStar = int(nPsfStar)
285 summary.psfStarDeltaE1Median = float(psfStarDeltaE1Median)
286 summary.psfStarDeltaE2Median = float(psfStarDeltaE2Median)
287 summary.psfStarDeltaE1Scatter = float(psfStarDeltaE1Scatter)
288 summary.psfStarDeltaE2Scatter = float(psfStarDeltaE2Scatter)
289 summary.psfStarDeltaSizeMedian = float(psfStarDeltaSizeMedian)
290 summary.psfStarDeltaSizeScatter = float(psfStarDeltaSizeScatter)
291 summary.psfStarScaledDeltaSizeScatter = float(psfStarScaledDeltaSizeScatter)
293 if image_mask
is not None:
297 sampling=self.config.psfSampling,
298 bad_mask_bits=self.config.psfBadMaskPlanes
300 summary.maxDistToNearestPsf = float(maxDistToNearestPsf)
303 """Compute all summary-statistic fields that depend on the WCS model.
307 summary : `lsst.afw.image.ExposureSummaryStats`
308 Summary object to update in-place.
309 wcs : `lsst.afw.geom.SkyWcs` or `None`
310 Astrometric calibration model. If `None`, all fields that depend
311 on the WCS will be reset (generally to NaN).
312 bbox : `lsst.geom.Box2I`
313 Bounding box of the image for which summary stats are being
315 visitInfo : `lsst.afw.image.VisitInfo`
316 Observation information used in together with ``wcs`` to compute
320 summary.raCorners = [nan]*4
321 summary.decCorners = [nan]*4
324 summary.zenithDistance = nan
329 sph_pts = wcs.pixelToSky(
geom.Box2D(bbox).getCorners())
330 summary.raCorners = [float(sph.getRa().asDegrees())
for sph
in sph_pts]
331 summary.decCorners = [float(sph.getDec().asDegrees())
for sph
in sph_pts]
333 sph_pt = wcs.pixelToSky(bbox.getCenter())
334 summary.ra = sph_pt.getRa().asDegrees()
335 summary.dec = sph_pt.getDec().asDegrees()
337 date = visitInfo.getDate()
344 observatory = visitInfo.getObservatory()
345 loc = EarthLocation(lat=observatory.getLatitude().asDegrees()*units.deg,
346 lon=observatory.getLongitude().asDegrees()*units.deg,
347 height=observatory.getElevation()*units.m)
348 obstime = Time(visitInfo.getDate().get(system=DateTime.MJD),
349 location=loc, format=
'mjd')
351 summary.ra*units.degree,
352 summary.dec*units.degree,
356 with warnings.catch_warnings():
357 warnings.simplefilter(
'ignore')
358 altaz = coord.transform_to(AltAz)
360 summary.zenithDistance = float(90.0 - altaz.alt.degree)
406 """Compute summary-statistic fields that depend on the masked image
411 summary : `lsst.afw.image.ExposureSummaryStats`
412 Summary object to update in-place.
413 masked_image : `lsst.afw.image.MaskedImage` or `None`
414 Masked image. If `None`, all fields that depend
415 on the masked image will be reset (generally to NaN).
418 if masked_image
is None:
419 summary.skyNoise = nan
420 summary.meanVar = nan
422 statsCtrl = afwMath.StatisticsControl()
423 statsCtrl.setNumSigmaClip(self.config.sigmaClip)
424 statsCtrl.setNumIter(self.config.clipIter)
425 statsCtrl.setAndMask(afwImage.Mask.getPlaneBitMask(self.config.badMaskPlanes))
426 statsCtrl.setNanSafe(
True)
428 statObj = afwMath.makeStatistics(masked_image, afwMath.STDEVCLIP, statsCtrl)
429 skyNoise, _ = statObj.getResult(afwMath.STDEVCLIP)
430 summary.skyNoise = skyNoise
432 statObj = afwMath.makeStatistics(masked_image.variance, masked_image.mask, afwMath.MEANCLIP,
434 meanVar, _ = statObj.getResult(afwMath.MEANCLIP)
435 summary.meanVar = meanVar