100def readFitsWithOptions(filename, stamp_factory, options):
101 """Read stamps from FITS file, allowing for only a subregion of the stamps
102 to be read.
103
104 Parameters
105 ----------
106 filename : `str`
107 A string indicating the file to read
108 stamp_factory : classmethod
109 A factory function defined on a dataclass for constructing
110 stamp objects a la `~lsst.meas.algorithm.Stamp`
111 options : `PropertyList` or `dict`
112 A collection of parameters. If it contains a bounding box
113 (``bbox`` key), or if certain other keys (``llcX``, ``llcY``,
114 ``width``, ``height``) are available for one to be constructed,
115 the bounding box is passed to the ``FitsReader`` in order to
116 return a sub-image.
117
118 Returns
119 -------
120 stamps : `list` of dataclass objects like `Stamp`, PropertyList
121 A tuple of a list of `Stamp`-like objects
122 metadata : `PropertyList`
123 The metadata
124
125 Notes
126 -----
127 The data are read using the data type expected by the
128 `~lsst.afw.image.MaskedImage` class attached to the `AbstractStamp`
129 dataclass associated with the factory method.
130 """
131
132 metadata = readMetadata(filename, hdu=0)
133 nStamps = metadata["N_STAMPS"]
134 has_archive = metadata["HAS_ARCHIVE"]
135 if has_archive:
136 archive_ids = metadata.getArray("ARCHIVE_IDS")
137 with Fits(filename, "r") as f:
138 nExtensions = f.countHdus()
139
140 kwargs = {}
141 if options:
142
143 if "bbox" in options.keys():
144 kwargs["bbox"] = options["bbox"]
145
146 elif "llcX" in options.keys():
147 llcX = options["llcX"]
148 llcY = options["llcY"]
149 width = options["width"]
150 height = options["height"]
151 bbox = Box2I(Point2I(llcX, llcY), Extent2I(width, height))
152 kwargs["bbox"] = bbox
153 stamp_parts = {}
154
155
156
157
158 masked_image_cls = None
159 for stamp_field in fields(stamp_factory.__self__):
160 if issubclass(stamp_field.type, MaskedImage):
161 masked_image_cls = stamp_field.type
162 break
163 else:
164 raise RuntimeError("Stamp factory does not use MaskedImage.")
165 default_dtype = np.dtype(masked_image_cls.dtype)
166 variance_dtype = np.dtype(np.float32)
167
168
169 stamp_metadata = []
170 for idx in range(nExtensions - 1):
171 dtype = None
172 _metadata = copy.copy(metadata)
173 md = readMetadata(filename, hdu=idx + 1)
174 _metadata.update(md)
175 stamp_metadata.append(_metadata)
176
177 if md["XTENSION"] == "BINTABLE" and not ("ZIMAGE" in md and md["ZIMAGE"]):
178 if md["EXTNAME"] != "ARCHIVE_INDEX":
179 continue
180 if md["EXTNAME"] in ("IMAGE", "VARIANCE"):
181 reader = ImageFitsReader(filename, hdu=idx + 1)
182 if md["EXTNAME"] == "VARIANCE":
183 dtype = variance_dtype
184 else:
185 dtype = default_dtype
186 elif md["EXTNAME"] == "MASK":
187 reader = MaskFitsReader(filename, hdu=idx + 1)
188 elif md["EXTNAME"] == "ARCHIVE_INDEX":
189 f.setHdu(idx + 1)
190 archive = InputArchive.readFits(f)
191 continue
192 elif md["EXTTYPE"] == "ARCHIVE_DATA":
193 continue
194 else:
195 raise ValueError(f"Unknown extension type: {md['EXTNAME']}")
196 stamp_parts.setdefault(md["EXTVER"], {})[md["EXTNAME"].lower()] = reader.read(dtype=dtype,
197 **kwargs)
198 if len(stamp_parts) != nStamps:
199 raise ValueError(
200 f"Number of stamps read ({len(stamp_parts)}) does not agree with the "
201 f"number of stamps recorded in the metadata ({nStamps})."
202 )
203
204 stamps = []
205 for k in range(nStamps):
206
207 maskedImage = masked_image_cls(**stamp_parts[k + 1])
208 archive_element = archive.get(archive_ids[k]) if has_archive else None
209 stamps.append(stamp_factory(maskedImage, stamp_metadata[k], k, archive_element))
210
211 return stamps, metadata
212
213
214@dataclass