46 """Calculate the PSF matching kernel peak offset from the nominal
51 kernel : `~lsst.afw.math.LinearCombinationKernel`
52 The PSF matching kernel to evaluate.
54 The x position on the detector to evaluate the kernel
56 The y position on the detector to evaluate the kernel
57 image : `~lsst.afw.image.ImageD`
58 The image to use as base for computing kernel pixel values
63 The sum of the kernel on the desired location
65 The displacement of the kernel averaged peak, with respect to the
66 center of the extraction of the kernel
68 The displacement of the kernel averaged peak, with respect to the
69 center of the extraction of the kernel
71 The position angle in detector coordinates of the displacement
73 The displacement module of the kernel centroid in pixel units
77 image = afwImage.ImageD(kernel.getDimensions())
80 hsize = kernel.getWidth()//2
81 kernel_sum = kernel.computeImage(image, doNormalize=
False, x=x, y=y)
91 dx = np.dot(vx, xx) - hsize
95 dy = np.dot(vy, yy) - hsize
98 pos_angle = np.arctan2(dy, dx)
99 length = np.sqrt(dx**2 + dy**2)
101 return kernel_sum, dx, dy, pos_angle, length
105 """Directly calculate the horizontal and vertical widths
106 of a PSF at half its maximum value.
110 psf : `~lsst.afw.detection.Psf`
111 Point spread function (PSF) to evaluate.
112 average : `bool`, optional
113 Set to return the average width over Y and X axes.
114 position : `~lsst.geom.Point2D`, optional
115 The position at which to evaluate the PSF. If `None`, then the
116 average position is used.
120 psfSize : `float` | `tuple` [`float`]
121 The FWHM of the PSF computed at its average position.
122 Returns the widths along the Y and X axes,
123 or the average of the two if `average` is set.
130 position = psf.getAveragePosition()
131 shape = psf.computeShape(position)
132 sigmaToFwhm = 2*np.log(2*np.sqrt(2))
135 return sigmaToFwhm*shape.getTraceRadius()
137 return [sigmaToFwhm*np.sqrt(shape.getIxx()), sigmaToFwhm*np.sqrt(shape.getIyy())]
141 fwhmExposureBuffer: float, fwhmExposureGrid: int) -> float:
142 """Get the mean PSF FWHM by evaluating it on a grid within an exposure.
146 exposure : `~lsst.afw.image.Exposure`
147 The exposure for which the mean FWHM of the PSF is to be computed.
148 The exposure must contain a `psf` attribute.
149 fwhmExposureBuffer : `float`
150 Fractional buffer margin to be left out of all sides of the image
151 during the construction of the grid to compute mean PSF FWHM in an
153 fwhmExposureGrid : `int`
154 Grid size to compute the mean FWHM in an exposure.
159 The mean PSF FWHM on the exposure.
164 Raised if the PSF cannot be computed at any of the grid points.
174 bbox = exposure.getBBox()
175 xmax, ymax = bbox.getMax()
176 xmin, ymin = bbox.getMin()
178 xbuffer = fwhmExposureBuffer*(xmax-xmin)
179 ybuffer = fwhmExposureBuffer*(ymax-ymin)
182 for (x, y)
in itertools.product(np.linspace(xmin+xbuffer, xmax-xbuffer, fwhmExposureGrid),
183 np.linspace(ymin+ybuffer, ymax-ybuffer, fwhmExposureGrid)
187 fwhm =
getPsfFwhm(psf, average=
True, position=pos)
188 except (InvalidParameterError, RangeError):
189 _LOG.debug(
"Unable to compute PSF FWHM at position (%f, %f).", x, y)
195 raise ValueError(
"Unable to compute PSF FWHM at any position on the exposure.")
197 return np.nanmean(width)
201 psfExposureBuffer: float, psfExposureGrid: int) -> afwImage.ImageD:
202 """Get the average PSF by evaluating it on a grid within an exposure.
206 exposure : `~lsst.afw.image.Exposure`
207 The exposure for which the average PSF is to be computed.
208 The exposure must contain a `psf` attribute.
209 psfExposureBuffer : `float`
210 Fractional buffer margin to be left out of all sides of the image
211 during the construction of the grid to compute average PSF in an
213 psfExposureGrid : `int`
214 Grid size to compute the average PSF in an exposure.
218 psfImage : `~lsst.afw.image.Image`
219 The average PSF across the exposure.
224 Raised if the PSF cannot be computed at any of the grid points.
228 `evaluateMeanPsfFwhm`
233 bbox = exposure.getBBox()
234 xmax, ymax = bbox.getMax()
235 xmin, ymin = bbox.getMin()
237 xbuffer = psfExposureBuffer*(xmax-xmin)
238 ybuffer = psfExposureBuffer*(ymax-ymin)
242 for (x, y)
in itertools.product(np.linspace(xmin+xbuffer, xmax-xbuffer, psfExposureGrid),
243 np.linspace(ymin+ybuffer, ymax-ybuffer, psfExposureGrid)
247 singleImage = psf.computeKernelImage(pos)
248 except InvalidParameterError:
249 _LOG.debug(
"Unable to compute PSF image at position (%f, %f).", x, y)
253 psfArray = singleImage.array
255 psfArray += singleImage.array
259 raise ValueError(
"Unable to compute PSF image at any position on the exposure.")
261 psfImage = afwImage.ImageD(psfArray/nImg)
266 """Calculate a robust mean of the variance plane of an exposure.
270 image : `lsst.afw.image.Image`
271 Image or variance plane of an exposure to evaluate.
272 mask : `lsst.afw.image.Mask`
273 Mask plane to use for excluding pixels.
274 statsCtrl : `lsst.afw.math.StatisticsControl`
275 Statistics control object for configuring the calculation.
276 statistic : `lsst.afw.math.Property`, optional
277 The type of statistic to compute. Typical values are
278 ``afwMath.MEANCLIP`` or ``afwMath.STDEVCLIP``.
283 The result of the statistic calculated from the unflagged pixels.
286 return statObj.getValue(statistic)
342 r"""Compute quality metrics (saved to the task metadata) on the
343 difference image, at the locations of detected stars on the science
344 image. This restricts the metric to locations that should be
349 science : `lsst.afw.image.ExposureF`
350 Science exposure that was subtracted.
351 difference : `lsst.afw.image.ExposureF`
352 Result of subtracting template and science.
353 stars : `lsst.afw.table.SourceCatalog`
354 Good calibration sources detected on science image; these
355 footprints are what the metrics are computed on.
359 metrics : `lsst.pipe.base.Struct`
361 ``differenceFootprintRatioMean`` : `float`
362 Mean of the ratio of the absolute value of the difference image
363 (with the mean absolute value of the sky regions on the difference
364 image removed) to the science image, computed in the footprints
365 of stars detected on the science image (the sums below are of the
366 pixels in each star or sky footprint):
367 :math:`\mathrm{mean}_{footprints}((\sum |difference| -
368 \mathrm{mean}(\sum |difference_{sky}|)) / \sum science)`
369 ``differenceFootprintRatioStdev`` : `float`
370 Standard Deviation across footprints of the above ratio.
371 ``differenceFootprintSkyRatioMean`` : `float`
372 Mean of the ratio of the absolute value of sky source regions on
373 the difference image to the science image (the sum below is of the
374 pixels in each sky source footprint):
375 :math:`\mathrm{mean}_{footprints}(\sum |difference_{sky}| / \sum science_{sky})`
376 ``differenceFootprintSkyRatioStdev`` : `float`
377 Standard Deivation across footprints of the above sky ratio.
379 def footprint_mean(sources, sky=0):
380 """Compute ratio of the absolute value of the diffim to the science
381 image, within each source footprint, subtracting the sky from the
382 diffim values if provided.
385 science_footprints = np.zeros(n)
386 difference_footprints = np.zeros(n)
388 for i, record
in enumerate(sources):
389 footprint = record.getFootprint()
390 heavy = afwDetection.makeHeavyFootprint(footprint, science.maskedImage)
391 heavy_diff = afwDetection.makeHeavyFootprint(footprint, difference.maskedImage)
392 science_footprints[i] = abs(heavy.getImageArray()).mean()
393 difference_footprints[i] = abs(heavy_diff.getImageArray()).mean()
394 ratio[i] = abs((difference_footprints[i] - sky)/science_footprints[i])
395 return science_footprints, difference_footprints, ratio
397 if "sky_source" in stars.schema:
398 sky = stars[
"sky_source"]
399 selectStars = stars[~sky]
400 if sky_sources
is None:
401 sky_sources = stars[sky]
405 if sky_sources
is not None and len(sky_sources) > 0:
406 sky_science, sky_difference, sky_ratio = footprint_mean(sky_sources)
407 sky_mean = sky_ratio.mean()
408 sky_std = sky_ratio.std()
409 sky_difference = sky_difference.mean()
413 sky_difference = np.nanmedian(np.abs(difference.image.array))
414 science_footprints, difference_footprints, ratio = footprint_mean(selectStars, sky_difference)
415 return lsst.pipe.base.Struct(differenceFootprintRatioMean=ratio.mean(),
416 differenceFootprintRatioStdev=ratio.std(),
417 differenceFootprintSkyRatioMean=sky_mean,
418 differenceFootprintSkyRatioStdev=sky_std,
423 """Populate a cache of predicted satellite positions in the sattle service.
427 visit_info: `lsst.afw.table.ExposureRecord.visitInfo`
428 Visit info for the science exposure being processed.
430 Set to True if observations are older than the current day.
435 Raised if sattle call does not return success.
438 visit_mjd = visit_info.getDate().toAstropy().mjd
440 exposure_time_days = visit_info.getExposureTime() / 86400.0
441 exposure_end_mjd = visit_mjd + exposure_time_days / 2.0
442 exposure_start_mjd = visit_mjd - exposure_time_days / 2.0
444 boresight_ra = visit_info.boresightRaDec.getRa().asDegrees()
445 boresight_dec = visit_info.boresightRaDec.getDec().asDegrees()
448 f
'{os.getenv("SATTLE_URI_BASE")}/visit_cache',
449 json={
"visit_id": visit_info.getId(),
450 "exposure_start_mjd": exposure_start_mjd,
451 "exposure_end_mjd": exposure_end_mjd,
452 "boresight_ra": boresight_ra,
453 "boresight_dec": boresight_dec,
454 "historical": historical})
460 """Exclude sources that have masked pixels in their footprints.
464 mask : `lsst.afw.image.Mask`
465 The image mask plane to use to reject sources
466 based on the location of their centroid on the ccd.
467 sources : `lsst.afw.table.SourceCatalog`
468 The source catalog to evaluate.
469 excludeMaskPlanes : `list` of `str`
470 List of the names of the mask planes to exclude.
474 good : `numpy.ndarray` of `bool`
475 Array indicating whether each source in the catalog should be
476 kept (True) or rejected (False) based on the value of the
477 mask plane at its location.
479 setExcludeMaskPlanes = [
480 maskPlane
for maskPlane
in excludeMaskPlanes
if maskPlane
in mask.getMaskPlaneDict()
483 excludePixelMask = mask.getPlaneBitMask(setExcludeMaskPlanes)
485 good = np.ones(len(sources), dtype=bool)
486 for i, source
in enumerate(sources):
487 bbox = source.getFootprint().getBBox()
488 if not mask.getBBox().contains(bbox):
493 if (mask.subset(bbox).array & excludePixelMask).any():
Statistics makeStatistics(lsst::afw::image::Image< Pixel > const &img, lsst::afw::image::Mask< image::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl=StatisticsControl())