Coverage for tests/test_inject_engine.py: 33%
65 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-11 09:21 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-11 09:21 +0000
1# This file is part of source_injection.
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 logging
23import unittest
24from types import GeneratorType
26import galsim
27import numpy as np
28from galsim import BoundsI, GSObject
30import lsst.utils.tests
31from lsst.geom import Point2D, SpherePoint, degrees
32from lsst.source.injection.inject_engine import (
33 generate_galsim_objects,
34 infer_gain_from_image,
35 inject_galsim_objects_into_exposure,
36 make_galsim_object,
37)
38from lsst.source.injection.utils.test_utils import make_test_exposure, make_test_injection_catalog
39from lsst.utils.tests import TestCase
42class InjectEngineTestCase(TestCase):
43 """Test the inject_engine.py module."""
45 def setUp(self):
46 """Set up synthetic source injection inputs.
48 This method sets up a noisy synthetic image with Gaussian PSFs injected
49 into the frame, an example source injection catalog, and a generator of
50 GalSim objects intended for injection.
51 """
52 self.exposure = make_test_exposure()
53 self.injection_catalog = make_test_injection_catalog(
54 self.exposure.getWcs(),
55 self.exposure.getBBox(),
56 )
57 self.galsim_objects = generate_galsim_objects(
58 injection_catalog=self.injection_catalog,
59 photo_calib=self.exposure.photoCalib,
60 wcs=self.exposure.wcs,
61 fits_alignment="wcs",
62 stamp_prefix="",
63 )
65 def tearDown(self):
66 del self.exposure
67 del self.injection_catalog
68 del self.galsim_objects
70 def test_make_galsim_object(self):
71 source_data = self.injection_catalog[0]
72 sky_coords = SpherePoint(float(source_data["ra"]), float(source_data["dec"]), degrees)
73 pixel_coords = self.exposure.wcs.skyToPixel(sky_coords)
74 inst_flux = self.exposure.photoCalib.magnitudeToInstFlux(source_data["mag"], pixel_coords)
75 object = make_galsim_object(
76 source_data=source_data,
77 source_type=source_data["source_type"],
78 inst_flux=inst_flux,
79 )
80 self.assertIsInstance(object, GSObject)
81 self.assertIsInstance(object, getattr(galsim, source_data["source_type"]))
83 def test_generate_galsim_objects(self):
84 self.assertTrue(isinstance(self.galsim_objects, GeneratorType))
85 for galsim_object in self.galsim_objects:
86 self.assertIsInstance(galsim_object, tuple)
87 self.assertEqual(len(galsim_object), 4)
88 self.assertIsInstance(galsim_object[0], SpherePoint) # RA/Dec
89 self.assertIsInstance(galsim_object[1], Point2D) # x/y
90 self.assertIsInstance(galsim_object[2], int) # draw size
91 self.assertIsInstance(galsim_object[3], GSObject) # GSObject
93 def test_infer_gain_nonexistent_mask(self):
94 """Test that infer_gain_from_image returns a gain value and logs a
95 warning when provided with nonexistent mask plane names.
96 """
97 logger = logging.getLogger(__name__)
98 with self.assertLogs(logger, level="WARNING") as cm:
99 gain = infer_gain_from_image(
100 self.exposure,
101 bad_mask_names=["NONEXISTENT_MASK1", "NONEXISTENT_MASK2"],
102 logger=logger,
103 )
104 self.assertTrue(any("NONEXISTENT_MASK1" in msg for msg in cm.output))
105 self.assertIsInstance(gain, float)
106 self.assertTrue(np.isfinite(gain))
107 gain_no_mask = infer_gain_from_image(self.exposure, bad_mask_names=[])
108 self.assertAlmostEqual(gain, gain_no_mask)
110 def test_inject_galsim_objects_into_exposure(self):
111 flux0 = np.sum(self.exposure.image.array)
112 injected_outputs = inject_galsim_objects_into_exposure(
113 exposure=self.exposure,
114 objects=self.galsim_objects,
115 mask_plane_name="INJECTED",
116 calib_flux_radius=12.0,
117 draw_size_max=1000,
118 add_noise=False,
119 )
120 pc = self.exposure.getPhotoCalib()
121 inst_fluxes = [float(pc.magnitudeToInstFlux(mag)) for mag in self.injection_catalog["mag"]]
122 self.assertAlmostEqual(
123 np.sum(self.exposure.image.array) - flux0,
124 np.sum(inst_fluxes),
125 delta=0.00015 * np.sum(inst_fluxes),
126 )
127 self.assertEqual(len(injected_outputs[0]), len(self.injection_catalog["ra"]))
128 self.assertTrue(all(isinstance(injected_output, list) for injected_output in injected_outputs))
129 self.assertTrue(all(isinstance(item, int) for item in injected_outputs[0])) # draw sizes
130 self.assertTrue(all(isinstance(item, BoundsI) for item in injected_outputs[1])) # common bounds
131 self.assertTrue(all(isinstance(item, bool) for item in injected_outputs[2])) # FFT size errors
132 self.assertTrue(all(isinstance(item, bool) for item in injected_outputs[3])) # PSF compute errors
135class MemoryTestCase(lsst.utils.tests.MemoryTestCase):
136 """Test memory usage of functions in this script."""
138 pass
141def setup_module(module):
142 """Configure pytest."""
143 lsst.utils.tests.init()
146if __name__ == "__main__": 146 ↛ 147line 146 didn't jump to line 147 because the condition on line 146 was never true
147 lsst.utils.tests.init()
148 unittest.main()