22"""Collection of small images (stamps)."""
24__all__ = [
"Stamp",
"Stamps",
"StampsBase",
"writeFits",
"readFitsWithOptions"]
27from collections.abc
import Sequence
28from dataclasses
import dataclass
32from lsst.afw.image import ImageFitsReader, MaskedImageF, MaskFitsReader
35from lsst.geom import Angle, Box2I, Extent2I, Point2I, SpherePoint, degrees
36from lsst.utils
import doImport
37from lsst.utils.introspection
import get_full_type_name
40def writeFits(filename, stamps, metadata, type_name, write_mask, write_variance, write_archive=False):
41 """Write a single FITS file containing all stamps.
46 A string indicating the output filename
47 stamps : iterable of `BaseStamp`
48 An iterable of Stamp objects
49 metadata : `PropertyList`
50 A collection of key, value metadata pairs to be
51 written to the primary header
53 Python type name of the StampsBase subclass to use
55 Write the mask data to the output file?
56 write_variance : `bool`
57 Write the variance data to the output file?
58 write_archive : `bool`, optional
59 Write an archive to store Persistables along with each stamp?
62 metadata["HAS_MASK"] = write_mask
63 metadata[
"HAS_VARIANCE"] = write_variance
64 metadata[
"HAS_ARCHIVE"] = write_archive
65 metadata[
"N_STAMPS"] = len(stamps)
66 metadata[
"STAMPCLS"] = type_name
68 metadata[
"VERSION"] = 1
70 fitsFile =
Fits(filename,
"w")
71 fitsFile.createEmpty()
75 archive_ids = [oa.put(stamp.archive_element)
for stamp
in stamps]
76 metadata[
"ARCHIVE_IDS"] = archive_ids
77 fitsFile.writeMetadata(metadata)
78 oa.writeFits(fitsFile)
80 fitsFile.writeMetadata(metadata)
83 for i, stamp
in enumerate(stamps):
86 metadata.update({
"EXTVER": i + 1,
"EXTNAME":
"IMAGE"})
87 stamp.stamp_im.getImage().
writeFits(filename, metadata=metadata, mode=
"a")
90 metadata.update({
"EXTVER": i + 1,
"EXTNAME":
"MASK"})
91 stamp.stamp_im.getMask().
writeFits(filename, metadata=metadata, mode=
"a")
94 metadata.update({
"EXTVER": i + 1,
"EXTNAME":
"VARIANCE"})
95 stamp.stamp_im.getVariance().
writeFits(filename, metadata=metadata, mode=
"a")
99def readFitsWithOptions(filename, stamp_factory, options):
100 """Read stamps from FITS file, allowing for only a subregion of the stamps
106 A string indicating the file to read
107 stamp_factory : classmethod
108 A factory function defined on a dataclass for constructing
109 stamp objects a la `~lsst.meas.alrogithm.Stamp`
110 options : `PropertyList`
or `dict`
111 A collection of parameters. If it contains a bounding box
112 (``bbox`` key),
or if certain other keys (``llcX``, ``llcY``,
113 ``width``, ``height``) are available
for one to be constructed,
114 the bounding box
is passed to the ``FitsReader``
in order to
119 stamps : `list` of dataclass objects like `Stamp`, PropertyList
120 A tuple of a list of `Stamp`-like objects
121 metadata : `PropertyList`
125 metadata = readMetadata(filename, hdu=0)
126 nStamps = metadata[
"N_STAMPS"]
127 has_archive = metadata[
"HAS_ARCHIVE"]
129 archive_ids = metadata.getArray(
"ARCHIVE_IDS")
130 with Fits(filename,
"r")
as f:
131 nExtensions = f.countHdus()
136 if "bbox" in options.keys():
137 kwargs[
"bbox"] = options[
"bbox"]
139 elif "llcX" in options.keys():
140 llcX = options[
"llcX"]
141 llcY = options[
"llcY"]
142 width = options[
"width"]
143 height = options[
"height"]
144 bbox =
Box2I(Point2I(llcX, llcY), Extent2I(width, height))
145 kwargs[
"bbox"] = bbox
148 for idx
in range(nExtensions - 1):
149 md = readMetadata(filename, hdu=idx + 1)
150 if md[
"EXTNAME"]
in (
"IMAGE",
"VARIANCE"):
152 elif md[
"EXTNAME"] ==
"MASK":
154 elif md[
"EXTNAME"] ==
"ARCHIVE_INDEX":
156 archive = InputArchive.readFits(f)
158 elif md[
"EXTTYPE"] ==
"ARCHIVE_DATA":
161 raise ValueError(f
"Unknown extension type: {md['EXTNAME']}")
162 stamp_parts.setdefault(md[
"EXTVER"], {})[md[
"EXTNAME"].lower()] = reader.read(**kwargs)
163 if len(stamp_parts) != nStamps:
165 f
"Number of stamps read ({len(stamp_parts)}) does not agree with the "
166 f
"number of stamps recorded in the metadata ({nStamps})."
170 for k
in range(nStamps):
172 maskedImage = MaskedImageF(**stamp_parts[k + 1])
173 archive_element = archive.get(archive_ids[k])
if has_archive
else None
174 stamps.append(stamp_factory(maskedImage, metadata, k, archive_element))
176 return stamps, metadata
181 """Single abstract stamp.
185 Inherit from this
class to add metadata to the stamp.
190 def factory(cls, stamp_im, metadata, index, archive_element=None):
191 """This method is needed to service the FITS reader. We need a standard
192 interface to construct objects like this. Parameters needed to
193 construct this object are passed in via a metadata dictionary
and then
194 passed to the constructor of this
class.
199 Pixel data to
pass to the constructor
201 Dictionary containing the information
202 needed by the constructor.
204 Index into the lists
in ``metadata``
206 Archive element (e.g. Transform
or WCS) associated
with this stamp.
210 stamp : `AbstractStamp`
211 An instance of this
class
213 raise NotImplementedError
222 stamp_im : `~lsst.afw.image.MaskedImageF`
223 The actual pixel values for the postage stamp.
225 Archive element (e.g. Transform
or WCS) associated
with this stamp.
227 Position of the center of the stamp. Note the user must keep track of
228 the coordinate system.
231 stamp_im: MaskedImageF
232 archive_element: Persistable | None =
None
236 def factory(cls, stamp_im, metadata, index, archive_element=None):
237 """This method is needed to service the FITS reader. We need a standard
238 interface to construct objects like this. Parameters needed to
239 construct this object are passed in via a metadata dictionary
and then
240 passed to the constructor of this
class. If lists of values are passed
241 with the following keys, they will be passed to the constructor,
242 otherwise dummy values will be passed: RA_DEG, DEC_DEG. They should
243 each point to lists of values.
248 Pixel data to
pass to the constructor
250 Dictionary containing the information
251 needed by the constructor.
253 Index into the lists
in ``metadata``
255 Archive element (e.g. Transform
or WCS) associated
with this stamp.
260 An instance of this
class
262 if "RA_DEG" in metadata
and "DEC_DEG" in metadata:
265 archive_element=archive_element,
267 Angle(metadata.getArray(
"RA_DEG")[index], degrees),
268 Angle(metadata.getArray(
"DEC_DEG")[index], degrees),
274 archive_element=archive_element,
280 """Collection of stamps and associated metadata.
285 This should be an iterable of dataclass objects
288 Metadata associated with the objects within the stamps.
289 use_mask : `bool`, optional
290 If ``
True`` read
and write the mask data. Default ``
True``.
291 use_variance : `bool`, optional
292 If ``
True`` read
and write the variance data. Default ``
True``.
293 use_archive : `bool`, optional
294 If ``
True``, read
and write an Archive that contains a Persistable
295 associated
with each stamp,
for example a Transform
or a WCS.
300 A butler can be used to read only a part of the stamps,
303 >>> starSubregions = butler.get(
306 parameters={
"bbox": bbox}
310 def __init__(self, stamps, metadata=None, use_mask=True, use_variance=True, use_archive=False):
312 if not isinstance(stamp, AbstractStamp):
313 raise ValueError(f
"The entries in stamps must inherit from AbstractStamp. Got {type(stamp)}.")
322 """Build an instance of this class from a file.
327 Name of the file to read
333 def readFitsWithOptions(cls, filename, options):
334 """Build an instance of this class with options.
339 Name of the file to read
340 options : `PropertyList`
341 Collection of metadata parameters
348 if cls
is not StampsBase:
349 raise NotImplementedError(f
"Please implement specific FITS reader for class {cls}")
352 metadata = readMetadata(filename, hdu=0)
353 type_name = metadata.get(
"STAMPCLS")
354 if type_name
is None:
356 f
"No class name in file {filename}. Unable to instantiate correct stamps subclass. "
357 "Is this an old version format Stamps file?"
361 stamp_type = doImport(type_name)
367 def _refresh_metadata(self):
368 """Make sure metadata is up to date, as this object can be extended."""
369 raise NotImplementedError
372 """Write this object to a file.
377 Name of file to write.
380 type_name = get_full_type_name(self)
401 """Retrieve star images.
406 `list` [`~lsst.afw.image.MaskedImageF`]
408 return [stamp.stamp_im
for stamp
in self.
_stamps]
411 """Retrieve archive elements associated with each stamp.
418 return [stamp.archive_element
for stamp
in self.
_stamps]
426 def _refresh_metadata(self):
428 self.
_metadata[
"RA_DEG"] = [p.getRa().asDegrees()
for p
in positions]
429 self.
_metadata[
"DEC_DEG"] = [p.getDec().asDegrees()
for p
in positions]
432 return [s.position
for s
in self.
_stamps]
435 """Add an additional stamp.
440 Stamp object to append.
442 if not isinstance(item, Stamp):
443 raise ValueError(
"Objects added must be a Stamp object.")
448 """Extend Stamps instance by appending elements from another instance.
452 stamps_list : `list` [`Stamp`]
453 List of Stamp object to append.
456 if not isinstance(s, Stamp):
457 raise ValueError(
"Can only extend with Stamp objects")
462 """Build an instance of this class from a file.
467 Name of the file to read.
472 An instance of this class.
477 def readFitsWithOptions(cls, filename, options):
478 """Build an instance of this class with options.
483 Name of the file to read.
484 options : `PropertyList` or `dict`
485 Collection of metadata parameters.
490 An instance of this
class.
492 stamps, metadata = readFitsWithOptions(filename, Stamp.factory, options)
496 use_mask=metadata[
"HAS_MASK"],
497 use_variance=metadata[
"HAS_VARIANCE"],
498 use_archive=metadata[
"HAS_ARCHIVE"],
def factory(cls, stamp_im, metadata, index, archive_element=None)
def factory(cls, stamp_im, metadata, index, archive_element=None)
def getArchiveElements(self)
def readFits(cls, filename)
def __getitem__(self, index)
def writeFits(self, filename)
def _refresh_metadata(self)
def readFitsWithOptions(cls, filename, options)
def getMaskedImages(self)
def __init__(self, stamps, metadata=None, use_mask=True, use_variance=True, use_archive=False)
def extend(self, stamp_list)
def readFits(cls, filename)
def readFitsWithOptions(cls, filename, options)
def writeFits(filename, stamps, metadata, type_name, write_mask, write_variance, write_archive=False)