LSST Applications g0f08755f38+c89d42e150,g1635faa6d4+b6cf076a36,g1653933729+a8ce1bb630,g1a0ca8cf93+4c08b13bf7,g28da252d5a+f33f8200ef,g29321ee8c0+0187be18b1,g2bbee38e9b+9634bc57db,g2bc492864f+9634bc57db,g2cdde0e794+c2c89b37c4,g3156d2b45e+41e33cbcdc,g347aa1857d+9634bc57db,g35bb328faa+a8ce1bb630,g3a166c0a6a+9634bc57db,g3e281a1b8c+9f2c4e2fc3,g414038480c+077ccc18e7,g41af890bb2+e740673f1a,g5fbc88fb19+17cd334064,g7642f7d749+c89d42e150,g781aacb6e4+a8ce1bb630,g80478fca09+f8b2ab54e1,g82479be7b0+e2bd23ab8b,g858d7b2824+c89d42e150,g9125e01d80+a8ce1bb630,g9726552aa6+10f999ec6a,ga5288a1d22+065360aec4,gacf8899fa4+9553554aa7,gae0086650b+a8ce1bb630,gb58c049af0+d64f4d3760,gbd46683f8f+ac57cbb13d,gc28159a63d+9634bc57db,gcf0d15dbbd+e37acf7834,gda3e153d99+c89d42e150,gda6a2b7d83+e37acf7834,gdaeeff99f8+1711a396fd,ge2409df99d+cb1e6652d6,ge79ae78c31+9634bc57db,gf0baf85859+147a0692ba,gf3967379c6+02b11634a5,w.2024.45
LSST Data Management Base Package
Loading...
Searching...
No Matches
fit_psf.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__ = ["FittedPsfObservation", "FittedPsfBlend"]
23
24from typing import Callable, cast
25
26import numpy as np
27
28from ..bbox import Box
29from ..blend import Blend
30from ..fft import Fourier, get_fft_shape
31from ..image import Image
32from ..observation import Observation
33from ..parameters import parameter
34
35
37 """An observation that fits the PSF used to convolve the model."""
38
40 self,
41 images: np.ndarray | Image,
42 variance: np.ndarray | Image,
43 weights: np.ndarray | Image,
44 psfs: np.ndarray,
45 model_psf: np.ndarray | None = None,
46 noise_rms: np.ndarray | None = None,
47 bbox: Box | None = None,
48 bands: tuple | None = None,
49 padding: int = 3,
50 convolution_mode: str = "fft",
51 ):
52 """Initialize a `FitPsfObservation`
53
54 See `Observation` for a description of the parameters.
55 """
56 super().__init__(
57 images,
58 variance,
59 weights,
60 psfs,
61 model_psf,
62 noise_rms,
63 bbox,
64 bands,
65 padding,
66 convolution_mode,
67 )
68
69 self.axes = (-2, -1)
70
71 self.fft_shape = get_fft_shape(self.images.data[0], psfs[0], padding, self.axes)
72
73 # Make the DFT of the psf a fittable parameter
74 self._fitted_kernel = parameter(cast(Fourier, self.diff_kernel).fft(self.fft_shape, self.axes))
75
77 self, input_grad: np.ndarray, kernel: np.ndarray, model_fft: np.ndarray
78 ) -> np.ndarray:
79 # Transform the upstream gradient into k-space
80 grad_fft = Fourier(input_grad)
81 _grad_fft = grad_fft.fft(self.fft_shape, self.axes)
82 return _grad_fft * model_fft
83
84 def prox_kernel(self, kernel: np.ndarray) -> np.ndarray:
85 # No prox for now
86 return kernel
87
88 @property
89 def fitted_kernel(self) -> np.ndarray:
90 return self._fitted_kernel.x
91
92 @property
93 def cached_kernel(self):
94 return self.fitted_kernel.real - self.fitted_kernel.imag * 1j
95
96 def convolve(self, image: Image, mode: str | None = None, grad: bool = False) -> Image:
97 """Convolve the model into the observed seeing in each band.
98
99 Parameters
100 ----------
101 image:
102 The image to convolve
103 mode:
104 The convolution mode to use.
105 This should be "real" or "fft" or `None`,
106 where `None` will use the default `convolution_mode`
107 specified during init.
108 grad:
109 Whether this is a backward gradient convolution
110 (`grad==True`) or a pure convolution with the PSF.
111 """
112 if grad:
113 kernel = self.cached_kernel
114 else:
115 kernel = self.fitted_kernel
116
117 if mode != "fft" and mode is not None:
118 return super().convolve(image, mode, grad)
119
120 fft_image = Fourier(image.data)
121 fft = fft_image.fft(self.fft_shape, self.axes)
122
123 result = Fourier.from_fft(fft * kernel, self.fft_shape, image.shape, self.axes)
124 return Image(result.image, bands=image.bands, yx0=image.yx0)
125
126 def update(self, it: int, input_grad: np.ndarray, model: Image):
127 _model = Fourier(model.data[:, ::-1, ::-1])
128 model_fft = _model.fft(self.fft_shape, self.axes)
129 self._fitted_kernel.update(it, input_grad, model_fft)
130
131 def parameterize(self, parameterization: Callable) -> None:
132 """Convert the component parameter arrays into Parameter instances
133
134 Parameters
135 ----------
136 parameterization: Callable
137 A function to use to convert parameters of a given type into
138 a `Parameter` in place. It should take a single argument that
139 is the `Component` or `Source` that is to be parameterized.
140 """
141 # Update the spectrum and morph in place
142 parameterization(self)
143 # update the parameters
144 self._fitted_kernel.grad = self.grad_fit_kernel
145 self._fitted_kernel.prox = self.prox_kernel
146
147
149 """A blend that attempts to fit the PSF along with the source models."""
150
151 def _grad_log_likelihood(self) -> Image:
152 """Gradient of the likelihood wrt the unconvolved model"""
153 model = self.get_model(convolve=True)
154 # Update the loss
155 self.loss.append(self.observationobservation.log_likelihood(model))
156 # Calculate the gradient wrt the model d(logL)/d(model)
157 result = self.observationobservation.weights * (model - self.observationobservation.images)
158 return result
159
160 def fit(
161 self,
162 max_iter: int,
163 e_rel: float = 1e-4,
164 min_iter: int = 1,
165 resize: int = 10,
166 ) -> tuple[int, float]:
167 """Fit all of the parameters
168
169 Parameters
170 ----------
171 max_iter: int
172 The maximum number of iterations
173 e_rel: float
174 The relative error to use for determining convergence.
175 min_iter: int
176 The minimum number of iterations.
177 resize: int
178 Number of iterations before attempting to resize the
179 resizable components. If `resize` is `None` then
180 no resizing is ever attempted.
181 """
182 it = self.itit
183 while it < max_iter:
184 # Calculate the gradient wrt the on-convolved model
185 grad_log_likelihood = self._grad_log_likelihood_grad_log_likelihood()
186 _grad_log_likelihood = self.observationobservation.convolve(grad_log_likelihood, grad=True)
187 # Check if resizing needs to be performed in this iteration
188 if resize is not None and self.itit > 0 and self.itit % resize == 0:
189 do_resize = True
190 else:
191 do_resize = False
192 # Update each component given the current gradient
193 for component in self.components:
194 overlap = component.bbox & self.bboxbboxbbox
195 component.update(it, _grad_log_likelihood[overlap].data)
196 # Check to see if any components need to be resized
197 if do_resize:
198 component.resize(self.bboxbboxbbox)
199
200 # Update the PSF
201 cast(FittedPsfObservation, self.observationobservation).update(
202 it, grad_log_likelihood.data, self.get_model()
203 )
204 # Stopping criteria
205 it += 1
206 if it > min_iter and np.abs(self.loss[-1] - self.loss[-2]) < e_rel * np.abs(self.loss[-1]):
207 break
208 self.itit = it
209 return it, self.loss[-1]
A class to represent a 2-dimensional array of pixels.
Definition Image.h:51
Image _grad_log_likelihood(self)
Definition blend.py:116
list[Component] components(self)
Definition blend.py:72
Image get_model(self, bool convolve=False, bool use_flux=False)
Definition blend.py:80
update(self, int it, np.ndarray input_grad, Image model)
Definition fit_psf.py:126
Image convolve(self, Image image, str|None mode=None, bool grad=False)
Definition fit_psf.py:96
np.ndarray prox_kernel(self, np.ndarray kernel)
Definition fit_psf.py:84
None parameterize(self, Callable parameterization)
Definition fit_psf.py:131
np.ndarray grad_fit_kernel(self, np.ndarray input_grad, np.ndarray kernel, np.ndarray model_fft)
Definition fit_psf.py:78
__init__(self, np.ndarray|Image images, np.ndarray|Image variance, np.ndarray|Image weights, np.ndarray psfs, np.ndarray|None model_psf=None, np.ndarray|None noise_rms=None, Box|None bbox=None, tuple|None bands=None, int padding=3, str convolution_mode="fft")
Definition fit_psf.py:51