LSST Applications g04e9c324dd+8c5ae1fdc5,g0644efc3f0+366663dc37,g123d84c11c+8c5ae1fdc5,g1ec0fe41b4+6ec6b74de1,g1fd858c14a+1be88e80db,g3533f9d6cb+366663dc37,g35bb328faa+8c5ae1fdc5,g35ef7ab7cf+285dd5b202,g53246c7159+8c5ae1fdc5,g60b5630c4e+366663dc37,g663da51e9b+41529343ca,g6735e52a0d+29de3d959a,g67b6fd64d1+57193d00fb,g7605de067c+8f72e4d2dc,g78460c75b0+7e33a9eb6d,g786e29fd12+668abc6043,g844c57033c+03ddc13274,g8852436030+e345a59dd4,g89139ef638+57193d00fb,g989de1cb63+57193d00fb,g9a0bdda227+852181cf57,g9f33ca652e+a2d35689ce,ga1e959baac+5fbc491aed,ga2f891cd6c+366663dc37,gabe3b4be73+8856018cbb,gabf8522325+cc757f8247,gac2eed3f23+57193d00fb,gb1101e3267+f6b489998a,gb89ab40317+57193d00fb,gcf25f946ba+e345a59dd4,gd107969129+227687db21,gd6cbbdb0b4+8e46defd2a,gde0f65d7ad+2dad650f79,ge278dab8ac+2322f1d6ea,ge410e46f29+57193d00fb,gf30d85a44d+8e3077faf9,gf5e32f922b+8c5ae1fdc5,gff02db199a+5c78c1866e,w.2025.28
LSST Data Management Base Package
Loading...
Searching...
No Matches
free_form.py
Go to the documentation of this file.
1# This file is part of scarlet_lite.
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/>.
21
22__all__ = ["FactorizedFreeFormComponent"]
23
24from typing import Callable, cast
25
26import numpy as np
27from lsst.scarlet.lite.detect_pybind11 import get_connected_multipeak, get_footprints # type: ignore
28
29from ..bbox import Box
30from ..component import Component, FactorizedComponent
31from ..detect import footprints_to_image
32from ..image import Image
33from ..parameters import Parameter, parameter
34
35
37 """Implements a free-form component
38
39 With no constraints this component is typically either a garbage collector,
40 or part of a set of components to deconvolve an image by separating out
41 the different spectral components.
42
43 See `FactorizedComponent` for a list of parameters not shown here.
44
45 Parameters
46 ----------
47 peaks: `list` of `tuple`
48 A set of ``(cy, cx)`` peaks for detected sources.
49 If peak is not ``None`` then only pixels in the same "footprint"
50 as one of the peaks are included in the morphology.
51 If `peaks` is ``None`` then there is no constraint applied.
52 min_area: float
53 The minimum area for a peak.
54 If `min_area` is not `None` then all regions of the morphology
55 with fewer than `min_area` connected pixels are removed.
56 """
57
59 self,
60 bands: tuple,
61 spectrum: np.ndarray | Parameter,
62 morph: np.ndarray | Parameter,
63 model_bbox: Box,
64 bg_thresh: float | None = None,
65 bg_rms: np.ndarray | None = None,
66 floor: float = 1e-20,
67 peaks: list[tuple[int, int]] | None = None,
68 min_area: float = 0,
69 ):
70 super().__init__(
71 bands=bands,
72 spectrum=spectrum,
73 morph=morph,
74 bbox=model_bbox,
75 peak=None,
76 bg_rms=bg_rms,
77 bg_thresh=bg_thresh,
78 floor=floor,
79 )
80
81 self.peaks = peaks
82 self.min_area = min_area
83
84 def prox_spectrum(self, spectrum: np.ndarray) -> np.ndarray:
85 """Apply a prox-like update to the spectrum
86
87 This differs from `FactorizedComponent` because an
88 `SedComponent` has the spectrum normalized to unity.
89 """
90 # prevent divergent spectrum
91 spectrum[spectrum < self.floor] = self.floor
92 # Normalize the spectrum
93 spectrum = spectrum / np.sum(spectrum)
94 return spectrum
95
96 def prox_morph(self, morph: np.ndarray) -> np.ndarray:
97 """Apply a prox-like update to the morphology
98
99 This is the main difference between an `SedComponent` and a
100 `FactorizedComponent`, since this component has fewer constraints.
101 """
102 from lsst.scarlet.lite.detect_pybind11 import get_connected_multipeak, get_footprints # type: ignore
103
104 if self.bg_thresh is not None and isinstance(self.bg_rms, np.ndarray):
105 bg_thresh = self.bg_rms * self.bg_thresh
106 # Enforce background thresholding
107 model = self.spectrum[:, None, None] * morph[None, :, :]
108 morph[np.all(model < bg_thresh[:, None, None], axis=0)] = 0
109 else:
110 # enforce positivity
111 morph[morph < 0] = 0
112
113 if self.peaks is not None:
114 footprint = get_connected_multipeak(morph, self.peaks, 0)
115 morph = morph * footprint
116
117 if self.min_area > 0:
118 footprints = get_footprints(morph, 4.0, self.min_area, 0, 0, False)
119 bbox = self.bbox.copy()
120 bbox.origin = (0, 0)
121 footprint_image = footprints_to_image(footprints, bbox)
122 morph = morph * (footprint_image > 0).data
123
124 if np.all(morph == 0):
125 morph[0, 0] = self.floor
126
127 return morph
128
129 def resize(self, model_box: Box) -> bool:
130 return False
131
132 def __str__(self):
133 return (
134 f"FactorizedFreeFormComponent(\n bands={self.bands}\n "
135 f"spectrum={self.spectrum})\n center={self.peak}\n "
136 f"morph_shape={self.morph.shape}"
137 )
138
139 def __repr__(self):
140 return self.__str__()
141
142
144 """Implements a component with no spectral or monotonicty constraints
145
146 This is a FreeFormComponent that is not factorized into a
147 spectrum and morphology with no monotonicity constraint.
148 """
149
151 self,
152 bands: tuple,
153 model: np.ndarray | Parameter,
154 model_bbox: Box,
155 bg_thresh: float | None = None,
156 bg_rms: np.ndarray | None = None,
157 floor: float = 1e-20,
158 peaks: list[tuple[int, int]] | None = None,
159 min_area: float = 0,
160 ):
161 super().__init__(bands=bands, bbox=model_bbox)
162 self._model = parameter(model)
163 self.bg_rms = bg_rms
164 self.bg_thresh = bg_thresh
165 self.floor = floor
166 self.peaks = peaks
167 self.min_area = min_area
168
169 @property
170 def model(self) -> np.ndarray:
171 return self._model.x
172
173 def get_model(self) -> Image:
174 return Image(self.model, bands=self.bands, yx0=cast(tuple[int, int], self.bbox.origin))
175
176 @property
177 def shape(self) -> tuple:
178 return self.model.shape
179
180 def grad_model(self, input_grad: np.ndarray, model: np.ndarray) -> np.ndarray:
181 return input_grad
182
183 def prox_model(self, model: np.ndarray) -> np.ndarray:
184 if self.bg_thresh is not None and isinstance(self.bg_rms, np.ndarray):
185 bg_thresh = self.bg_rms * self.bg_thresh
186 # Enforce background thresholding
187 model[model < bg_thresh[:, None, None]] = 0
188 else:
189 # enforce positivity
190 model[model < 0] = 0
191
192 if self.peaks is not None:
193 # Remove pixels not connected to one of the peaks
194 model2d = np.sum(model, axis=0)
195 footprint = get_connected_multipeak(model2d, self.peaks, 0)
196 model = model * footprint[None, :, :]
197
198 if self.min_area > 0:
199 # Remove regions with fewer than min_area connected pixels
200 model2d = np.sum(model, axis=0)
201 footprints = get_footprints(model2d, 4.0, self.min_area, 0, 0, False)
202 bbox = self.bbox.copy()
203 bbox.origin = (0, 0)
204 footprint_image = footprints_to_image(footprints, bbox)
205 model = model * (footprint_image > 0).data[None, :, :]
206
207 if np.all(model == 0):
208 # If the model is all zeros, set a single pixel to the floor
209 model[0, 0] = self.floor
210
211 return model
212
213 def resize(self, model_box: Box) -> bool:
214 return False
215
216 def update(self, it: int, grad_log_likelihood: np.ndarray):
217 self._model.update(it, grad_log_likelihood)
218
219 def parameterize(self, parameterization: Callable) -> None:
220 """Convert the component parameter arrays into Parameter instances
221
222 Parameters
223 ----------
224 parameterization: Callable
225 A function to use to convert parameters of a given type into
226 a `Parameter` in place. It should take a single argument that
227 is the `Component` or `Source` that is to be parameterized.
228 """
229 # Update the spectrum and morph in place
230 parameterization(self)
231 # update the parameters
232 self._model.grad = self.grad_model
233 self._model.prox = self.prox_model
234
235 def __str__(self):
236 result = f"FreeFormComponent<bands={self.bands}, shape={self.shape}>"
237 return result
238
239 def __repr__(self):
240 return self.__str__()
A class to represent a 2-dimensional array of pixels.
Definition Image.h:51
__init__(self, tuple bands, np.ndarray|Parameter spectrum, np.ndarray|Parameter morph, Box model_bbox, float|None bg_thresh=None, np.ndarray|None bg_rms=None, float floor=1e-20, list[tuple[int, int]]|None peaks=None, float min_area=0)
Definition free_form.py:69
np.ndarray prox_spectrum(self, np.ndarray spectrum)
Definition free_form.py:84
np.ndarray prox_model(self, np.ndarray model)
Definition free_form.py:183
__init__(self, tuple bands, np.ndarray|Parameter model, Box model_bbox, float|None bg_thresh=None, np.ndarray|None bg_rms=None, float floor=1e-20, list[tuple[int, int]]|None peaks=None, float min_area=0)
Definition free_form.py:160
np.ndarray grad_model(self, np.ndarray input_grad, np.ndarray model)
Definition free_form.py:180
update(self, int it, np.ndarray grad_log_likelihood)
Definition free_form.py:216
None parameterize(self, Callable parameterization)
Definition free_form.py:219
MatrixB get_connected_multipeak(py::EigenDRef< const M > image, const std::vector< std::vector< int > > &centers, const double thresh=0)
Proximal operator to trim pixels not connected to one of the source centers.
std::vector< Footprint > get_footprints(py::EigenDRef< const M > image, const double min_separation, const int min_area, const double peak_thresh, const double footprint_thresh, const bool find_peaks=true, const int y0=0, const int x0=0)
Get all footprints in an image.