49 """Calculate the PSF matching kernel peak offset from the nominal
54 kernel : `~lsst.afw.math.LinearCombinationKernel`
55 The PSF matching kernel to evaluate.
57 The x position on the detector to evaluate the kernel
59 The y position on the detector to evaluate the kernel
60 image : `~lsst.afw.image.ImageD`
61 The image to use as base for computing kernel pixel values
66 The sum of the kernel on the desired location
68 The displacement of the kernel averaged peak, with respect to the
69 center of the extraction of the kernel
71 The displacement of the kernel averaged peak, with respect to the
72 center of the extraction of the kernel
74 The position angle in detector coordinates of the displacement
76 The displacement module of the kernel centroid in pixel units
80 image = afwImage.ImageD(kernel.getDimensions())
83 hsize = kernel.getWidth()//2
84 kernel_sum = kernel.computeImage(image, doNormalize=
False, x=x, y=y)
94 dx = np.dot(vx, xx) - hsize
98 dy = np.dot(vy, yy) - hsize
101 pos_angle = np.arctan2(dy, dx)
102 length = np.sqrt(dx**2 + dy**2)
104 return kernel_sum, dx, dy, pos_angle, length
108 """Directly calculate the horizontal and vertical widths
109 of a PSF at half its maximum value.
113 psf : `~lsst.afw.detection.Psf`
114 Point spread function (PSF) to evaluate.
115 average : `bool`, optional
116 Set to return the average width over Y and X axes.
117 position : `~lsst.geom.Point2D`, optional
118 The position at which to evaluate the PSF. If `None`, then the
119 average position is used.
123 psfSize : `float` | `tuple` [`float`]
124 The FWHM of the PSF computed at its average position.
125 Returns the widths along the Y and X axes,
126 or the average of the two if `average` is set.
133 position = psf.getAveragePosition()
134 shape = psf.computeShape(position)
137 return gaussian_sigma_to_fwhm*shape.getTraceRadius()
139 return [gaussian_sigma_to_fwhm*np.sqrt(shape.getIxx()),
140 gaussian_sigma_to_fwhm*np.sqrt(shape.getIyy())]
144 fwhmExposureBuffer: float, fwhmExposureGrid: int) -> float:
145 """Get the mean PSF FWHM by evaluating it on a grid within an exposure.
149 exposure : `~lsst.afw.image.Exposure`
150 The exposure for which the mean FWHM of the PSF is to be computed.
151 The exposure must contain a `psf` attribute.
152 fwhmExposureBuffer : `float`
153 Fractional buffer margin to be left out of all sides of the image
154 during the construction of the grid to compute mean PSF FWHM in an
156 fwhmExposureGrid : `int`
157 Grid size to compute the mean FWHM in an exposure.
162 The mean PSF FWHM on the exposure.
167 Raised if the PSF cannot be computed at any of the grid points.
177 bbox = exposure.getBBox()
178 xmax, ymax = bbox.getMax()
179 xmin, ymin = bbox.getMin()
181 xbuffer = fwhmExposureBuffer*(xmax-xmin)
182 ybuffer = fwhmExposureBuffer*(ymax-ymin)
185 for (x, y)
in itertools.product(np.linspace(xmin+xbuffer, xmax-xbuffer, fwhmExposureGrid),
186 np.linspace(ymin+ybuffer, ymax-ybuffer, fwhmExposureGrid)
190 fwhm =
getPsfFwhm(psf, average=
True, position=pos)
191 except (InvalidParameterError, RangeError):
192 _LOG.debug(
"Unable to compute PSF FWHM at position (%f, %f).", x, y)
198 raise ValueError(
"Unable to compute PSF FWHM at any position on the exposure.")
200 return np.nanmean(width)
204 psfExposureBuffer: float, psfExposureGrid: int) -> afwImage.ImageD:
205 """Get the average PSF by evaluating it on a grid within an exposure.
209 exposure : `~lsst.afw.image.Exposure`
210 The exposure for which the average PSF is to be computed.
211 The exposure must contain a `psf` attribute.
212 psfExposureBuffer : `float`
213 Fractional buffer margin to be left out of all sides of the image
214 during the construction of the grid to compute average PSF in an
216 psfExposureGrid : `int`
217 Grid size to compute the average PSF in an exposure.
221 psfImage : `~lsst.afw.image.Image`
222 The average PSF across the exposure.
227 Raised if the PSF cannot be computed at any of the grid points.
231 `evaluateMeanPsfFwhm`
236 bbox = exposure.getBBox()
237 xmax, ymax = bbox.getMax()
238 xmin, ymin = bbox.getMin()
240 xbuffer = psfExposureBuffer*(xmax-xmin)
241 ybuffer = psfExposureBuffer*(ymax-ymin)
245 for (x, y)
in itertools.product(np.linspace(xmin+xbuffer, xmax-xbuffer, psfExposureGrid),
246 np.linspace(ymin+ybuffer, ymax-ybuffer, psfExposureGrid)
250 singleImage = psf.computeKernelImage(pos)
251 except InvalidParameterError:
252 _LOG.debug(
"Unable to compute PSF image at position (%f, %f).", x, y)
256 psfArray = singleImage.array
258 psfArray += singleImage.array
262 raise ValueError(
"Unable to compute PSF image at any position on the exposure.")
264 psfImage = afwImage.ImageD(psfArray/nImg)
269 """Calculate a robust mean of the variance plane of an exposure.
273 image : `lsst.afw.image.Image`
274 Image or variance plane of an exposure to evaluate.
275 mask : `lsst.afw.image.Mask`
276 Mask plane to use for excluding pixels.
277 statsCtrl : `lsst.afw.math.StatisticsControl`
278 Statistics control object for configuring the calculation.
279 statistic : `lsst.afw.math.Property`, optional
280 The type of statistic to compute. Typical values are
281 ``afwMath.MEANCLIP`` or ``afwMath.STDEVCLIP``.
286 The result of the statistic calculated from the unflagged pixels.
289 return statObj.getValue(statistic)
345 r"""Compute quality metrics (saved to the task metadata) on the
346 difference image, at the locations of detected stars on the science
347 image. This restricts the metric to locations that should be
352 science : `lsst.afw.image.ExposureF`
353 Science exposure that was subtracted.
354 difference : `lsst.afw.image.ExposureF`
355 Result of subtracting template and science.
356 stars : `lsst.afw.table.SourceCatalog`
357 Good calibration sources detected on science image; these
358 footprints are what the metrics are computed on.
362 metrics : `lsst.pipe.base.Struct`
364 ``differenceFootprintRatioMean`` : `float`
365 Mean of the ratio of the absolute value of the difference image
366 (with the mean absolute value of the sky regions on the difference
367 image removed) to the science image, computed in the footprints
368 of stars detected on the science image (the sums below are of the
369 pixels in each star or sky footprint):
370 :math:`\mathrm{mean}_{footprints}((\sum |difference| -
371 \mathrm{mean}(\sum |difference_{sky}|)) / \sum science)`
372 ``differenceFootprintRatioStdev`` : `float`
373 Standard Deviation across footprints of the above ratio.
374 ``differenceFootprintSkyRatioMean`` : `float`
375 Mean of the ratio of the absolute value of sky source regions on
376 the difference image to the science image (the sum below is of the
377 pixels in each sky source footprint):
378 :math:`\mathrm{mean}_{footprints}(\sum |difference_{sky}| / \sum science_{sky})`
379 ``differenceFootprintSkyRatioStdev`` : `float`
380 Standard Deivation across footprints of the above sky ratio.
382 def footprint_mean(sources, sky=0):
383 """Compute ratio of the absolute value of the diffim to the science
384 image, within each source footprint, subtracting the sky from the
385 diffim values if provided.
388 science_footprints = np.zeros(n)
389 difference_footprints = np.zeros(n)
391 for i, record
in enumerate(sources):
392 footprint = record.getFootprint()
393 heavy = afwDetection.makeHeavyFootprint(footprint, science.maskedImage)
394 heavy_diff = afwDetection.makeHeavyFootprint(footprint, difference.maskedImage)
395 science_footprints[i] = abs(heavy.getImageArray()).mean()
396 difference_footprints[i] = abs(heavy_diff.getImageArray()).mean()
397 ratio[i] = abs((difference_footprints[i] - sky)/science_footprints[i])
398 return science_footprints, difference_footprints, ratio
400 if "sky_source" in stars.schema:
401 sky = stars[
"sky_source"]
402 selectStars = stars[~sky]
403 if sky_sources
is None:
404 sky_sources = stars[sky]
408 if sky_sources
is not None and len(sky_sources) > 0:
409 sky_science, sky_difference, sky_ratio = footprint_mean(sky_sources)
410 sky_mean = sky_ratio.mean()
411 sky_std = sky_ratio.std()
412 sky_difference = sky_difference.mean()
416 sky_difference = np.nanmedian(np.abs(difference.image.array))
417 science_footprints, difference_footprints, ratio = footprint_mean(selectStars, sky_difference)
418 return lsst.pipe.base.Struct(differenceFootprintRatioMean=ratio.mean(),
419 differenceFootprintRatioStdev=ratio.std(),
420 differenceFootprintSkyRatioMean=sky_mean,
421 differenceFootprintSkyRatioStdev=sky_std,
426 """Populate a cache of predicted satellite positions in the sattle service.
430 visit_info: `lsst.afw.table.ExposureRecord.visitInfo`
431 Visit info for the science exposure being processed.
433 Set to True if observations are older than the current day.
438 Raised if sattle call does not return success.
441 visit_mjd = visit_info.getDate().toAstropy().mjd
443 exposure_time_days = visit_info.getExposureTime() / 86400.0
444 exposure_end_mjd = visit_mjd + exposure_time_days / 2.0
445 exposure_start_mjd = visit_mjd - exposure_time_days / 2.0
447 boresight_ra = visit_info.boresightRaDec.getRa().asDegrees()
448 boresight_dec = visit_info.boresightRaDec.getDec().asDegrees()
451 f
'{os.getenv("SATTLE_URI_BASE")}/visit_cache',
452 json={
"visit_id": visit_info.getId(),
453 "exposure_start_mjd": exposure_start_mjd,
454 "exposure_end_mjd": exposure_end_mjd,
455 "boresight_ra": boresight_ra,
456 "boresight_dec": boresight_dec,
457 "historical": historical})
463 """Exclude sources that have masked pixels in their footprints.
467 mask : `lsst.afw.image.Mask`
468 The image mask plane to use to reject sources
469 based on the location of their centroid on the ccd.
470 sources : `lsst.afw.table.SourceCatalog`
471 The source catalog to evaluate.
472 excludeMaskPlanes : `list` of `str`
473 List of the names of the mask planes to exclude.
477 good : `numpy.ndarray` of `bool`
478 Array indicating whether each source in the catalog should be
479 kept (True) or rejected (False) based on the value of the
480 mask plane at its location.
482 setExcludeMaskPlanes = [
483 maskPlane
for maskPlane
in excludeMaskPlanes
if maskPlane
in mask.getMaskPlaneDict()
486 excludePixelMask = mask.getPlaneBitMask(setExcludeMaskPlanes)
488 good = np.ones(len(sources), dtype=bool)
489 for i, source
in enumerate(sources):
490 bbox = source.getFootprint().getBBox()
491 if not mask.getBBox().contains(bbox):
496 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())