LSSTApplications  19.0.0-14-gb0260a2+72efe9b372,20.0.0+7927753e06,20.0.0+8829bf0056,20.0.0+995114c5d2,20.0.0+b6f4b2abd1,20.0.0+bddc4f4cbe,20.0.0-1-g253301a+8829bf0056,20.0.0-1-g2b7511a+0d71a2d77f,20.0.0-1-g5b95a8c+7461dd0434,20.0.0-12-g321c96ea+23efe4bbff,20.0.0-16-gfab17e72e+fdf35455f6,20.0.0-2-g0070d88+ba3ffc8f0b,20.0.0-2-g4dae9ad+ee58a624b3,20.0.0-2-g61b8584+5d3db074ba,20.0.0-2-gb780d76+d529cf1a41,20.0.0-2-ged6426c+226a441f5f,20.0.0-2-gf072044+8829bf0056,20.0.0-2-gf1f7952+ee58a624b3,20.0.0-20-geae50cf+e37fec0aee,20.0.0-25-g3dcad98+544a109665,20.0.0-25-g5eafb0f+ee58a624b3,20.0.0-27-g64178ef+f1f297b00a,20.0.0-3-g4cc78c6+e0676b0dc8,20.0.0-3-g8f21e14+4fd2c12c9a,20.0.0-3-gbd60e8c+187b78b4b8,20.0.0-3-gbecbe05+48431fa087,20.0.0-38-ge4adf513+a12e1f8e37,20.0.0-4-g97dc21a+544a109665,20.0.0-4-gb4befbc+087873070b,20.0.0-4-gf910f65+5d3db074ba,20.0.0-5-gdfe0fee+199202a608,20.0.0-5-gfbfe500+d529cf1a41,20.0.0-6-g64f541c+d529cf1a41,20.0.0-6-g9a5b7a1+a1cd37312e,20.0.0-68-ga3f3dda+5fca18c6a4,20.0.0-9-g4aef684+e18322736b,w.2020.45
LSSTDataManagementBasePackage
brightStarStamps.py
Go to the documentation of this file.
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 """
24 
25 __all__ = ["BrightStarStamp", "BrightStarStamps"]
26 
27 import collections.abc
28 from typing import NamedTuple
29 from enum import Enum, auto
30 
31 import lsst.afw.image as afwImage
32 import lsst.afw.fits as afwFits
33 from lsst.geom import Box2I, Point2I, Extent2I
34 from lsst.daf.base import PropertySet
35 
36 
37 class RadiiEnum(Enum):
38  INNER_RADIUS = auto()
39  OUTER_RADIUS = auto()
40 
41  def __str__(self):
42  return self.name
43 
44 
45 class BrightStarStamp(NamedTuple):
46  """Single stamp centered on a bright star, normalized by its
47  annularFlux.
48  """
49  starStamp: afwImage.maskedImage.MaskedImageF
50  gaiaGMag: float
51  gaiaId: int
52  annularFlux: float
53 
54 
55 class BrightStarStamps(collections.abc.Sequence):
56  """Collection of bright star stamps and associated metadata.
57 
58  Parameters
59  ----------
60  starStamps : `collections.abc.Sequence` [`BrightStarStamp`]
61  Sequence of star stamps.
62  innerRadius : `int`, optional
63  Inner radius value, in pixels. This and ``outerRadius`` define the
64  annulus used to compute the ``"annularFlux"`` values within each
65  ``starStamp``. Must be provided if ``"INNER_RADIUS"`` and
66  ``"OUTER_RADIUS"`` are not present in ``metadata``.
67  outerRadius : `int`, optional
68  Outer radius value, in pixels. This and ``innerRadius`` define the
69  annulus used to compute the ``"annularFlux"`` values within each
70  ``starStamp``. Must be provided if ``"INNER_RADIUS"`` and
71  ``"OUTER_RADIUS"`` are not present in ``metadata``.
72  metadata : `lsst.daf.base.PropertyList`, optional
73  Metadata associated with the bright stars.
74 
75  Raises
76  ------
77  ValueError
78  Raised if one of the star stamps provided does not contain the
79  required keys.
80  AttributeError
81  Raised if the definition of the annulus used to compute each star's
82  normalization factor are not provided, that is, if ``"INNER_RADIUS"``
83  and ``"OUTER_RADIUS"`` are not present in ``metadata`` _and_
84  ``innerRadius`` and ``outerRadius`` are not provided.
85 
86  Notes
87  -----
88  A (gen2) butler can be used to read only a part of the stamps,
89  specified by a bbox:
90 
91  >>> starSubregions = butler.get("brightStarStamps_sub", dataId, bbox=bbox)
92  """
93 
94  def __init__(self, starStamps, innerRadius=None, outerRadius=None,
95  metadata=None,):
96  for item in starStamps:
97  if not isinstance(item, BrightStarStamp):
98  raise ValueError(f"Can only add instances of BrightStarStamp, got {type(item)}")
99  self._starStamps = starStamps
100  self._metadata = PropertySet() if metadata is None else metadata.deepCopy()
101  # Add inner and outer radii to metadata
102  self._checkRadius(innerRadius, RadiiEnum.INNER_RADIUS)
103  self._innerRadius = innerRadius
104  self._checkRadius(outerRadius, RadiiEnum.OUTER_RADIUS)
105  self._outerRadius = outerRadius
106 
107  def __len__(self):
108  return len(self._starStamps)
109 
110  def __getitem__(self, index):
111  return self._starStamps[index]
112 
113  def __iter__(self):
114  return iter(self._starStamps)
115 
116  def append(self, item, innerRadius, outerRadius):
117  """Add an additional bright star stamp.
118 
119  Parameters
120  ----------
121  item : `BrightStarStamp`
122  Bright star stamp to append.
123  innerRadius : `int`
124  Inner radius value, in4 pixels. This and ``outerRadius`` define the
125  annulus used to compute the ``"annularFlux"`` values within each
126  ``starStamp``.
127  outerRadius : `int`, optional
128  Outer radius value, in pixels. This and ``innerRadius`` define the
129  annulus used to compute the ``"annularFlux"`` values within each
130  ``starStamp``.
131  """
132  if not isinstance(item, BrightStarStamp):
133  raise ValueError(f"Can only add instances of BrightStarStamp, got {type(item)}.")
134  self._checkRadius(innerRadius, RadiiEnum.INNER_RADIUS)
135  self._checkRadius(outerRadius, RadiiEnum.OUTER_RADIUS)
136  self._starStamps.append(item)
137  return None
138 
139  def extend(self, bss):
140  """Extend BrightStarStamps instance by appending elements from another
141  instance.
142 
143  Parameters
144  ----------
145  bss : `BrightStarStamps`
146  Other instance to concatenate.
147  """
148  self._checkRadius(bss._innerRadius, RadiiEnum.INNER_RADIUS)
149  self._checkRadius(bss._outerRadius, RadiiEnum.OUTER_RADIUS)
150  self._starStamps += bss._starStamps
151 
152  def getMaskedImages(self):
153  """Retrieve star images.
154 
155  Returns
156  -------
157  maskedImages :
158  `list` [`lsst.afw.image.maskedImage.maskedImage.MaskedImageF`]
159  """
160  return [stamp.starStamp for stamp in self._starStamps]
161 
162  def getMagnitudes(self):
163  """Retrieve Gaia G magnitudes for each star.
164 
165  Returns
166  -------
167  gaiaGMags : `list` [`float`]
168  """
169  return [stamp.gaiaGMag for stamp in self._starStamps]
170 
171  def getGaiaIds(self):
172  """Retrieve Gaia IDs for each star.
173 
174  Returns
175  -------
176  gaiaIds : `list` [`int`]
177  """
178  return [stamp.gaiaId for stamp in self._starStamps]
179 
180  def getAnnularFluxes(self):
181  """Retrieve normalization factors for each star.
182 
183  These are computed by integrating the flux in annulus centered on the
184  bright star, far enough from center to be beyond most severe ghosts and
185  saturation. The inner and outer radii that define the annulus can be
186  recovered from the metadata.
187 
188  Returns
189  -------
190  annularFluxes : list[`float`]
191  """
192  return [stamp.annularFlux for stamp in self._starStamps]
193 
194  def selectByMag(self, magMin=None, magMax=None):
195  """Return the subset of bright star stamps for objects with specified
196  magnitude cuts (in Gaia G).
197 
198  Parameters
199  ----------
200  magMin : `float`, optional
201  Keep only stars fainter than this value.
202  magMax : `float`, optional
203  Keep only stars brighter than this value.
204  """
205  subset = [stamp for stamp in self._starStamps
206  if (magMin is None or stamp.gaiaGMag > magMin)
207  and (magMax is None or stamp.gaiaGMag < magMax)]
208  # This is an optimization to save looping over the init argument when
209  # it is already guaranteed to be the correct type
210  instance = BrightStarStamps((), metadata=self._metadata)
211  instance._starStamps = subset
212  return instance
213 
214  @property
215  def metadata(self):
216  return self._metadata.deepCopy()
217 
218  def _checkRadius(self, radiusValue, metadataEnum):
219  """Ensure provided annulus radius is consistent with that present
220  in metadata. If metadata does not contain annulus radius, add it.
221  """
222  # if a radius value is already present in metadata, ensure it matches
223  # the one given
224  metadataName = str(metadataEnum)
225  if self._metadata.exists(metadataName):
226  if radiusValue is not None:
227  if self._metadata[metadataName] != radiusValue:
228  raise AttributeError("BrightStarStamps instance already contains different annulus radii "
229  + f"values ({metadataName}).")
230  # if not already in metadata, a value must be provided
231  elif radiusValue is None:
232  raise AttributeError("No radius value provided for the AnnularFlux measurement "
233  + f"({metadataName}), and none present in metadata.")
234  else:
235  self._metadata[metadataName] = radiusValue
236  return None
237 
238  def writeFits(self, filename):
239  """Write a single FITS file containing all bright star stamps.
240  """
241  # ensure metadata contains current number of objects
242  self._metadata["N_STARS"] = len(self)
243 
244  # add full list of Gaia magnitudes, IDs and annularFlxes to shared
245  # metadata
246  self._metadata["G_MAGS"] = self.getMagnitudes()
247  self._metadata["GAIA_IDS"] = self.getGaiaIds()
248  self._metadata["ANNULAR_FLUXES"] = self.getAnnularFluxes()
249 
250  # create primary HDU with global metadata
251  fitsPrimary = afwFits.Fits(filename, "w")
252  fitsPrimary.createEmpty()
253  fitsPrimary.writeMetadata(self._metadata)
254  fitsPrimary.closeFile()
255 
256  # add all stamps and mask planes
257  for stamp in self.getMaskedImages():
258  stamp.getImage().writeFits(filename, mode='a')
259  stamp.getMask().writeFits(filename, mode='a')
260  return None
261 
262  @classmethod
263  def readFits(cls, filename):
264  """Read bright star stamps from FITS file.
265 
266  Returns
267  -------
268  bss : `BrightStarStamps`
269  Collection of bright star stamps.
270  """
271  bss = cls.readFitsWithOptions(filename, None)
272  return bss
273 
274  @classmethod
275  def readFitsWithOptions(cls, filename, options):
276  """Read bright star stamps from FITS file, allowing for only a
277  subregion of the stamps to be read.
278 
279  Returns
280  -------
281  bss : `BrightStarStamps`
282  Collection of bright star stamps.
283  """
284  # extract necessary info from metadata
285  visitMetadata = afwFits.readMetadata(filename, hdu=0)
286  nbStarStamps = visitMetadata["N_STARS"]
287  gaiaGMags = visitMetadata.getArray("G_MAGS")
288  gaiaIds = visitMetadata.getArray("GAIA_IDS")
289  annularFluxes = visitMetadata.getArray("ANNULAR_FLUXES")
290  # check if a bbox was provided
291  kwargs = {}
292  if options and options.exists("llcX"):
293  llcX = options["llcX"]
294  llcY = options["llcY"]
295  width = options["width"]
296  height = options["height"]
297  bbox = Box2I(Point2I(llcX, llcY), Extent2I(width, height))
298  kwargs["bbox"] = bbox
299  # read stamps themselves
300  starStamps = []
301  for bStarIdx in range(nbStarStamps):
302  imReader = afwImage.ImageFitsReader(filename, hdu=2*bStarIdx + 1)
303  maskReader = afwImage.MaskFitsReader(filename, hdu=2*(bStarIdx + 1))
304  maskedImage = afwImage.MaskedImageF(image=imReader.read(**kwargs),
305  mask=maskReader.read(**kwargs))
306  starStamps.append(BrightStarStamp(starStamp=maskedImage,
307  gaiaGMag=gaiaGMags[bStarIdx],
308  gaiaId=gaiaIds[bStarIdx],
309  annularFlux=annularFluxes[bStarIdx]))
310  bss = cls(starStamps, metadata=visitMetadata)
311  return bss
lsst::meas::algorithms.brightStarStamps.RadiiEnum.__str__
def __str__(self)
Definition: brightStarStamps.py:41
lsst::meas::algorithms.brightStarStamps.BrightStarStamps
Definition: brightStarStamps.py:55
lsst::afw::image::MaskFitsReader
A FITS reader class for Masks.
Definition: MaskFitsReader.h:39
lsst::afw::image
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
Definition: imageAlgorithm.dox:1
lsst::meas::algorithms.brightStarStamps.BrightStarStamps.extend
def extend(self, bss)
Definition: brightStarStamps.py:139
lsst::meas::algorithms.brightStarStamps.BrightStarStamps.__len__
def __len__(self)
Definition: brightStarStamps.py:107
lsst::meas::algorithms.brightStarStamps.BrightStarStamps.__getitem__
def __getitem__(self, index)
Definition: brightStarStamps.py:110
lsst::meas::algorithms.brightStarStamps.BrightStarStamps.getMagnitudes
def getMagnitudes(self)
Definition: brightStarStamps.py:162
lsst::meas::algorithms.brightStarStamps.RadiiEnum
Definition: brightStarStamps.py:37
lsst::meas::algorithms.brightStarStamps.BrightStarStamps.append
def append(self, item, innerRadius, outerRadius)
Definition: brightStarStamps.py:116
lsst::meas::algorithms.brightStarStamps.BrightStarStamps.writeFits
def writeFits(self, filename)
Definition: brightStarStamps.py:238
lsst::meas::algorithms.brightStarStamps.BrightStarStamp
Definition: brightStarStamps.py:45
lsst::meas::algorithms.brightStarStamps.BrightStarStamps._innerRadius
_innerRadius
Definition: brightStarStamps.py:102
lsst::afw::image::ImageFitsReader
A FITS reader class for regular Images.
Definition: ImageFitsReader.h:39
lsst::afw::geom.transform.transformContinued.cls
cls
Definition: transformContinued.py:33
lsst::meas::algorithms.brightStarStamps.BrightStarStamps._metadata
_metadata
Definition: brightStarStamps.py:99
lsst::meas::algorithms.brightStarStamps.BrightStarStamps.metadata
def metadata(self)
Definition: brightStarStamps.py:215
lsst::meas::algorithms.brightStarStamps.BrightStarStamps._starStamps
_starStamps
Definition: brightStarStamps.py:98
lsst::meas::algorithms.brightStarStamps.BrightStarStamps.getAnnularFluxes
def getAnnularFluxes(self)
Definition: brightStarStamps.py:180
lsst::meas::algorithms.brightStarStamps.BrightStarStamps.selectByMag
def selectByMag(self, magMin=None, magMax=None)
Definition: brightStarStamps.py:194
lsst::meas::algorithms.brightStarStamps.BrightStarStamps.readFitsWithOptions
def readFitsWithOptions(cls, filename, options)
Definition: brightStarStamps.py:275
lsst::meas::algorithms.brightStarStamps.BrightStarStamps._outerRadius
_outerRadius
Definition: brightStarStamps.py:104
lsst::geom
Definition: AffineTransform.h:36
lsst::meas::algorithms.brightStarStamps.BrightStarStamps._checkRadius
def _checkRadius(self, radiusValue, metadataEnum)
Definition: brightStarStamps.py:218
lsst::daf::base
Definition: Utils.h:47
lsst::meas::algorithms.brightStarStamps.BrightStarStamps.getMaskedImages
def getMaskedImages(self)
Definition: brightStarStamps.py:152
lsst::afw::fits
Definition: fits.h:31
lsst::meas::algorithms.brightStarStamps.BrightStarStamps.__init__
def __init__(self, starStamps, innerRadius=None, outerRadius=None, metadata=None)
Definition: brightStarStamps.py:94
lsst::geom::Extent2I
Extent< int, 2 > Extent2I
Definition: Extent.h:397
lsst::meas::algorithms.brightStarStamps.BrightStarStamps.__iter__
def __iter__(self)
Definition: brightStarStamps.py:113
lsst::daf::base::PropertySet
Class for storing generic metadata.
Definition: PropertySet.h:67
lsst::geom::Point2I
Point< int, 2 > Point2I
Definition: Point.h:321
lsst::geom::Box2I
An integer coordinate rectangle.
Definition: Box.h:55
lsst::meas::algorithms.brightStarStamps.BrightStarStamps.readFits
def readFits(cls, filename)
Definition: brightStarStamps.py:263
lsst::meas::algorithms.brightStarStamps.BrightStarStamps.getGaiaIds
def getGaiaIds(self)
Definition: brightStarStamps.py:171
astshim.fitsChanContinued.iter
def iter(self)
Definition: fitsChanContinued.py:88