22"""Collection of small images (stamps)."""
24__all__ = [
"Stamp",
"Stamps",
"StampsBase",
"writeFits",
"readFitsWithOptions"]
28from dataclasses
import dataclass, field, fields
32from lsst.afw.image import ImageFitsReader, MaskedImage, MaskedImageF, MaskFitsReader
35from lsst.geom import Angle, Box2I, Extent2I, Point2I, SpherePoint, degrees
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.algorithm.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`
126 The data are read using the data type expected by the
128 dataclass associated
with the factory method.
131 metadata = readMetadata(filename, hdu=0)
132 nStamps = metadata[
"N_STAMPS"]
133 has_archive = metadata[
"HAS_ARCHIVE"]
135 archive_ids = metadata.getArray(
"ARCHIVE_IDS")
136 with Fits(filename,
"r")
as f:
137 nExtensions = f.countHdus()
142 if "bbox" in options.keys():
143 kwargs[
"bbox"] = options[
"bbox"]
145 elif "llcX" in options.keys():
146 llcX = options[
"llcX"]
147 llcY = options[
"llcY"]
148 width = options[
"width"]
149 height = options[
"height"]
150 bbox =
Box2I(Point2I(llcX, llcY), Extent2I(width, height))
151 kwargs[
"bbox"] = bbox
157 masked_image_cls =
None
158 for stamp_field
in fields(stamp_factory.__self__):
159 if issubclass(stamp_field.type, MaskedImage):
160 masked_image_cls = stamp_field.type
163 raise RuntimeError(
"Stamp factory does not use MaskedImage.")
164 default_dtype = np.dtype(masked_image_cls.dtype)
165 variance_dtype = np.dtype(np.float32)
168 for idx
in range(nExtensions - 1):
170 md = readMetadata(filename, hdu=idx + 1)
171 if md[
"EXTNAME"]
in (
"IMAGE",
"VARIANCE"):
173 if md[
"EXTNAME"] ==
"VARIANCE":
174 dtype = variance_dtype
176 dtype = default_dtype
177 elif md[
"EXTNAME"] ==
"MASK":
179 elif md[
"EXTNAME"] ==
"ARCHIVE_INDEX":
181 archive = InputArchive.readFits(f)
183 elif md[
"EXTTYPE"] ==
"ARCHIVE_DATA":
186 raise ValueError(f
"Unknown extension type: {md['EXTNAME']}")
187 stamp_parts.setdefault(md[
"EXTVER"], {})[md[
"EXTNAME"].lower()] = reader.read(dtype=dtype,
189 if len(stamp_parts) != nStamps:
191 f
"Number of stamps read ({len(stamp_parts)}) does not agree with the "
192 f
"number of stamps recorded in the metadata ({nStamps})."
196 for k
in range(nStamps):
198 maskedImage = masked_image_cls(**stamp_parts[k + 1])
199 archive_element = archive.get(archive_ids[k])
if has_archive
else None
200 stamps.append(stamp_factory(maskedImage, metadata, k, archive_element))
202 return stamps, metadata
207 """Single abstract stamp.
211 Inherit from this
class to add metadata
to the stamp.
216 def factory(cls, stamp_im, metadata, index, archive_element=None):
217 """This method is needed to service the FITS reader. We need a standard
218 interface to construct objects like this. Parameters needed to
219 construct this object are passed in via a metadata dictionary
and then
220 passed to the constructor of this
class.
225 Pixel data to
pass to the constructor
227 Dictionary containing the information
228 needed by the constructor.
230 Index into the lists
in ``metadata``
232 Archive element (e.g. Transform
or WCS) associated
with this stamp.
236 stamp : `AbstractStamp`
237 An instance of this
class
239 raise NotImplementedError
254 stamp_im : `~lsst.afw.image.MaskedImageF`
255 The actual pixel values for the postage stamp.
257 Archive element (e.g. Transform
or WCS) associated
with this stamp.
259 Position of the center of the stamp. Note the user must keep track of
260 the coordinate system.
263 stamp_im: MaskedImageF
264 archive_element: Persistable | None =
None
265 position: SpherePoint |
None = field(default_factory=_default_position)
268 def factory(cls, stamp_im, metadata, index, archive_element=None):
269 """This method is needed to service the FITS reader. We need a standard
270 interface to construct objects like this. Parameters needed to
271 construct this object are passed in via a metadata dictionary
and then
272 passed to the constructor of this
class. If lists of values are passed
273 with the following keys, they will be passed to the constructor,
274 otherwise dummy values will be passed: RA_DEG, DEC_DEG. They should
275 each point to lists of values.
280 Pixel data to
pass to the constructor
282 Dictionary containing the information
283 needed by the constructor.
285 Index into the lists
in ``metadata``
287 Archive element (e.g. Transform
or WCS) associated
with this stamp.
292 An instance of this
class
294 if "RA_DEG" in metadata
and "DEC_DEG" in metadata:
297 archive_element=archive_element,
299 Angle(metadata.getArray(
"RA_DEG")[index], degrees),
300 Angle(metadata.getArray(
"DEC_DEG")[index], degrees),
306 archive_element=archive_element,
312 """Collection of stamps and associated metadata.
317 This should be an iterable of dataclass objects
320 Metadata associated with the objects within the stamps.
321 use_mask : `bool`, optional
322 If ``
True`` read
and write the mask data. Default ``
True``.
323 use_variance : `bool`, optional
324 If ``
True`` read
and write the variance data. Default ``
True``.
325 use_archive : `bool`, optional
326 If ``
True``, read
and write an Archive that contains a Persistable
327 associated
with each stamp,
for example a Transform
or a WCS.
332 A butler can be used to read only a part of the stamps,
335 >>> starSubregions = butler.get(
338 parameters={
"bbox": bbox}
342 def __init__(self, stamps, metadata=None, use_mask=True, use_variance=True, use_archive=False):
344 if not isinstance(stamp, AbstractStamp):
345 raise ValueError(f
"The entries in stamps must inherit from AbstractStamp. Got {type(stamp)}.")
354 """Build an instance of this class from a file.
359 Name of the file to read
365 def readFitsWithOptions(cls, filename, options):
366 """Build an instance of this class with options.
371 Name of the file to read
372 options : `PropertyList`
373 Collection of metadata parameters
380 if cls
is not StampsBase:
381 raise NotImplementedError(f
"Please implement specific FITS reader for class {cls}")
384 metadata = readMetadata(filename, hdu=0)
385 type_name = metadata.get(
"STAMPCLS")
386 if type_name
is None:
388 f
"No class name in file {filename}. Unable to instantiate correct stamps subclass. "
389 "Is this an old version format Stamps file?"
393 stamp_type = doImport(type_name)
400 """Make sure metadata is up to date, as this object can be extended."""
401 raise NotImplementedError
404 """Write this object to a file.
409 Name of file to write.
412 type_name = get_full_type_name(self)
433 """Retrieve star images.
438 `list` [`~lsst.afw.image.MaskedImageF`]
440 return [stamp.stamp_im
for stamp
in self.
_stamps]
443 """Retrieve archive elements associated with each stamp.
450 return [stamp.archive_element
for stamp
in self.
_stamps]
460 self.
_metadata[
"RA_DEG"] = [p.getRa().asDegrees()
for p
in positions]
461 self.
_metadata[
"DEC_DEG"] = [p.getDec().asDegrees()
for p
in positions]
464 return [s.position
for s
in self.
_stamps]
467 """Add an additional stamp.
472 Stamp object to append.
474 if not isinstance(item, Stamp):
475 raise ValueError(
"Objects added must be a Stamp object.")
480 """Extend Stamps instance by appending elements from another instance.
484 stamps_list : `list` [`Stamp`]
485 List of Stamp object to append.
488 if not isinstance(s, Stamp):
489 raise ValueError(
"Can only extend with Stamp objects")
494 """Build an instance of this class from a file.
499 Name of the file to read.
504 An instance of this class.
509 def readFitsWithOptions(cls, filename, options):
510 """Build an instance of this class with options.
515 Name of the file to read.
516 options : `PropertyList` or `dict`
517 Collection of metadata parameters.
522 An instance of this
class.
524 stamps, metadata = readFitsWithOptions(filename, Stamp.factory, options)
528 use_mask=metadata[
"HAS_MASK"],
529 use_variance=metadata[
"HAS_VARIANCE"],
530 use_archive=metadata[
"HAS_ARCHIVE"],
A simple struct that combines the two arguments that must be passed to most cfitsio routines and cont...
A FITS reader class for regular Images.
A FITS reader class for Masks.
A class to manipulate images, masks, and variance as a single object.
A multi-catalog archive object used to save table::io::Persistable objects.
A base class for objects that can be persisted via afw::table::io Archive classes.
Class for storing ordered metadata with comments.
A class representing an angle.
An integer coordinate rectangle.
Point in an unspecified spherical coordinate system.
factory(cls, stamp_im, metadata, index, archive_element=None)
factory(cls, stamp_im, metadata, index, archive_element=None)
__init__(self, stamps, metadata=None, use_mask=True, use_variance=True, use_archive=False)
readFitsWithOptions(cls, filename, options)
writeFits(self, filename)
readFitsWithOptions(cls, filename, options)
writeFits(filename, stamps, metadata, type_name, write_mask, write_variance, write_archive=False)