166 """Load reference objects and mask bright stars on an exposure.
170 exposure : `lsst.afw.image.Exposure`
171 Science exposure to set the SPIKE plane. Will be modified in place.
175 spikeCat : `lsst.afw.table.SourceCatalog`
176 The entries from the reference catalog selected as stars with
180 raise RuntimeError(
"Running diffraction spike mask task with no refObjLoader set in"
181 " __init__ or setRefObjLoader")
185 filterLabel = exposure.getFilter()
186 region =
getRegion(exposure, lsst.sphgeom.Angle.fromDegrees(self.config.angleMargin/3600.))
187 refCat = self.
refObjLoader.loadRegion(region, filterLabel.bandLabel).refCat
194 bright = (magnitudes < self.config.magnitudeThreshold)
196 nBright = np.count_nonzero(bright)
199 mask.addMaskPlane(self.config.spikeMask)
201 xvals, yvals = exposure.wcs.skyToPixelArray(refCat[bright][self.config.raKey],
202 refCat[bright][self.config.decKey])
204 nSpike = len(spikeCandidates)
208 self.log.info(
"Calculating mask for %d stars brighter than magnitude %f", nSpike,
209 self.config.magnitudeThreshold)
211 yvals[spikeCandidates],
212 radii[bright][spikeCandidates],
215 self.log.info(
"No bright stars found in the reference catalog; not masking diffraction spikes.")
216 return afwTable.SourceCatalog(refCat.schema)
218 return refCat[bright][spikeCandidates].copy(deep=
True)
221 """Select saturated sources, and bright sources that are off the image.
225 xvals, yvals : `numpy.ndarray`
226 Array of x- and y-values of bright sources to mask.
227 mask : `lsst.afw.image.Mask`
228 The mask plane of the image to set the BRIGHT mask plane.
232 candidates : `numpy.ndarray`
233 Array of boolean flags indicating whether the given coordinates
234 should have a diffraction spike mask calculated.
236 candidates = np.zeros(len(xvals), dtype=bool)
237 saturatedBitMask = mask.getPlaneBitMask(self.config.saturatedMaskPlane)
238 bbox = mask.getBBox()
240 for i, (x, y)
in enumerate(zip(xvals, yvals)):
242 if bbox.contains(srcBox):
243 candidates[i] = np.all((mask[srcBox].array & saturatedBitMask) > 0)
249 """Apply the SPIKE mask for a given set of coordinates. The mask plane
250 will be modified in place.
254 xvals, yvals : `numpy.ndarray`
255 Array of x- and y-values of bright sources to mask.
256 radii : `numpy.ndarray`
257 Array of radius values for each bright source.
258 mask : `lsst.afw.image.Mask`
259 The mask plane of the image to set the BRIGHT mask plane.
261 bbox = mask.getBBox()
262 for x, y, r
in zip(xvals, yvals, radii):
264 singleBBox = maskSingle.getBBox()
265 if bbox.overlaps(singleBBox):
266 singleBBox = singleBBox.clippedTo(bbox)
267 mask[singleBBox] |= maskSingle[singleBBox]
270 """Create a mask plane centered on a single source with the BRIGHT mask
271 set. This mask does not have to be fully contained in the bounding box
272 of the mask of the science image.
277 Coordinates of the source to be masked.
279 Expected length of a diffraction spike for a source with a
284 mask : `lsst.afw.image.Mask`
285 A mask plane centered on the single source being modeled.
293 xLong = math.cos(np.deg2rad(angle))*r
294 yLong = math.sin(np.deg2rad(angle))*r
295 xShort = -math.sin(np.deg2rad(angle))*r/self.config.spikeAspectRatio
296 yShort = math.cos(np.deg2rad(angle))*r/self.config.spikeAspectRatio
302 polygons.append(afwGeom.Polygon(corners))
304 polygon = polygons[0]
305 for poly
in polygons[1:]:
306 polygon = polygon.unionSingle(poly)
308 polyImage = polygon.createImage(bbox).array
310 mask.array[polyImage > 0] = mask.getPlaneBitMask(self.config.spikeMask)
345 """Extract magnitude and magnitude error arrays from the given catalog.
349 refCat : `lsst.afw.table.SourceCatalog`
350 The input reference catalog.
352 Label of filter being calibrated.
356 result : `lsst.pipe.base.Struct`
357 Results as a struct with attributes:
360 Reference magnitude (`np.array`).
362 Reference magnitude error (`np.array`).
364 A list of field names of the reference catalog used for fluxes (1 or 2 strings) (`list`).
366 refSchema = refCat.schema
368 if self.config.applyColorTerms:
369 self.log.info(
"Applying color terms for filter=%r, config.photoCatName=%s",
370 filterLabel.physicalLabel, self.config.photoCatName)
371 colorterm = self.config.colorterms.getColorterm(filterLabel.physicalLabel,
372 self.config.photoCatName,
375 refMagArr, refMagErrArr = colorterm.getCorrectedMagnitudes(refCat)
376 fluxFieldList = [getRefFluxField(refSchema, filt)
for filt
in (colorterm.primary,
377 colorterm.secondary)]
379 self.log.info(
"Not applying color terms.")
382 fluxFieldList = [getRefFluxField(refSchema, filterLabel.bandLabel)]
383 fluxField = getRefFluxField(refSchema, filterLabel.bandLabel)
384 fluxKey = refSchema.find(fluxField).key
385 refFluxArr = np.array(refCat.get(fluxKey))
388 fluxErrKey = refSchema.find(fluxField +
"Err").key
389 refFluxErrArr = np.array(refCat.get(fluxErrKey))
392 self.log.warning(
"Reference catalog does not have flux uncertainties for %s;"
393 " using sqrt(flux).", fluxField)
394 refFluxErrArr = np.sqrt(refFluxArr)
396 refMagArr = u.Quantity(refFluxArr, u.nJy).to_value(u.ABmag)
398 refMagErrArr = abMagErrFromFluxErr(refFluxErrArr*1e-9, refFluxArr*1e-9)
403 badMagnitudes = np.isnan(refMagArr)
404 if np.count_nonzero(badMagnitudes):
405 fluxName = f
"{self.config.fallbackFluxField}_flux"
408 fallbackFluxKey = refSchema.find(fluxName).key
410 self.log.warning(f
"Field {fluxName} not found in photometric reference catalog."
411 " Not masking bright stars with missing magnitudes.")
413 fallbackFluxArr = np.array(refCat.get(fallbackFluxKey))
414 fallbackMagArr = u.Quantity(fallbackFluxArr[badMagnitudes], u.nJy).to_value(u.ABmag)
415 refMagArr[badMagnitudes] = fallbackMagArr
419 refMagErr=refMagErrArr,
420 refFluxFieldList=fluxFieldList,