99def readFitsWithOptions(filename, stamp_factory, options):
100 """Read stamps from FITS file, allowing for only a subregion of the stamps
101 to be read.
102
103 Parameters
104 ----------
105 filename : `str`
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
115 return a sub-image.
116
117 Returns
118 -------
119 stamps : `list` of dataclass objects like `Stamp`, PropertyList
120 A tuple of a list of `Stamp`-like objects
121 metadata : `PropertyList`
122 The metadata
123
124 Notes
125 -----
126 The data are read using the data type expected by the
127 `~lsst.afw.image.MaskedImage` class attached to the `AbstractStamp`
128 dataclass associated with the factory method.
129 """
130
131 metadata = readMetadata(filename, hdu=0)
132 nStamps = metadata["N_STAMPS"]
133 has_archive = metadata["HAS_ARCHIVE"]
134 if has_archive:
135 archive_ids = metadata.getArray("ARCHIVE_IDS")
136 with Fits(filename, "r") as f:
137 nExtensions = f.countHdus()
138
139 kwargs = {}
140 if options:
141
142 if "bbox" in options.keys():
143 kwargs["bbox"] = options["bbox"]
144
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
152 stamp_parts = {}
153
154
155
156
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
161 break
162 else:
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)
166
167
168 for idx in range(nExtensions - 1):
169 dtype = None
170 md = readMetadata(filename, hdu=idx + 1)
171
172 if md["XTENSION"] == "BINTABLE" and not ("ZIMAGE" in md and md["ZIMAGE"]):
173 if md["EXTNAME"] != "ARCHIVE_INDEX":
174 continue
175 if md["EXTNAME"] in ("IMAGE", "VARIANCE"):
176 reader = ImageFitsReader(filename, hdu=idx + 1)
177 if md["EXTNAME"] == "VARIANCE":
178 dtype = variance_dtype
179 else:
180 dtype = default_dtype
181 elif md["EXTNAME"] == "MASK":
182 reader = MaskFitsReader(filename, hdu=idx + 1)
183 elif md["EXTNAME"] == "ARCHIVE_INDEX":
184 f.setHdu(idx + 1)
185 archive = InputArchive.readFits(f)
186 continue
187 elif md["EXTTYPE"] == "ARCHIVE_DATA":
188 continue
189 else:
190 raise ValueError(f"Unknown extension type: {md['EXTNAME']}")
191 stamp_parts.setdefault(md["EXTVER"], {})[md["EXTNAME"].lower()] = reader.read(dtype=dtype,
192 **kwargs)
193 if len(stamp_parts) != nStamps:
194 raise ValueError(
195 f"Number of stamps read ({len(stamp_parts)}) does not agree with the "
196 f"number of stamps recorded in the metadata ({nStamps})."
197 )
198
199 stamps = []
200 for k in range(nStamps):
201
202 maskedImage = masked_image_cls(**stamp_parts[k + 1])
203 archive_element = archive.get(archive_ids[k]) if has_archive else None
204 stamps.append(stamp_factory(maskedImage, metadata, k, archive_element))
205
206 return stamps, metadata
207
208
209@dataclass