LSST Applications g063fba187b+cac8b7c890,g0f08755f38+6aee506743,g1653933729+a8ce1bb630,g168dd56ebc+a8ce1bb630,g1a2382251a+b4475c5878,g1dcb35cd9c+8f9bc1652e,g20f6ffc8e0+6aee506743,g217e2c1bcf+73dee94bd0,g28da252d5a+1f19c529b9,g2bbee38e9b+3f2625acfc,g2bc492864f+3f2625acfc,g3156d2b45e+6e55a43351,g32e5bea42b+1bb94961c2,g347aa1857d+3f2625acfc,g35bb328faa+a8ce1bb630,g3a166c0a6a+3f2625acfc,g3e281a1b8c+c5dd892a6c,g3e8969e208+a8ce1bb630,g414038480c+5927e1bc1e,g41af890bb2+8a9e676b2a,g7af13505b9+809c143d88,g80478fca09+6ef8b1810f,g82479be7b0+f568feb641,g858d7b2824+6aee506743,g89c8672015+f4add4ffd5,g9125e01d80+a8ce1bb630,ga5288a1d22+2903d499ea,gb58c049af0+d64f4d3760,gc28159a63d+3f2625acfc,gcab2d0539d+b12535109e,gcf0d15dbbd+46a3f46ba9,gda6a2b7d83+46a3f46ba9,gdaeeff99f8+1711a396fd,ge79ae78c31+3f2625acfc,gef2f8181fd+0a71e47438,gf0baf85859+c1f95f4921,gfa517265be+6aee506743,gfa999e8aa5+17cd334064,w.2024.51
LSST Data Management Base Package
Loading...
Searching...
No Matches
detect.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
22from __future__ import annotations
23
24import logging
25from typing import Sequence
26
27import numpy as np
28from lsst.scarlet.lite.detect_pybind11 import Footprint, get_footprints # type: ignore
29
30from .bbox import Box, overlapped_slices
31from .image import Image
32from .utils import continue_class
33from .wavelet import (
34 get_multiresolution_support,
35 get_starlet_scales,
36 multiband_starlet_reconstruction,
37 starlet_transform,
38)
39
40logger = logging.getLogger("scarlet.detect")
41
42
43def bounds_to_bbox(bounds: tuple[int, int, int, int]) -> Box:
44 """Convert the bounds of a Footprint into a Box
45
46 Notes
47 -----
48 Unlike slices, the bounds are _inclusive_ of the end points.
49
50 Parameters
51 ----------
52 bounds:
53 The bounds of the `Footprint` as a `tuple` of
54 ``(bottom, top, left, right)``.
55 Returns
56 -------
57 result:
58 The `Box` created from the bounds
59 """
60 return Box(
61 (bounds[1] + 1 - bounds[0], bounds[3] + 1 - bounds[2]),
62 origin=(bounds[0], bounds[2]),
63 )
64
65
66@continue_class
67class Footprint: # type: ignore # noqa
68 @property
69 def bbox(self) -> Box:
70 """Bounding box for the Footprint
71
72 Returns
73 -------
74 bbox:
75 The minimal `Box` that contains the entire `Footprint`.
76 """
77 return bounds_to_bbox(self.bounds) # type: ignore
78
79 @property
80 def yx0(self) -> tuple[int, int]:
81 """Origin in y, x of the lower left corner of the footprint"""
82 return self.bounds[0], self.bounds[2] # type: ignore
83
84 def intersection(self, other: Footprint) -> Image | None:
85 """The intersection of two footprints
86
87 Parameters
88 ----------
89 other:
90 The other footprint to compare.
91
92 Returns
93 -------
94 intersection:
95 The intersection of two footprints.
96 """
97 footprint1 = Image(self.data, yx0=self.yx0) # type: ignore
98 footprint2 = Image(other.data, yx0=other.yx0) # type: ignore # noqa
99 return footprint1 & footprint2
100
101 def union(self, other: Footprint) -> Image | None:
102 """The intersection of two footprints
103
104 Parameters
105 ----------
106 other:
107 The other footprint to compare.
108
109 Returns
110 -------
111 union:
112 The union of two footprints.
113 """
114 footprint1 = Image(self.data, yx0=self.yx0) # type: ignore
115 footprint2 = Image(other.data, yx0=other.yx0)
116 return footprint1 | footprint2
117
118
119def footprints_to_image(footprints: Sequence[Footprint], bbox: Box) -> Image:
120 """Convert a set of scarlet footprints to a pixelized image.
121
122 Parameters
123 ----------
124 footprints:
125 The footprints to convert into an image.
126 box:
127 The full box of the image that will contain the footprints.
128
129 Returns
130 -------
131 result:
132 The image created from the footprints.
133 """
134 result = Image.from_box(bbox, dtype=int)
135 for k, footprint in enumerate(footprints):
136 slices = overlapped_slices(result.bbox, footprint.bbox)
137 result.data[slices[0]] += footprint.data[slices[1]] * (k + 1)
138 return result
139
140
142 images: np.ndarray,
143 variance: np.ndarray,
144 scales: int | None = None,
145 generation: int = 2,
146) -> np.ndarray:
147 """Calculate wavelet coefficents given a set of images and their variances
148
149 Parameters
150 ----------
151 images:
152 The array of images with shape `(bands, Ny, Nx)` for which to
153 calculate wavelet coefficients.
154 variance:
155 An array of variances with the same shape as `images`.
156 scales:
157 The maximum number of wavelet scales to use.
158
159 Returns
160 -------
161 coeffs:
162 The array of coefficents with shape `(scales+1, bands, Ny, Nx)`.
163 Note that the result has `scales+1` total arrays,
164 since the last set of coefficients is the image of all
165 flux with frequency greater than the last wavelet scale.
166 """
167 sigma = np.median(np.sqrt(variance), axis=(1, 2))
168 # Create the wavelet coefficients for the significant pixels
169 scales = get_starlet_scales(images[0].shape, scales)
170 coeffs = np.empty((scales + 1,) + images.shape, dtype=images.dtype)
171 for b, image in enumerate(images):
172 _coeffs = starlet_transform(image, scales=scales, generation=generation)
173 support = get_multiresolution_support(
174 image=image,
175 starlets=_coeffs,
176 sigma=sigma[b],
177 sigma_scaling=3,
178 epsilon=1e-1,
179 max_iter=20,
180 )
181 coeffs[:, b] = (support.support * _coeffs).astype(images.dtype)
182 return coeffs
183
184
185def get_detect_wavelets(images: np.ndarray, variance: np.ndarray, scales: int = 3) -> np.ndarray:
186 """Get an array of wavelet coefficents to use for detection
187
188 Parameters
189 ----------
190 images:
191 The array of images with shape `(bands, Ny, Nx)` for which to
192 calculate wavelet coefficients.
193 variance:
194 An array of variances with the same shape as `images`.
195 scales:
196 The maximum number of wavelet scales to use.
197 Note that the result will have `scales+1` total arrays,
198 where the last set of coefficients is the image of all
199 flux with frequency greater than the last wavelet scale.
200
201 Returns
202 -------
203 starlets:
204 The array of wavelet coefficients for pixels with siignificant
205 amplitude in each scale.
206 """
207 sigma = np.median(np.sqrt(variance))
208 # Create the wavelet coefficients for the significant pixels
209 detect = np.sum(images, axis=0)
210 _coeffs = starlet_transform(detect, scales=scales)
211 support = get_multiresolution_support(
212 image=detect,
213 starlets=_coeffs,
214 sigma=sigma, # type: ignore
215 sigma_scaling=3,
216 epsilon=1e-1,
217 max_iter=20,
218 )
219 return (support.support * _coeffs).astype(images.dtype)
220
221
223 images: np.ndarray,
224 variance: np.ndarray,
225 scales: int = 2,
226 generation: int = 2,
227 origin: tuple[int, int] | None = None,
228 min_separation: float = 4,
229 min_area: int = 4,
230 peak_thresh: float = 5,
231 footprint_thresh: float = 5,
232 find_peaks: bool = True,
233 remove_high_freq: bool = True,
234 min_pixel_detect: int = 1,
235) -> list[Footprint]:
236 """Detect footprints in an image
237
238 Parameters
239 ----------
240 images:
241 The array of images with shape `(bands, Ny, Nx)` for which to
242 calculate wavelet coefficients.
243 variance:
244 An array of variances with the same shape as `images`.
245 scales:
246 The maximum number of wavelet scales to use.
247 If `remove_high_freq` is `False`, then this argument is ignored.
248 generation:
249 The generation of the starlet transform to use.
250 If `remove_high_freq` is `False`, then this argument is ignored.
251 origin:
252 The location (y, x) of the lower corner of the image.
253 min_separation:
254 The minimum separation between peaks in pixels.
255 min_area:
256 The minimum area of a footprint in pixels.
257 peak_thresh:
258 The threshold for peak detection.
259 footprint_thresh:
260 The threshold for footprint detection.
261 find_peaks:
262 If `True`, then detect peaks in the detection image,
263 otherwise only the footprints are returned.
264 remove_high_freq:
265 If `True`, then remove high frequency wavelet coefficients
266 before detecting peaks.
267 min_pixel_detect:
268 The minimum number of bands that must be above the
269 detection threshold for a pixel to be included in a footprint.
270 """
271
272 if origin is None:
273 origin = (0, 0)
274 if remove_high_freq:
275 # Build the wavelet coefficients
276 wavelets = get_wavelets(
277 images,
278 variance,
279 scales=scales,
280 generation=generation,
281 )
282 # Remove the high frequency wavelets.
283 # This has the effect of preventing high frequency noise
284 # from interfering with the detection of peak positions.
285 wavelets[0] = 0
286 # Reconstruct the image from the remaining wavelet coefficients
287 _images = multiband_starlet_reconstruction(
288 wavelets,
289 generation=generation,
290 )
291 else:
292 _images = images
293 # Build a SNR weighted detection image
294 sigma = np.median(np.sqrt(variance), axis=(1, 2)) / 2
295 detection = np.sum(_images / sigma[:, None, None], axis=0)
296 if min_pixel_detect > 1:
297 mask = np.sum(images > 0, axis=0) >= min_pixel_detect
298 detection[~mask] = 0
299 # Detect peaks on the detection image
300 footprints = get_footprints(
301 detection,
302 min_separation,
303 min_area,
304 peak_thresh,
305 footprint_thresh,
306 find_peaks,
307 origin[0],
308 origin[1],
309 )
310
311 return footprints
A class to represent a 2-dimensional array of pixels.
Definition Image.h:51
Image|None intersection(self, Footprint other)
Definition detect.py:84
Image|None union(self, Footprint other)
Definition detect.py:101
tuple[int, int] yx0(self)
Definition detect.py:80
np.ndarray get_detect_wavelets(np.ndarray images, np.ndarray variance, int scales=3)
Definition detect.py:185
Image footprints_to_image(Sequence[Footprint] footprints, Box bbox)
Definition detect.py:119
np.ndarray get_wavelets(np.ndarray images, np.ndarray variance, int|None scales=None, int generation=2)
Definition detect.py:146
list[Footprint] detect_footprints(np.ndarray images, np.ndarray variance, int scales=2, int generation=2, tuple[int, int]|None origin=None, float min_separation=4, int min_area=4, float peak_thresh=5, float footprint_thresh=5, bool find_peaks=True, bool remove_high_freq=True, int min_pixel_detect=1)
Definition detect.py:235
Box bounds_to_bbox(tuple[int, int, int, int] bounds)
Definition detect.py:43