22 """Collection of small images (stamps), each centered on a bright star.
25 __all__ = [
"BrightStarStamp",
"BrightStarStamps"]
27 from dataclasses
import dataclass
28 from operator
import ior
29 from functools
import reduce
30 from typing
import Optional
35 from .stamps
import StampsBase, AbstractStamp, readFitsWithOptions
40 """Single stamp centered on a bright star, normalized by its
45 stamp_im : `lsst.afw.image.MaskedImage`
46 Pixel data for this postage stamp
48 Gaia G magnitude for the object in this stamp
50 Gaia object identifier
51 annularFlux : `Optional[float]`
52 Flux in an annulus around the object
54 stamp_im: MaskedImageF
57 annularFlux: Optional[float] =
None
60 def factory(cls, stamp_im, metadata, idx):
61 """This method is needed to service the FITS reader.
62 We need a standard interface to construct objects like this.
63 Parameters needed to construct this object are passed in via
64 a metadata dictionary and then passed to the constructor of
65 this class. This particular factory method requires keys:
66 G_MAGS, GAIA_IDS, and ANNULAR_FLUXES. They should each
67 point to lists of values.
71 stamp_im : `lsst.afw.image.MaskedImage`
72 Pixel data to pass to the constructor
74 Dictionary containing the information
75 needed by the constructor.
77 Index into the lists in ``metadata``
81 brightstarstamp : `BrightStarStamp`
82 An instance of this class
84 return cls(stamp_im=stamp_im,
85 gaiaGMag=metadata.getArray(
'G_MAGS')[idx],
86 gaiaId=metadata.getArray(
'GAIA_IDS')[idx],
87 annularFlux=metadata.getArray(
'ANNULAR_FLUXES')[idx])
91 badMaskPlanes=(
'BAD',
'SAT',
'NO_DATA')):
92 """Compute "annularFlux", the integrated flux within an annulus
93 around an object's center, and normalize it.
95 Since the center of bright stars are saturated and/or heavily affected
96 by ghosts, we measure their flux in an annulus with a large enough
97 inner radius to avoid the most severe ghosts and contain enough
102 annulus : `lsst.afw.geom.spanSet.SpanSet`
103 SpanSet containing the annulus to use for normalization.
104 statsControl : `lsst.afw.math.statistics.StatisticsControl`, optional
105 StatisticsControl to be used when computing flux over all pixels
107 statsFlag : `lsst.afw.math.statistics.Property`, optional
108 statsFlag to be passed on to ``afwMath.makeStatistics`` to compute
109 annularFlux. Defaults to a simple MEAN.
110 badMaskPlanes : `collections.abc.Collection` [`str`]
111 Collection of mask planes to ignore when computing annularFlux.
113 stampSize = self.stamp_im.getDimensions()
116 maskPlaneDict = self.stamp_im.mask.getMaskPlaneDict()
117 annulusImage = MaskedImageF(stampSize, planeDict=maskPlaneDict)
118 annulusMask = annulusImage.mask
119 annulusMask.array[:] = 2**maskPlaneDict[
'NO_DATA']
120 annulus.copyMaskedImage(self.stamp_im, annulusImage)
122 andMask = reduce(ior, (annulusMask.getPlaneBitMask(bm)
for bm
in badMaskPlanes))
123 statsControl.setAndMask(andMask)
128 self.stamp_im.image.array /= self.
annularFluxannularFlux
133 """Collection of bright star stamps and associated metadata.
137 starStamps : `collections.abc.Sequence` [`BrightStarStamp`]
138 Sequence of star stamps. Cannot contain both normalized and
140 innerRadius : `int`, optional
141 Inner radius value, in pixels. This and ``outerRadius`` define the
142 annulus used to compute the ``"annularFlux"`` values within each
143 ``starStamp``. Must be provided if ``normalize`` is True.
144 outerRadius : `int`, optional
145 Outer radius value, in pixels. This and ``innerRadius`` define the
146 annulus used to compute the ``"annularFlux"`` values within each
147 ``starStamp``. Must be provided if ``normalize`` is True.
148 metadata : `lsst.daf.base.PropertyList`, optional
149 Metadata associated with the bright stars.
151 If `True` read and write mask data. Default `True`.
152 use_variance : `bool`
153 If ``True`` read and write variance data. Default ``False``.
158 Raised if one of the star stamps provided does not contain the
161 Raised if there is a mix-and-match of normalized and unnormalized
162 stamps, stamps normalized with different annulus definitions, or if
163 stamps are to be normalized but annular radii were not provided.
168 A butler can be used to read only a part of the stamps, specified by a
171 >>> starSubregions = butler.get("brightStarStamps_sub", dataId, bbox=bbox)
174 def __init__(self, starStamps, innerRadius=None, outerRadius=None,
175 metadata=None, use_mask=True, use_variance=False):
176 super().
__init__(starStamps, metadata, use_mask, use_variance)
180 self._innerRadius, self.
_outerRadius_outerRadius = innerRadius, outerRadius
181 if innerRadius
is not None and outerRadius
is not None:
188 metadata=None, use_mask=True, use_variance=False,
190 statsControl=afwMath.StatisticsControl(),
192 badMaskPlanes=(
'BAD',
'SAT',
'NO_DATA')):
193 """Normalize a set of bright star stamps and initialize a
194 BrightStarStamps instance.
196 Since the center of bright stars are saturated and/or heavily affected
197 by ghosts, we measure their flux in an annulus with a large enough
198 inner radius to avoid the most severe ghosts and contain enough
199 non-saturated pixels.
203 starStamps : `collections.abc.Sequence` [`BrightStarStamp`]
204 Sequence of star stamps. Cannot contain both normalized and
207 Inner radius value, in pixels. This and ``outerRadius`` define the
208 annulus used to compute the ``"annularFlux"`` values within each
211 Outer radius value, in pixels. This and ``innerRadius`` define the
212 annulus used to compute the ``"annularFlux"`` values within each
214 metadata : `lsst.daf.base.PropertyList`, optional
215 Metadata associated with the bright stars.
217 If `True` read and write mask data. Default `True`.
218 use_variance : `bool`
219 If ``True`` read and write variance data. Default ``False``.
220 imCenter : `collections.abc.Sequence`, optional
221 Center of the object, in pixels. If not provided, the center of the
222 first stamp's pixel grid will be used.
223 statsControl : `lsst.afw.math.statistics.StatisticsControl`, optional
224 StatisticsControl to be used when computing flux over all pixels
226 statsFlag : `lsst.afw.math.statistics.Property`, optional
227 statsFlag to be passed on to ``afwMath.makeStatistics`` to compute
228 annularFlux. Defaults to a simple MEAN.
229 badMaskPlanes : `collections.abc.Collection` [`str`]
230 Collection of mask planes to ignore when computing annularFlux.
235 Raised if one of the star stamps provided does not contain the
238 Raised if there is a mix-and-match of normalized and unnormalized
239 stamps, stamps normalized with different annulus definitions, or if
240 stamps are to be normalized but annular radii were not provided.
243 stampSize = starStamps[0].stamp_im.getDimensions()
244 imCenter = stampSize[0]//2, stampSize[1]//2
246 outerCircle = afwGeom.SpanSet.fromShape(outerRadius, afwGeom.Stencil.CIRCLE, offset=imCenter)
247 innerCircle = afwGeom.SpanSet.fromShape(innerRadius, afwGeom.Stencil.CIRCLE, offset=imCenter)
248 annulus = outerCircle.intersectNot(innerCircle)
250 bss = cls(starStamps, innerRadius=
None, outerRadius=
None,
251 metadata=metadata, use_mask=use_mask,
252 use_variance=use_variance)
254 bss._checkNormalization(
True, innerRadius, outerRadius)
255 bss._innerRadius, bss._outerRadius = innerRadius, outerRadius
257 for stamp
in bss._stamps:
258 stamp.measureAndNormalize(annulus, statsControl=statsControl, statsFlag=statsFlag,
259 badMaskPlanes=badMaskPlanes)
260 bss.normalized =
True
263 def _refresh_metadata(self):
264 """Refresh the metadata. Should be called before writing this object
273 self.
_metadata_metadata[
"INNER_RADIUS"] = self._innerRadius
279 """Build an instance of this class from a file.
284 Name of the file to read
290 """Build an instance of this class with options.
295 Name of the file to read
296 options : `PropertyList`
297 Collection of metadata parameters
300 if metadata[
"NORMALIZED"]:
302 innerRadius=metadata[
"INNER_RADIUS"], outerRadius=metadata[
"OUTER_RADIUS"],
303 metadata=metadata, use_mask=metadata[
'HAS_MASK'],
304 use_variance=metadata[
'HAS_VARIANCE'])
306 return cls(stamps, metadata=metadata, use_mask=metadata[
'HAS_MASK'],
307 use_variance=metadata[
'HAS_VARIANCE'])
309 def append(self, item, innerRadius=None, outerRadius=None):
310 """Add an additional bright star stamp.
314 item : `BrightStarStamp`
315 Bright star stamp to append.
316 innerRadius : `int`, optional
317 Inner radius value, in pixels. This and ``outerRadius`` define the
318 annulus used to compute the ``"annularFlux"`` values within each
320 outerRadius : `int`, optional
321 Outer radius value, in pixels. This and ``innerRadius`` define the
322 annulus used to compute the ``"annularFlux"`` values within each
325 if not isinstance(item, BrightStarStamp):
326 raise ValueError(f
"Can only add instances of BrightStarStamp, got {type(item)}.")
327 if (item.annularFlux
is None) == self.
normalizednormalized:
328 raise AttributeError(
"Trying to append an unnormalized stamp to a normalized BrightStarStamps "
329 "instance, or vice-versa.")
336 """Extend BrightStarStamps instance by appending elements from another
341 bss : `BrightStarStamps`
342 Other instance to concatenate.
344 if not isinstance(bss, BrightStarStamps):
345 raise ValueError(
'Can only extend with a BrightStarStamps object. '
347 self.
_checkRadius_checkRadius(bss._innerRadius, bss._outerRadius)
348 self.
_stamps_stamps += bss._stamps
351 """Retrieve Gaia G magnitudes for each star.
355 gaiaGMags : `list` [`float`]
357 return [stamp.gaiaGMag
for stamp
in self.
_stamps_stamps]
360 """Retrieve Gaia IDs for each star.
364 gaiaIds : `list` [`int`]
366 return [stamp.gaiaId
for stamp
in self.
_stamps_stamps]
369 """Retrieve normalization factors for each star.
371 These are computed by integrating the flux in annulus centered on the
372 bright star, far enough from center to be beyond most severe ghosts and
373 saturation. The inner and outer radii that define the annulus can be
374 recovered from the metadata.
378 annularFluxes : `list` [`float`]
380 return [stamp.annularFlux
for stamp
in self.
_stamps_stamps]
383 """Return the subset of bright star stamps for objects with specified
384 magnitude cuts (in Gaia G).
388 magMin : `float`, optional
389 Keep only stars fainter than this value.
390 magMax : `float`, optional
391 Keep only stars brighter than this value.
393 subset = [stamp
for stamp
in self.
_stamps_stamps
394 if (magMin
is None or stamp.gaiaGMag > magMin)
395 and (magMax
is None or stamp.gaiaGMag < magMax)]
399 innerRadius=self._innerRadius, outerRadius=self.
_outerRadius_outerRadius,
401 instance._stamps = subset
404 def _checkRadius(self, innerRadius, outerRadius):
405 """Ensure provided annulus radius is consistent with that already
406 present in the instance, or with arguments passed on at initialization.
408 if innerRadius != self._innerRadius
or outerRadius != self.
_outerRadius_outerRadius:
409 raise AttributeError(
"Trying to mix stamps normalized with annulus radii "
410 f
"{innerRadius, outerRadius} with those of BrightStarStamp instance\n"
411 f
"(computed with annular radii {self._innerRadius, self._outerRadius}).")
413 def _checkNormalization(self, normalize, innerRadius, outerRadius):
414 """Ensure there is no mixing of normalized and unnormalized stars, and
415 that, if requested, normalization can be performed.
419 nFluxVals = nStamps - noneFluxCount
420 if noneFluxCount
and noneFluxCount < nStamps:
423 raise AttributeError(f
"Only {nFluxVals} stamps contain an annularFlux value.\nAll stamps in a "
424 "BrightStarStamps instance must either be normalized with the same annulus "
425 "definition, or none of them can contain an annularFlux value.")
429 if innerRadius
is None or outerRadius
is None:
430 raise AttributeError(
"For stamps to be normalized (normalize=True), please provide a valid "
431 "value (in pixels) for both innerRadius and outerRadius.")
432 elif noneFluxCount < nStamps:
433 raise AttributeError(f
"{nFluxVals} stamps already contain an annularFlux value. For stamps to"
434 " be normalized, all their annularFlux must be None.")
435 elif innerRadius
is not None and outerRadius
is not None:
439 raise AttributeError(f
"{noneFluxCount} stamps contain no annularFlux, but annular radius "
440 "values were provided and normalize=False.\nTo normalize stamps, set "
441 "normalize to True.")
446 raise AttributeError(f
"{nFluxVals} stamps contain an annularFlux value. If stamps have "
447 "been normalized, the innerRadius and outerRadius values used must "
def factory(cls, stamp_im, metadata, idx)
def measureAndNormalize(self, annulus, statsControl=afwMath.StatisticsControl(), statsFlag=afwMath.stringToStatisticsProperty("MEAN"), badMaskPlanes=('BAD', 'SAT', 'NO_DATA'))
def _checkNormalization(self, normalize, innerRadius, outerRadius)
def __init__(self, starStamps, innerRadius=None, outerRadius=None, metadata=None, use_mask=True, use_variance=False)
def initAndNormalize(cls, starStamps, innerRadius, outerRadius, metadata=None, use_mask=True, use_variance=False, imCenter=None, statsControl=afwMath.StatisticsControl(), statsFlag=afwMath.stringToStatisticsProperty("MEAN"), badMaskPlanes=('BAD', 'SAT', 'NO_DATA'))
def readFits(cls, filename)
def append(self, item, innerRadius=None, outerRadius=None)
def selectByMag(self, magMin=None, magMax=None)
def getAnnularFluxes(self)
def readFitsWithOptions(cls, filename, options)
def _checkRadius(self, innerRadius, outerRadius)
def readFitsWithOptions(cls, filename, options)
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
Statistics makeStatistics(lsst::afw::image::Image< Pixel > const &img, lsst::afw::image::Mask< image::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl=StatisticsControl())
Handle a watered-down front-end to the constructor (no variance)
Property stringToStatisticsProperty(std::string const property)
Conversion function to switch a string to a Property (see Statistics.h)