Coverage for tests/scenes.py: 100%
19 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-09 09:32 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-09 09:32 +0000
1# This file is part of meas_extensions_scarlet.
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"""Named, atomic source layouts for the deblender test suite.
24Each :class:`Scene` carries a list of :class:`DeblenderTestModel`
25instances plus the band ordering it is to be rendered in. Scenes
26hold no rendered image data; rendering is a job for
27``pipeline.build_image``, which caches its output keyed by scene
28name.
30The ``"multi-blend"`` scene is the union of the four per-blend
31scenes and reproduces the historic 8-source / 4-blend layout that
32``test_deblend.py`` has used since the package was first written.
33"""
35from dataclasses import dataclass
37import numpy as np
39from utils import DeblenderTestModel, PsfModel, SersicModel
42# Band ordering shared by every scene (`tuple` [`str`]).
43BANDS: tuple[str, ...] = tuple("gri")
46@dataclass(frozen=True)
47class Scene:
48 """Named layout of synthetic sources for one deblender test case.
50 A scene is pure metadata: it names the layout, lists the bands it is
51 to be rendered in, and carries the source models. Turning it into
52 pixel data is the job of ``pipeline.build_image``.
54 Parameters
55 ----------
56 name : `str`
57 Identifier used as the rendering cache key and as the test ID
58 in parametrized tests.
59 bands : `tuple` [`str`]
60 Band ordering for the rendered image; the model spectra are
61 indexed in this order.
62 description : `str`
63 Short human-readable summary of the layout, surfaced in test
64 failure messages.
65 models : `list` [`DeblenderTestModel`]
66 Source models that make up the scene.
67 """
69 name: str
70 bands: tuple[str, ...]
71 description: str
72 models: list[DeblenderTestModel]
75def _one_isolated_psf_models() -> list[DeblenderTestModel]:
76 """Build the models for the ``one_isolated_psf`` scene."""
77 return [
78 PsfModel(center=(30, 15), spectrum=np.array([8, 2, 1]), bands=BANDS),
79 ]
82def _psf_plus_sersic_blend_models() -> list[DeblenderTestModel]:
83 """Build the models for the ``psf_plus_sersic_blend`` scene."""
84 return [
85 SersicModel(
86 center=(40, 20), major=5, minor=2, radius=15, theta=-np.pi / 4,
87 n=1, spectrum=np.array([2, 4, 8]), bands=BANDS,
88 ),
89 PsfModel(center=(12, 20), spectrum=np.array([1, 2, 8]), bands=BANDS),
90 ]
93def _three_source_blend_models() -> list[DeblenderTestModel]:
94 """Build the models for the ``three_source_blend`` scene."""
95 return [
96 SersicModel(
97 center=(25, 70), major=5, minor=2, radius=20, theta=np.pi / 48,
98 n=1, spectrum=np.array([2, 5, 8]), bands=BANDS,
99 ),
100 PsfModel(center=(32, 60), spectrum=np.array([1, 2, 8]), bands=BANDS),
101 PsfModel(center=(16, 80), spectrum=np.array([8, 2, 1]), bands=BANDS),
102 ]
105def _large_two_sersic_models() -> list[DeblenderTestModel]:
106 """Build the models for the ``large_two_sersic`` scene."""
107 return [
108 SersicModel(
109 center=(70, 70), major=5, minor=2, radius=25, theta=0,
110 n=1, spectrum=np.array([2, 10, 18]), bands=BANDS,
111 ),
112 SersicModel(
113 center=(85, 85), major=5, minor=2, radius=25, theta=np.pi / 2,
114 n=1, spectrum=np.array([5, 10, 20]), bands=BANDS,
115 ),
116 ]
119# Registry of every available scene, keyed by scene name
120SCENES: dict[str, Scene] = {
121 "one_isolated_psf": Scene(
122 name="one_isolated_psf",
123 bands=BANDS,
124 description="One isolated PSF source, away from any neighbor.",
125 models=_one_isolated_psf_models(),
126 ),
127 "psf_plus_sersic_blend": Scene(
128 name="psf_plus_sersic_blend",
129 bands=BANDS,
130 description="One Sersic with a nearby PSF overlapping its wing.",
131 models=_psf_plus_sersic_blend_models(),
132 ),
133 "three_source_blend": Scene(
134 name="three_source_blend",
135 bands=BANDS,
136 description="One Sersic plus two PSFs in a single blend.",
137 models=_three_source_blend_models(),
138 ),
139 "large_two_sersic": Scene(
140 name="large_two_sersic",
141 bands=BANDS,
142 description="Two large overlapping Sersics.",
143 models=_large_two_sersic_models(),
144 ),
145 "multi-blend": Scene(
146 name="multi-blend",
147 bands=BANDS,
148 description=(
149 "Composite scene: one isolated PSF, one PSF+Sersic blend, "
150 "one three-source blend, and one two-Sersic blend. "
151 "Reproduces the historic 8-source / 4-blend layout from "
152 "test_deblend.py."
153 ),
154 models=(
155 _one_isolated_psf_models()
156 + _psf_plus_sersic_blend_models()
157 + _three_source_blend_models()
158 + _large_two_sersic_models()
159 ),
160 ),
161}