Coverage for tests / test_stamps.py: 24%
127 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:55 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:55 +0000
1# This file is part of meas_algorithms.
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/>.
22import unittest
23import numpy as np
24import tempfile
25import os
27from lsst.meas.algorithms import stamps
28from lsst.afw import image as afwImage
29from lsst.afw.geom.testUtils import TransformTestBaseClass
30from lsst.daf.base import PropertyList
31import lsst.geom as geom
32import lsst.afw.geom.transformFactory as tF
33import lsst.utils.tests
35_RNG = np.random.Generator(np.random.MT19937(5))
38def make_stamps(n_stamps=3, use_archive=False):
39 stampSize = 25
40 # create dummy stamp stamps
41 stampImages = [afwImage.MaskedImageF(stampSize, stampSize)
42 for _ in range(n_stamps)]
43 for stampIm in stampImages:
44 stampImArray = stampIm.image.array
45 stampImArray += _RNG.random((stampSize, stampSize))
46 stampMaskArray = stampIm.mask.array
47 stampMaskArray += 10
48 stampVarArray = stampIm.variance.array
49 stampVarArray += 1000.
50 ras = _RNG.random(n_stamps)*360.
51 decs = _RNG.random(n_stamps)*180 - 90
52 archive_elements = [tF.makeTransform(geom.AffineTransform(_RNG.random(2))) if use_archive else None
53 for _ in range(n_stamps)]
55 metadata = PropertyList()
56 metadata['RA_DEG'] = ras
57 metadata['DEC_DEG'] = decs
59 stamp_list = [stamps.Stamp(stamp_im=stampIm,
60 position=geom.SpherePoint(geom.Angle(ra, geom.degrees),
61 geom.Angle(dec, geom.degrees)),
62 archive_element=ae,
63 metadata=metadata)
64 for stampIm, ra, dec, ae in zip(stampImages, ras, decs, archive_elements)]
66 return stamps.Stamps(stamp_list, metadata=metadata, use_archive=True)
69class TransformTestClass(TransformTestBaseClass):
70 """Class for unit tests for Transform<X>To<Y> within meas_algorithms.
72 Inherits from `lsst.afw.geom.testUtils.TransformTestBaseClass`.
73 """
74 def getTestDir(self):
75 """Returns the test directory of the `meas_algorithms` package.
77 If a similar test is needed in another package, inherit from
78 `TransformTestBaseClass` and overwrite this method; see the docstrings
79 in the parent class.
80 """
81 return os.path.join(lsst.utils.getPackageDir("meas_algorithms"), "tests")
84class StampsBaseTestCase(lsst.utils.tests.TestCase):
85 """Test StampsBase.
86 """
87 def testReadFitsWithOptionsNotImplementedErrorRaised(self):
88 """
89 Test that subclasses have their own version
90 of this implemented or an error is raised.
91 """
92 class FakeStampsBase(stamps.StampsBase):
93 def __init__(self):
94 return
96 with self.assertRaises(NotImplementedError):
97 FakeStampsBase.readFitsWithOptions('noFile', {})
99 def testReadFitsWithOptionsMetadataError(self):
100 """Test that error is raised when STAMPCLS returns None
101 """
102 with tempfile.NamedTemporaryFile() as f:
103 ss = make_stamps()
104 emptyMetadata = PropertyList()
105 stamps.writeFits(
106 f.name, [ss[0]], emptyMetadata, None, True, True
107 )
108 with self.assertRaises(RuntimeError):
109 stamps.StampsBase.readFits(f.name)
111 def testReadFitsReturnsNewClass(self):
112 """Test that readFits will return subclass
113 """
114 class FakeStampsBase(stamps.StampsBase):
115 def __init__(self):
116 self._metadata = {}
117 return
119 @classmethod
120 def readFitsWithOptions(cls, filename, options):
121 return cls()
123 def _refresh_metadata(self):
124 self._metadata = {}
126 fakeStamps = FakeStampsBase.readFitsWithOptions('noFile', {})
127 self.assertEqual(type(fakeStamps), FakeStampsBase)
130class StampsTestCase(lsst.utils.tests.TestCase):
131 """Test Stamps.
132 """
133 def testAppend(self):
134 """Test ability to append to a Stamps object
135 """
136 ss = make_stamps()
137 s = ss[-1]
138 ss.append(s)
139 self.roundtrip(ss)
140 # check if appending something other than a Stamp raises
141 with self.assertRaises(ValueError):
142 ss.append('hello world')
144 def testExtend(self):
145 ss = make_stamps()
146 ss2 = make_stamps()
147 ss.extend([s for s in ss2])
148 # check if extending with something other than a Stamps
149 # object raises
150 with self.assertRaises(ValueError):
151 ss.extend(['hello', 'world'])
153 def testIO(self):
154 """Test the class' write and readFits methods.
155 """
156 self.roundtrip(make_stamps())
158 def testIOone(self):
159 """Test the class' write and readFits methods for the special case of
160 one stamp.
161 """
162 self.roundtrip(make_stamps(1))
164 def testIOsub(self):
165 """Test the class' write and readFits when passing on a bounding box.
166 """
167 bbox = geom.Box2I(geom.Point2I(3, 9), geom.Extent2I(11, 7))
168 ss = make_stamps()
169 with tempfile.NamedTemporaryFile() as f:
170 ss.writeFits(f.name)
171 options = {'bbox': bbox}
172 subStamps = stamps.Stamps.readFitsWithOptions(f.name, options)
173 for s1, s2 in zip(ss, subStamps):
174 self.assertEqual(bbox.getDimensions(), s2.stamp_im.getDimensions())
175 self.assertMaskedImagesAlmostEqual(s1.stamp_im[bbox], s2.stamp_im)
177 def testIOarchive(self):
178 """Test the class' write and readFits when Stamps contain Persistables.
179 """
180 self.roundtripWithArchive(make_stamps(use_archive=True))
182 def testMetadata(self):
183 """Test that metadata is correctly written and read.
184 """
185 stamps = make_stamps()
186 for stamp in stamps:
187 self.assertTrue(stamp.metadata is not None)
188 self.assertIn('RA_DEG', stamp.metadata)
189 self.assertIn('DEC_DEG', stamp.metadata)
191 def roundtrip(self, ss):
192 """Round trip a Stamps object to disk and check values
193 """
194 with tempfile.NamedTemporaryFile() as f:
195 ss.writeFits(f.name)
196 options = PropertyList()
197 ss2 = stamps.Stamps.readFitsWithOptions(f.name, options)
198 self.assertEqual(len(ss), len(ss2))
199 for s1, s2 in zip(ss, ss2):
200 self.assertMaskedImagesAlmostEqual(s1.stamp_im, s2.stamp_im)
201 self.assertAlmostEqual(s1.position.getRa().asDegrees(),
202 s2.position.getRa().asDegrees())
203 self.assertAlmostEqual(s1.position.getDec().asDegrees(),
204 s2.position.getDec().asDegrees())
206 for k, v in s1.metadata.items():
207 self.assertIn(k, s2.metadata)
208 self.assertAlmostEqual(v, s2.metadata[k])
210 def roundtripWithArchive(self, ss):
211 """Round trip a Stamps object, including Archive elements, and check values
212 """
213 transformTest = TransformTestClass()
214 with tempfile.NamedTemporaryFile() as f:
215 ss.writeFits(f.name)
216 options = PropertyList()
217 ss2 = stamps.Stamps.readFitsWithOptions(f.name, options)
218 self.assertEqual(len(ss), len(ss2))
219 for s1, s2 in zip(ss, ss2):
220 self.assertMaskedImagesAlmostEqual(s1.stamp_im, s2.stamp_im)
221 self.assertAlmostEqual(s1.position.getRa().asDegrees(),
222 s2.position.getRa().asDegrees())
223 self.assertAlmostEqual(s1.position.getDec().asDegrees(),
224 s2.position.getDec().asDegrees())
225 transformTest.assertTransformsEqual(s1.archive_element, s2.archive_element)
228class MemoryTester(lsst.utils.tests.MemoryTestCase):
229 pass
232def setup_module(module):
233 lsst.utils.tests.init()
236if __name__ == "__main__": 236 ↛ 237line 236 didn't jump to line 237 because the condition on line 236 was never true
237 lsst.utils.tests.init()
238 unittest.main()