Coverage for python/lsst/meas/algorithms/brightStarStamps.py : 36%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of meas_algorithms.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
21#
22"""Collection of small images (stamps), each centered on a bright star.
23"""
25__all__ = ["BrightStarStamp", "BrightStarStamps"]
27from dataclasses import dataclass
28from enum import Enum, auto
30from lsst.afw.image import MaskedImage
31from .stamps import StampsBase, AbstractStamp, readFitsWithOptions
34class RadiiEnum(Enum):
35 INNER_RADIUS = auto()
36 OUTER_RADIUS = auto()
38 def __str__(self):
39 return self.name
42@dataclass
43class BrightStarStamp(AbstractStamp):
44 """Single stamp centered on a bright star, normalized by its
45 annularFlux.
47 Parameters
48 ----------
49 stamp_im : `lsst.afw.image.MaskedImage`
50 Pixel data for this postage stamp
51 gaiaGMag : `float`
52 Gaia G magnitude for the object in this stamp
53 gaiaId : `int`
54 Gaia object identifier
55 annularFlux : `float`
56 Flux in an annulus around the object
57 """
58 stamp_im: MaskedImage
59 gaiaGMag: float
60 gaiaId: int
61 annularFlux: float
63 @classmethod
64 def factory(cls, stamp_im, metadata, idx):
65 """This method is needed to service the FITS reader.
66 We need a standard interface to construct objects like this.
67 Parameters needed to construct this object are passed in via
68 a metadata dictionary and then passed to the constructor of
69 this class. This particular factory method requires keys:
70 G_MAGS, GAIA_IDS, and ANNULAR_FLUXES. They should each
71 point to lists of values.
73 Parameters
74 ----------
75 stamp_im : `lsst.afw.image.MaskedImage`
76 Pixel data to pass to the constructor
77 metadata : `dict`
78 Dictionary containing the information
79 needed by the constructor.
80 idx : `int`
81 Index into the lists in ``metadata``
83 Returns
84 -------
85 brightstarstamp : `BrightStarStamp`
86 An instance of this class
87 """
88 return cls(stamp_im=stamp_im,
89 gaiaGMag=metadata.getArray('G_MAGS')[idx],
90 gaiaId=metadata.getArray('GAIA_IDS')[idx],
91 annularFlux=metadata.getArray('ANNULAR_FLUXES')[idx])
94class BrightStarStamps(StampsBase):
95 """Collection of bright star stamps and associated metadata.
97 Parameters
98 ----------
99 starStamps : `collections.abc.Sequence` [`BrightStarStamp`]
100 Sequence of star stamps.
101 innerRadius : `int`, optional
102 Inner radius value, in pixels. This and ``outerRadius`` define the
103 annulus used to compute the ``"annularFlux"`` values within each
104 ``starStamp``. Must be provided if ``"INNER_RADIUS"`` and
105 ``"OUTER_RADIUS"`` are not present in ``metadata``.
106 outerRadius : `int`, optional
107 Outer radius value, in pixels. This and ``innerRadius`` define the
108 annulus used to compute the ``"annularFlux"`` values within each
109 ``starStamp``. Must be provided if ``"INNER_RADIUS"`` and
110 ``"OUTER_RADIUS"`` are not present in ``metadata``.
111 metadata : `lsst.daf.base.PropertyList`, optional
112 Metadata associated with the bright stars.
113 use_mask : `bool`
114 If `True` read and write mask data. Default `True`.
115 use_variance : `bool`
116 If ``True`` read and write variance data. Default ``False``.
118 Raises
119 ------
120 ValueError
121 Raised if one of the star stamps provided does not contain the
122 required keys.
123 AttributeError
124 Raised if the definition of the annulus used to compute each star's
125 normalization factor are not provided, that is, if ``"INNER_RADIUS"``
126 and ``"OUTER_RADIUS"`` are not present in ``metadata`` _and_
127 ``innerRadius`` and ``outerRadius`` are not provided.
129 Notes
130 -----
131 A (gen2) butler can be used to read only a part of the stamps,
132 specified by a bbox:
134 >>> starSubregions = butler.get("brightStarStamps_sub", dataId, bbox=bbox)
135 """
137 def __init__(self, starStamps, innerRadius=None, outerRadius=None,
138 metadata=None, use_mask=True, use_variance=False):
139 super().__init__(starStamps, metadata, use_mask, use_variance)
140 # Add inner and outer radii to metadata
141 self._checkRadius(innerRadius, RadiiEnum.INNER_RADIUS)
142 self._innerRadius = innerRadius
143 self._checkRadius(outerRadius, RadiiEnum.OUTER_RADIUS)
144 self._outerRadius = outerRadius
146 def _refresh_metadata(self):
147 """Refresh the metadata. Should be called before writing this object out.
148 """
149 # add full list of Gaia magnitudes, IDs and annularFlxes to shared
150 # metadata
151 self._metadata["G_MAGS"] = self.getMagnitudes()
152 self._metadata["GAIA_IDS"] = self.getGaiaIds()
153 self._metadata["ANNULAR_FLUXES"] = self.getAnnularFluxes()
154 return None
156 @classmethod
157 def readFits(cls, filename):
158 """Build an instance of this class from a file.
160 Parameters
161 ----------
162 filename : `str`
163 Name of the file to read
164 """
165 return cls.readFitsWithOptions(filename, None)
167 @classmethod
168 def readFitsWithOptions(cls, filename, options):
169 """Build an instance of this class with options.
171 Parameters
172 ----------
173 filename : `str`
174 Name of the file to read
175 options : `PropertyList`
176 Collection of metadata parameters
177 """
178 stamps, metadata = readFitsWithOptions(filename, BrightStarStamp.factory, options)
179 return cls(stamps, metadata=metadata, use_mask=metadata['HAS_MASK'],
180 use_variance=metadata['HAS_VARIANCE'])
182 def append(self, item, innerRadius, outerRadius):
183 """Add an additional bright star stamp.
185 Parameters
186 ----------
187 item : `BrightStarStamp`
188 Bright star stamp to append.
189 innerRadius : `int`
190 Inner radius value, in pixels. This and ``outerRadius`` define the
191 annulus used to compute the ``"annularFlux"`` values within each
192 ``starStamp``.
193 outerRadius : `int`, optional
194 Outer radius value, in pixels. This and ``innerRadius`` define the
195 annulus used to compute the ``"annularFlux"`` values within each
196 ``starStamp``.
197 """
198 if not isinstance(item, BrightStarStamp):
199 raise ValueError(f"Can only add instances of BrightStarStamp, got {type(item)}.")
200 self._checkRadius(innerRadius, RadiiEnum.INNER_RADIUS)
201 self._checkRadius(outerRadius, RadiiEnum.OUTER_RADIUS)
202 self._stamps.append(item)
203 return None
205 def extend(self, bss):
206 """Extend BrightStarStamps instance by appending elements from another
207 instance.
209 Parameters
210 ----------
211 bss : `BrightStarStamps`
212 Other instance to concatenate.
213 """
214 if not isinstance(bss, BrightStarStamps):
215 raise ValueError('Can only extend with a BrightStarStamps object. '
216 f'Got {type(bss)}.')
217 self._checkRadius(bss._innerRadius, RadiiEnum.INNER_RADIUS)
218 self._checkRadius(bss._outerRadius, RadiiEnum.OUTER_RADIUS)
219 self._stamps += bss._stamps
221 def getMagnitudes(self):
222 """Retrieve Gaia G magnitudes for each star.
224 Returns
225 -------
226 gaiaGMags : `list` [`float`]
227 """
228 return [stamp.gaiaGMag for stamp in self._stamps]
230 def getGaiaIds(self):
231 """Retrieve Gaia IDs for each star.
233 Returns
234 -------
235 gaiaIds : `list` [`int`]
236 """
237 return [stamp.gaiaId for stamp in self._stamps]
239 def getAnnularFluxes(self):
240 """Retrieve normalization factors for each star.
242 These are computed by integrating the flux in annulus centered on the
243 bright star, far enough from center to be beyond most severe ghosts and
244 saturation. The inner and outer radii that define the annulus can be
245 recovered from the metadata.
247 Returns
248 -------
249 annularFluxes : `list` [`float`]
250 """
251 return [stamp.annularFlux for stamp in self._stamps]
253 def selectByMag(self, magMin=None, magMax=None):
254 """Return the subset of bright star stamps for objects with specified
255 magnitude cuts (in Gaia G).
257 Parameters
258 ----------
259 magMin : `float`, optional
260 Keep only stars fainter than this value.
261 magMax : `float`, optional
262 Keep only stars brighter than this value.
263 """
264 subset = [stamp for stamp in self._stamps
265 if (magMin is None or stamp.gaiaGMag > magMin)
266 and (magMax is None or stamp.gaiaGMag < magMax)]
267 # This is an optimization to save looping over the init argument when
268 # it is already guaranteed to be the correct type
269 instance = BrightStarStamps((), metadata=self._metadata)
270 instance._stamps = subset
271 return instance
273 def _checkRadius(self, radiusValue, metadataEnum):
274 """Ensure provided annulus radius is consistent with that present
275 in metadata. If metadata does not contain annulus radius, add it.
276 """
277 # if a radius value is already present in metadata, ensure it matches
278 # the one given
279 metadataName = str(metadataEnum)
280 if self._metadata.exists(metadataName):
281 if radiusValue is not None:
282 if self._metadata[metadataName] != radiusValue:
283 raise AttributeError("BrightStarStamps instance already contains different annulus radii "
284 + f"values ({metadataName}).")
285 # if not already in metadata, a value must be provided
286 elif radiusValue is None:
287 raise AttributeError("No radius value provided for the AnnularFlux measurement "
288 + f"({metadataName}), and none present in metadata.")
289 else:
290 self._metadata[metadataName] = radiusValue
291 return None