22"""Collection of small images (stamps), each centered on a bright star."""
24__all__ = [
"BrightStarStamp",
"BrightStarStamps"]
28from dataclasses
import dataclass
29from functools
import reduce
30from operator
import ior
35from lsst.afw.math import Property, StatisticsControl, makeStatistics, stringToStatisticsProperty
39from .stamps
import AbstractStamp, Stamps, readFitsWithOptions
41logger = logging.getLogger(__name__)
46 """Single stamp centered on a bright star, normalized by its annularFlux.
51 Pixel data for this postage stamp
53 Gaia G magnitude
for the object
in this stamp
55 Gaia object identifier
57 Origin of the stamps
in its origin exposure (pixels)
59 Archive element (e.g. Transform
or WCS) associated
with this stamp.
60 annularFlux : `float`
or None, optional
61 Flux
in an annulus around the object
64 stamp_im: MaskedImageF
68 archive_element: Persistable | None =
None
69 annularFlux: float |
None =
None
70 minValidAnnulusFraction: float = 0.0
73 def factory(cls, stamp_im, metadata, idx, archive_element=None, minValidAnnulusFraction=0.0):
74 """This method is needed to service the FITS reader. We need a standard
75 interface to construct objects like this. Parameters needed to
76 construct this object are passed in via a metadata dictionary
and then
77 passed to the constructor of this
class. This particular factory
78 method requires keys: G_MAGS, GAIA_IDS,
and ANNULAR_FLUXES. They should
79 each point to lists of values.
84 Pixel data to
pass to the constructor
86 Dictionary containing the information
87 needed by the constructor.
89 Index into the lists
in ``metadata``
91 Archive element (e.g. Transform
or WCS) associated
with this stamp.
92 minValidAnnulusFraction : `float`, optional
93 The fraction of valid pixels within the normalization annulus of a
98 brightstarstamp : `BrightStarStamp`
99 An instance of this
class
101 if "X0S" in metadata
and "Y0S" in metadata:
102 x0 = metadata.getArray(
"X0S")[idx]
103 y0 = metadata.getArray(
"Y0S")[idx]
104 position = Point2I(x0, y0)
109 gaiaGMag=metadata.getArray(
"G_MAGS")[idx],
110 gaiaId=metadata.getArray(
"GAIA_IDS")[idx],
112 archive_element=archive_element,
113 annularFlux=metadata.getArray(
"ANNULAR_FLUXES")[idx],
114 minValidAnnulusFraction=minValidAnnulusFraction,
121 statsFlag: Property = stringToStatisticsProperty(
"MEAN"),
122 badMaskPlanes: Collection[str] = (
"BAD",
"SAT",
"NO_DATA"),
124 """Compute "annularFlux", the integrated flux within an annulus
125 around an object's center, and normalize it.
127 Since the center of bright stars are saturated and/
or heavily affected
128 by ghosts, we measure their flux
in an annulus
with a large enough
129 inner radius to avoid the most severe ghosts
and contain enough
130 non-saturated pixels.
134 annulus : `~lsst.afw.geom.spanSet.SpanSet`
135 SpanSet containing the annulus to use
for normalization.
136 statsControl : `~lsst.afw.math.statistics.StatisticsControl`, optional
137 StatisticsControl to be used when computing flux over all pixels
139 statsFlag : `~lsst.afw.math.statistics.Property`, optional
140 statsFlag to be passed on to ``afwMath.makeStatistics`` to compute
141 annularFlux. Defaults to a simple MEAN.
142 badMaskPlanes : `collections.abc.Collection` [`str`]
143 Collection of mask planes to ignore when computing annularFlux.
148 annulusImage = MaskedImageF(stampSize, planeDict=maskPlaneDict)
149 annulusMask = annulusImage.mask
150 annulusMask.array[:] = 2 ** maskPlaneDict[
"NO_DATA"]
153 andMask = reduce(ior, (annulusMask.getPlaneBitMask(bm)
for bm
in badMaskPlanes))
154 statsControl.setAndMask(andMask)
156 annulusStat = makeStatistics(annulusImage, statsFlag, statsControl)
158 unMasked = annulusMask.array.size - np.count_nonzero(annulusMask.array)
161 "The Star's annulus contains %s valid pixels and the annulus itself contains %s pixels.",
171 f
"Less than {self.minValidAnnulusFraction * 100}% of pixels within the annulus are valid."
174 raise RuntimeError(
"Annular flux computation failed, likely because there are no valid pixels.")
176 raise RuntimeError(
"The annular flux is negative. The stamp can not be normalized!")
183 """Collection of bright star stamps and associated metadata.
187 starStamps : `collections.abc.Sequence` [`BrightStarStamp`]
188 Sequence of star stamps. Cannot contain both normalized and
190 innerRadius : `int`, optional
191 Inner radius value,
in pixels. This
and ``outerRadius`` define the
192 annulus used to compute the ``
"annularFlux"`` values within each
193 ``starStamp``. Must be provided
if ``normalize``
is True.
194 outerRadius : `int`, optional
195 Outer radius value,
in pixels. This
and ``innerRadius`` define the
196 annulus used to compute the ``
"annularFlux"`` values within each
197 ``starStamp``. Must be provided
if ``normalize``
is True.
198 nb90Rots : `int`, optional
199 Number of 90 degree rotations required to compensate
for detector
202 Metadata associated
with the bright stars.
204 If `
True` read
and write mask data. Default `
True`.
205 use_variance : `bool`
206 If ``
True`` read
and write variance data. Default ``
False``.
208 If ``
True`` read
and write an Archive that contains a Persistable
209 associated
with each stamp. In the case of bright stars, this
is
210 usually a ``TransformPoint2ToPoint2``, used to warp each stamp
211 to the same pixel grid before stacking.
216 Raised
if one of the star stamps provided does
not contain the
219 Raised
if there
is a mix-
and-match of normalized
and unnormalized
220 stamps, stamps normalized
with different annulus definitions,
or if
221 stamps are to be normalized but annular radii were
not provided.
225 A butler can be used to read only a part of the stamps, specified by a
228 >>> starSubregions = butler.get(
231 parameters={
"bbox": bbox}
246 super().
__init__(starStamps, metadata, use_mask, use_variance, use_archive)
251 if innerRadius
is not None and outerRadius
is not None:
269 discardNanFluxObjects=True,
270 statsControl=StatisticsControl(),
271 statsFlag=stringToStatisticsProperty(
"MEAN"),
272 badMaskPlanes=(
"BAD",
"SAT",
"NO_DATA"),
274 """Normalize a set of bright star stamps and initialize a
275 BrightStarStamps instance.
277 Since the center of bright stars are saturated and/
or heavily affected
278 by ghosts, we measure their flux
in an annulus
with a large enough
279 inner radius to avoid the most severe ghosts
and contain enough
280 non-saturated pixels.
284 starStamps : `collections.abc.Sequence` [`BrightStarStamp`]
285 Sequence of star stamps. Cannot contain both normalized
and
288 Inner radius value,
in pixels. This
and ``outerRadius`` define the
289 annulus used to compute the ``
"annularFlux"`` values within each
292 Outer radius value,
in pixels. This
and ``innerRadius`` define the
293 annulus used to compute the ``
"annularFlux"`` values within each
295 nb90Rots : `int`, optional
296 Number of 90 degree rotations required to compensate
for detector
299 Metadata associated
with the bright stars.
301 If `
True` read
and write mask data. Default `
True`.
302 use_variance : `bool`
303 If ``
True`` read
and write variance data. Default ``
False``.
305 If ``
True`` read
and write an Archive that contains a Persistable
306 associated
with each stamp. In the case of bright stars, this
is
307 usually a ``TransformPoint2ToPoint2``, used to warp each stamp
308 to the same pixel grid before stacking.
309 imCenter : `collections.abc.Sequence`, optional
310 Center of the object,
in pixels. If
not provided, the center of the
311 first stamp
's pixel grid will be used.
312 discardNanFluxObjects : `bool`
313 Whether objects with NaN annular flux should be discarded.
314 If
False, these objects will
not be normalized.
315 statsControl : `~lsst.afw.math.statistics.StatisticsControl`, optional
316 StatisticsControl to be used when computing flux over all pixels
318 statsFlag : `~lsst.afw.math.statistics.Property`, optional
319 statsFlag to be passed on to ``~lsst.afw.math.makeStatistics`` to
320 compute annularFlux. Defaults to a simple MEAN.
321 badMaskPlanes : `collections.abc.Collection` [`str`]
322 Collection of mask planes to ignore when computing annularFlux.
327 Raised
if one of the star stamps provided does
not contain the
330 Raised
if there
is a mix-
and-match of normalized
and unnormalized
331 stamps, stamps normalized
with different annulus definitions,
or if
332 stamps are to be normalized but annular radii were
not provided.
335 stampSize = starStamps[0].stamp_im.getDimensions()
336 imCenter = stampSize[0] // 2, stampSize[1] // 2
338 outerCircle = SpanSet.fromShape(outerRadius, Stencil.CIRCLE, offset=imCenter)
339 innerCircle = SpanSet.fromShape(innerRadius, Stencil.CIRCLE, offset=imCenter)
340 annulus = outerCircle.intersectNot(innerCircle)
349 use_variance=use_variance,
350 use_archive=use_archive,
353 bss._checkNormalization(
True, innerRadius, outerRadius)
354 bss._innerRadius, bss._outerRadius = innerRadius, outerRadius
358 for stamp
in bss._stamps:
360 stamp.measureAndNormalize(
361 annulus, statsControl=statsControl, statsFlag=statsFlag, badMaskPlanes=badMaskPlanes
363 except RuntimeError
as err:
368 if discardNanFluxObjects:
369 rejects.append(stamp)
371 stamp.annularFlux = np.nan
373 if discardNanFluxObjects:
374 for reject
in rejects:
375 bss._stamps.remove(reject)
376 bss.normalized =
True
380 """Refresh metadata. Should be called before writing the object out."""
386 self.
_metadata[
"X0S"] = [xy0[0]
for xy0
in positions]
387 self.
_metadata[
"Y0S"] = [xy0[1]
for xy0
in positions]
398 """Build an instance of this class from a file.
403 Name of the file to read
408 def readFitsWithOptions(cls, filename, options):
409 """Build an instance of this class with options.
414 Name of the file to read
415 options : `PropertyList`
416 Collection of metadata parameters
418 stamps, metadata = readFitsWithOptions(filename, BrightStarStamp.factory, options)
419 nb90Rots = metadata["NB_90_ROTS"]
if "NB_90_ROTS" in metadata
else None
420 if metadata[
"NORMALIZED"]:
423 innerRadius=metadata[
"INNER_RADIUS"],
424 outerRadius=metadata[
"OUTER_RADIUS"],
427 use_mask=metadata[
"HAS_MASK"],
428 use_variance=metadata[
"HAS_VARIANCE"],
429 use_archive=metadata[
"HAS_ARCHIVE"],
436 use_mask=metadata[
"HAS_MASK"],
437 use_variance=metadata[
"HAS_VARIANCE"],
438 use_archive=metadata[
"HAS_ARCHIVE"],
441 def append(self, item, innerRadius=None, outerRadius=None):
442 """Add an additional bright star stamp.
446 item : `BrightStarStamp`
447 Bright star stamp to append.
448 innerRadius : `int`, optional
449 Inner radius value, in pixels. This
and ``outerRadius`` define the
450 annulus used to compute the ``
"annularFlux"`` values within each
452 outerRadius : `int`, optional
453 Outer radius value,
in pixels. This
and ``innerRadius`` define the
454 annulus used to compute the ``
"annularFlux"`` values within each
457 if not isinstance(item, BrightStarStamp):
458 raise ValueError(f
"Can only add instances of BrightStarStamp, got {type(item)}.")
459 if (item.annularFlux
is None) == self.
normalized:
460 raise AttributeError(
461 "Trying to append an unnormalized stamp to a normalized BrightStarStamps "
462 "instance, or vice-versa."
470 """Extend BrightStarStamps instance by appending elements from another
475 bss : `BrightStarStamps`
476 Other instance to concatenate.
478 if not isinstance(bss, BrightStarStamps):
479 raise ValueError(f
"Can only extend with a BrightStarStamps object. Got {type(bss)}.")
484 """Retrieve Gaia G magnitudes for each star.
488 gaiaGMags : `list` [`float`]
490 return [stamp.gaiaGMag
for stamp
in self.
_stamps]
493 """Retrieve Gaia IDs for each star.
497 gaiaIds : `list` [`int`]
499 return [stamp.gaiaId
for stamp
in self.
_stamps]
502 """Retrieve normalization factors for each star.
504 These are computed by integrating the flux in annulus centered on the
505 bright star, far enough
from center to be beyond most severe ghosts
and
506 saturation. The inner
and outer radii that define the annulus can be
507 recovered
from the metadata.
511 annularFluxes : `list` [`float`]
513 return [stamp.annularFlux
for stamp
in self.
_stamps]
516 """Return the subset of bright star stamps for objects with specified
517 magnitude cuts (in Gaia G).
521 magMin : `float`, optional
522 Keep only stars fainter than this value.
523 magMax : `float`, optional
524 Keep only stars brighter than this value.
529 if (magMin
is None or stamp.gaiaGMag > magMin)
and (magMax
is None or stamp.gaiaGMag < magMax)
536 instance._stamps = subset
540 """Ensure provided annulus radius is consistent with that already
541 present in the instance,
or with arguments passed on at initialization.
544 raise AttributeError(
545 "Trying to mix stamps normalized with annulus radii "
546 f
"{innerRadius, outerRadius} with those of BrightStarStamp instance\n"
547 f
"(computed with annular radii {self._innerRadius, self._outerRadius})."
551 """Ensure there is no mixing of normalized and unnormalized stars, and
552 that, if requested, normalization can be performed.
556 nFluxVals = nStamps - noneFluxCount
557 if noneFluxCount
and noneFluxCount < nStamps:
560 raise AttributeError(
561 f
"Only {nFluxVals} stamps contain an annularFlux value.\nAll stamps in a "
562 "BrightStarStamps instance must either be normalized with the same annulus "
563 "definition, or none of them can contain an annularFlux value."
568 if innerRadius
is None or outerRadius
is None:
569 raise AttributeError(
570 "For stamps to be normalized (normalize=True), please provide a valid "
571 "value (in pixels) for both innerRadius and outerRadius."
573 elif noneFluxCount < nStamps:
574 raise AttributeError(
575 f
"{nFluxVals} stamps already contain an annularFlux value. For stamps to "
576 "be normalized, all their annularFlux must be None."
578 elif innerRadius
is not None and outerRadius
is not None:
582 raise AttributeError(
583 f
"{noneFluxCount} stamps contain no annularFlux, but annular radius "
584 "values were provided and normalize=False.\nTo normalize stamps, set "
591 raise AttributeError(
592 f
"{nFluxVals} stamps contain an annularFlux value. If stamps have "
593 "been normalized, the innerRadius and outerRadius values used must "
A class to manipulate images, masks, and variance as a single object.
Pass parameters to a Statistics object.
A base class for objects that can be persisted via afw::table::io Archive classes.
Class for storing ordered metadata with comments.
factory(cls, stamp_im, metadata, idx, archive_element=None, minValidAnnulusFraction=0.0)
float minValidAnnulusFraction
measureAndNormalize(self, SpanSet annulus, StatisticsControl statsControl=StatisticsControl(), Property statsFlag=stringToStatisticsProperty("MEAN"), Collection[str] badMaskPlanes=("BAD", "SAT", "NO_DATA"))
readFitsWithOptions(cls, filename, options)
_checkRadius(self, innerRadius, outerRadius)
initAndNormalize(cls, starStamps, innerRadius, outerRadius, nb90Rots=None, metadata=None, use_mask=True, use_variance=False, use_archive=False, imCenter=None, discardNanFluxObjects=True, statsControl=StatisticsControl(), statsFlag=stringToStatisticsProperty("MEAN"), badMaskPlanes=("BAD", "SAT", "NO_DATA"))
append(self, item, innerRadius=None, outerRadius=None)
selectByMag(self, magMin=None, magMax=None)
_checkNormalization(self, normalize, innerRadius, outerRadius)
__init__(self, starStamps, innerRadius=None, outerRadius=None, nb90Rots=None, metadata=None, use_mask=True, use_variance=False, use_archive=False)
readFitsWithOptions(cls, filename, options)
readFitsWithOptions(cls, filename, options)