Coverage for python/lsst/afw/math/backgroundList.py : 16%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of afw.
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/>.
22__all__ = ["BackgroundList"]
24import os
25import warnings
26import lsst.daf.base as dafBase
27import lsst.geom
28import lsst.afw.image as afwImage
29from lsst.afw.fits import MemFileManager, reduceToFits, Fits
30from lsst.utils import suppress_deprecations
31from . import mathLib as afwMath
34class BackgroundList:
35 """A list-like class to contain a list of (`lsst.afw.math.Background`,
36 `lsst.afw.math.Interpolate.Style`, `~lsst.afw.math.UndersampleStyle`)
37 tuples.
39 Parameters
40 ----------
41 *args : `tuple` or `~lsst.afw.math.Background`
42 A sequence of arguments, each of which becomes an element of the list.
43 In deference to the deprecated-but-not-yet-removed
44 `~lsst.afw.math.Background.getImageF()` API, we also accept a single
45 `lsst.afw.math.Background` and extract the ``interpStyle`` and
46 ``undersampleStyle`` from the as-used values.
47 """
49 def __init__(self, *args):
50 self._backgrounds = []
51 for a in args:
52 self.append(a)
54 def __getitem__(self, *args):
55 """Return an item
57 Parameters
58 ----------
59 *args
60 Any valid list index.
61 """
62 #
63 # Set any previously-unknown Styles (they are set by bkgd.getImage())
64 #
65 for i, val in enumerate(self._backgrounds):
66 bkgd, interpStyle, undersampleStyle, approxStyle, \
67 approxOrderX, approxOrderY, approxWeighting = val
68 if interpStyle is None or undersampleStyle is None:
69 interpStyle = bkgd.getAsUsedInterpStyle()
70 undersampleStyle = bkgd.getAsUsedUndersampleStyle()
71 actrl = bkgd.getBackgroundControl().getApproximateControl()
72 approxStyle = actrl.getStyle()
73 approxOrderX = actrl.getOrderX()
74 approxOrderY = actrl.getOrderY()
75 approxWeighting = actrl.getWeighting()
76 self._backgrounds[i] = (bkgd, interpStyle, undersampleStyle,
77 approxStyle, approxOrderX, approxOrderY, approxWeighting)
78 #
79 # And return what they wanted
80 #
81 return self._backgrounds.__getitem__(*args)
83 def __len__(self, *args):
84 return self._backgrounds.__len__(*args)
86 def append(self, val):
87 try:
88 bkgd, interpStyle, undersampleStyle, approxStyle, \
89 approxOrderX, approxOrderY, approxWeighting = val
90 except TypeError:
91 warnings.warn("Passing Background objects to BackgroundList is deprecated; "
92 "use a (Background, Interpolation.Style, UndersampleStyle, "
93 "ApproximateControl.Style, int, int, bool) tuple instead.",
94 category=FutureWarning, stacklevel=2)
95 bkgd = val
96 interpStyle = None
97 undersampleStyle = None
98 approxStyle = None
99 approxOrderX = None
100 approxOrderY = None
101 approxWeighting = None
103 bgInfo = (bkgd, interpStyle, undersampleStyle, approxStyle,
104 approxOrderX, approxOrderY, approxWeighting)
105 self._backgrounds.append(bgInfo)
107 def clone(self):
108 """Return a shallow copy
110 Shallow copies do not share backgrounds that are appended after copying,
111 but do share changes to contained background objects.
112 """
113 return BackgroundList(*self)
115 def writeFits(self, fileName, flags=0):
116 """Save our list of Backgrounds to a file.
118 Parameters
119 -----------
120 fileName : `str`
121 FITS file to write
122 flags : `int`
123 Flags to control details of writing; currently unused, but present
124 for consistency with `lsst.afw.table.BaseCatalog.writeFits`.
125 """
127 for i, bkgd in enumerate(self):
128 (bkgd, interpStyle, undersampleStyle, approxStyle, approxOrderX, approxOrderY,
129 approxWeighting) = bkgd
131 statsImage = bkgd.getStatsImage()
133 md = dafBase.PropertyList()
134 md.set("INTERPSTYLE", int(interpStyle))
135 md.set("UNDERSAMPLESTYLE", int(undersampleStyle))
136 md.set("APPROXSTYLE", int(approxStyle))
137 md.set("APPROXORDERX", approxOrderX)
138 md.set("APPROXORDERY", approxOrderY)
139 md.set("APPROXWEIGHTING", approxWeighting)
140 bbox = bkgd.getImageBBox()
141 md.set("BKGD_X0", bbox.getMinX())
142 md.set("BKGD_Y0", bbox.getMinY())
143 md.set("BKGD_WIDTH", bbox.getWidth())
144 md.set("BKGD_HEIGHT", bbox.getHeight())
146 statsImage.getImage().writeFits(fileName, md, "w" if i == 0 else "a")
147 statsImage.getMask().writeFits(fileName, md, "a")
148 statsImage.getVariance().writeFits(fileName, md, "a")
150 @staticmethod
151 def readFits(fileName, hdu=0, flags=0):
152 """Read our list of Backgrounds from a file.
154 Parameters
155 ----------
156 fileName : `str`
157 FITS file to read
158 hdu : `int`
159 First Header/Data Unit to attempt to read from
160 flags : `int`
161 Flags to control details of reading; currently unused, but present
162 for consistency with `lsst.afw.table.BaseCatalog.readFits`.
164 See Also
165 --------
166 getImage()
167 """
168 if not isinstance(fileName, MemFileManager) and not os.path.exists(fileName):
169 raise RuntimeError(f"File not found: {fileName}")
171 self = BackgroundList()
173 f = Fits(fileName, 'r')
174 nHdus = f.countHdus()
175 f.closeFile()
176 if nHdus % 3 != 0:
177 raise RuntimeError(f"BackgroundList FITS file {fileName} has {nHdus} HDUs;"
178 f"expected a multiple of 3 (compression is not supported).")
180 for hdu in range(0, nHdus, 3):
181 # It seems like we ought to be able to just use
182 # MaskedImageFitsReader here, but it warns about EXTTYPE and still
183 # doesn't work quite naturally when starting from a nonzero HDU.
184 imageReader = afwImage.ImageFitsReader(fileName, hdu=hdu)
185 maskReader = afwImage.MaskFitsReader(fileName, hdu=hdu + 1)
186 varianceReader = afwImage.ImageFitsReader(fileName, hdu=hdu + 2)
187 statsImage = afwImage.MaskedImageF(imageReader.read(), maskReader.read(), varianceReader.read())
188 md = imageReader.readMetadata()
190 x0 = md["BKGD_X0"]
191 y0 = md["BKGD_Y0"]
192 width = md["BKGD_WIDTH"]
193 height = md["BKGD_HEIGHT"]
194 imageBBox = lsst.geom.BoxI(lsst.geom.PointI(x0, y0), lsst.geom.ExtentI(width, height))
196 interpStyle = afwMath.Interpolate.Style(md["INTERPSTYLE"])
197 undersampleStyle = afwMath.UndersampleStyle(md["UNDERSAMPLESTYLE"])
199 # Older outputs won't have APPROX* settings. Provide alternative defaults.
200 # Note: Currently X- and Y-orders must be equal due to a limitation in
201 # math::Chebyshev1Function2. Setting approxOrderY = -1 is equivalent
202 # to saying approxOrderY = approxOrderX.
203 approxStyle = md.get("APPROXSTYLE", afwMath.ApproximateControl.UNKNOWN)
204 approxStyle = afwMath.ApproximateControl.Style(approxStyle)
205 approxOrderX = md.get("APPROXORDERX", 1)
206 approxOrderY = md.get("APPROXORDERY", -1)
207 approxWeighting = md.get("APPROXWEIGHTING", True)
209 bkgd = afwMath.BackgroundMI(imageBBox, statsImage)
210 bctrl = bkgd.getBackgroundControl()
212 # TODO: DM-22814: remove this after v20.
213 # Still needed until then because other code might call the old-style getImageF.
214 with suppress_deprecations():
215 bctrl.setInterpStyle(interpStyle)
217 bctrl.setUndersampleStyle(undersampleStyle)
218 actrl = afwMath.ApproximateControl(approxStyle, approxOrderX, approxOrderY, approxWeighting)
219 bctrl.setApproximateControl(actrl)
220 bgInfo = (bkgd, interpStyle, undersampleStyle, approxStyle,
221 approxOrderX, approxOrderY, approxWeighting)
222 self.append(bgInfo)
224 return self
226 def getImage(self):
227 """Compute and return a full-resolution image from our list of
228 (Background, interpStyle, undersampleStyle).
229 """
231 bkgdImage = None
232 for (bkgd, interpStyle, undersampleStyle, approxStyle,
233 approxOrderX, approxOrderY, approxWeighting) in self:
234 if not bkgdImage:
235 bkgdImage = bkgd.getImageF(interpStyle, undersampleStyle)
236 else:
237 bkgdImage += bkgd.getImageF(interpStyle, undersampleStyle)
239 return bkgdImage
241 def __reduce__(self):
242 return reduceToFits(self)