168 """Load reference objects and mask bright stars on an exposure.
172 exposure : `lsst.afw.image.Exposure`
173 Science exposure to set the SPIKE plane. Will be modified in place.
177 spikeCat : `lsst.afw.table.SimpleCatalog`
178 The entries from the reference catalog selected as stars with
182 raise RuntimeError(
"Running diffraction spike mask task with no refObjLoader set in"
183 " __init__ or setRefObjLoader")
187 filterLabel = exposure.getFilter()
188 region =
getRegion(exposure, lsst.sphgeom.Angle.fromDegrees(self.config.angleMargin/3600.))
189 refCat = self.
refObjLoader.loadRegion(region, filterLabel.bandLabel).refCat
196 bright = (magnitudes < self.config.magnitudeThreshold)
198 nBright = np.count_nonzero(bright)
201 mask.addMaskPlane(self.config.spikeMask)
203 xvals, yvals = exposure.wcs.skyToPixelArray(refCat[bright][self.config.raKey],
204 refCat[bright][self.config.decKey])
205 spikeCandidates = self.
selectSources(xvals, yvals, radii, mask)
206 nSpike = len(spikeCandidates)
210 self.log.info(
"Calculating mask for %d stars brighter than magnitude %f", nSpike,
211 self.config.magnitudeThreshold)
213 yvals[spikeCandidates],
214 radii[bright][spikeCandidates],
217 self.log.info(
"No bright stars found in the reference catalog; not masking diffraction spikes.")
218 return afwTable.SimpleCatalog(refCat.schema)
220 return refCat[bright][spikeCandidates].copy(deep=
True)
223 """Select saturated sources, and bright sources that are off the image.
227 xvals, yvals : `numpy.ndarray`
228 Array of x- and y-values of bright sources to mask.
229 mask : `lsst.afw.image.Mask`
230 The mask plane of the image to set the SPIKE mask plane.
231 spikeRadii : `numpy.ndarray`
232 Predicted lengths in pixels of the diffraction spikes for each
237 candidates : `numpy.ndarray`
238 Array of boolean flags indicating whether the given coordinates
239 should have a diffraction spike mask calculated.
241 candidates = np.zeros(len(xvals), dtype=bool)
242 saturatedBitMask = mask.getPlaneBitMask(self.config.saturatedMaskPlane)
243 bbox = mask.getBBox()
244 projection = np.max([abs(np.cos(np.deg2rad(self.
angles))),
245 abs(np.sin(np.deg2rad(self.
angles)))])
247 for i, (x, y, r)
in enumerate(zip(xvals, yvals, spikeRadii)):
249 if bbox.contains(srcBox):
250 candidates[i] = np.all((mask[srcBox].array & saturatedBitMask) > 0)
258 """Apply the SPIKE mask for a given set of coordinates. The mask plane
259 will be modified in place.
263 xvals, yvals : `numpy.ndarray`
264 Array of x- and y-values of bright sources to mask.
265 spikeRadii : `numpy.ndarray`
266 Array of radius values for each bright source.
267 mask : `lsst.afw.image.Mask`
268 The mask plane of the image to set the SPIKE mask plane.
270 bbox = mask.getBBox()
271 for x, y, r
in zip(xvals, yvals, spikeRadii):
273 singleBBox = maskSingle.getBBox()
274 if bbox.overlaps(singleBBox):
275 singleBBox = singleBBox.clippedTo(bbox)
276 mask[singleBBox] |= maskSingle[singleBBox]
279 """Create a mask plane centered on a single source with the BRIGHT mask
280 set. This mask does not have to be fully contained in the bounding box
281 of the mask of the science image.
286 Coordinates of the source to be masked.
288 Expected length of a diffraction spike for a source with a
293 mask : `lsst.afw.image.Mask`
294 A mask plane centered on the single source being modeled.
302 xLong = np.cos(np.deg2rad(angle))*r
303 yLong = np.sin(np.deg2rad(angle))*r
304 xShort = -np.sin(np.deg2rad(angle))*r/self.config.spikeAspectRatio
305 yShort = np.cos(np.deg2rad(angle))*r/self.config.spikeAspectRatio
311 polygons.append(afwGeom.Polygon(corners))
313 polygon = polygons[0]
314 for poly
in polygons[1:]:
315 polygon = polygon.unionSingle(poly)
317 polyImage = polygon.createImage(bbox).array
319 mask.array[polyImage > 0] = mask.getPlaneBitMask(self.config.spikeMask)
354 """Extract magnitude and magnitude error arrays from the given catalog.
358 refCat : `lsst.afw.table.SimpleCatalog`
359 The input reference catalog.
361 Label of filter being calibrated.
365 result : `lsst.pipe.base.Struct`
366 Results as a struct with attributes:
369 Reference magnitude (`np.array`).
371 Reference magnitude error (`np.array`).
373 A list of field names of the reference catalog used for fluxes (1 or 2 strings) (`list`).
375 refSchema = refCat.schema
377 if self.config.applyColorTerms:
378 self.log.info(
"Applying color terms for filter=%r, config.photoCatName=%s",
379 filterLabel.physicalLabel, self.config.photoCatName)
380 colorterm = self.config.colorterms.getColorterm(filterLabel.physicalLabel,
381 self.config.photoCatName,
384 refMagArr, refMagErrArr = colorterm.getCorrectedMagnitudes(refCat)
385 fluxFieldList = [getRefFluxField(refSchema, filt)
for filt
in (colorterm.primary,
386 colorterm.secondary)]
388 self.log.info(
"Not applying color terms.")
391 fluxFieldList = [getRefFluxField(refSchema, filterLabel.bandLabel)]
392 fluxField = getRefFluxField(refSchema, filterLabel.bandLabel)
393 fluxKey = refSchema.find(fluxField).key
394 refFluxArr = np.array(refCat.get(fluxKey))
397 fluxErrKey = refSchema.find(fluxField +
"Err").key
398 refFluxErrArr = np.array(refCat.get(fluxErrKey))
401 self.log.warning(
"Reference catalog does not have flux uncertainties for %s;"
402 " using sqrt(flux).", fluxField)
403 refFluxErrArr = np.sqrt(refFluxArr)
405 refMagArr = u.Quantity(refFluxArr, u.nJy).to_value(u.ABmag)
407 refMagErrArr = abMagErrFromFluxErr(refFluxErrArr*1e-9, refFluxArr*1e-9)
412 badMagnitudes = np.isnan(refMagArr)
413 if np.count_nonzero(badMagnitudes):
414 fluxName = f
"{self.config.fallbackFluxField}_flux"
417 fallbackFluxKey = refSchema.find(fluxName).key
419 self.log.warning(f
"Field {fluxName} not found in photometric reference catalog."
420 " Not masking bright stars with missing magnitudes.")
422 fallbackFluxArr = np.array(refCat.get(fallbackFluxKey))
423 fallbackMagArr = u.Quantity(fallbackFluxArr[badMagnitudes], u.nJy).to_value(u.ABmag)
424 refMagArr[badMagnitudes] = fallbackMagArr
428 refMagErr=refMagErrArr,
429 refFluxFieldList=fluxFieldList,