LSST Applications 27.0.0,g0265f82a02+469cd937ee,g02d81e74bb+21ad69e7e1,g1470d8bcf6+cbe83ee85a,g2079a07aa2+e67c6346a6,g212a7c68fe+04a9158687,g2305ad1205+94392ce272,g295015adf3+81dd352a9d,g2bbee38e9b+469cd937ee,g337abbeb29+469cd937ee,g3939d97d7f+72a9f7b576,g487adcacf7+71499e7cba,g50ff169b8f+5929b3527e,g52b1c1532d+a6fc98d2e7,g591dd9f2cf+df404f777f,g5a732f18d5+be83d3ecdb,g64a986408d+21ad69e7e1,g858d7b2824+21ad69e7e1,g8a8a8dda67+a6fc98d2e7,g99cad8db69+f62e5b0af5,g9ddcbc5298+d4bad12328,ga1e77700b3+9c366c4306,ga8c6da7877+71e4819109,gb0e22166c9+25ba2f69a1,gb6a65358fc+469cd937ee,gbb8dafda3b+69d3c0e320,gc07e1c2157+a98bf949bb,gc120e1dc64+615ec43309,gc28159a63d+469cd937ee,gcf0d15dbbd+72a9f7b576,gdaeeff99f8+a38ce5ea23,ge6526c86ff+3a7c1ac5f1,ge79ae78c31+469cd937ee,gee10cc3b42+a6fc98d2e7,gf1cff7945b+21ad69e7e1,gfbcc870c63+9a11dc8c8f
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, cast
26
27import numpy as np
28from lsst.scarlet.lite.detect_pybind11 import Footprint # type: ignore
29
30from .bbox import Box
31from .image import Image
32from .utils import continue_class
33from .wavelet import get_multiresolution_support, starlet_transform
34
35logger = logging.getLogger("scarlet.detect")
36
37
38def bounds_to_bbox(bounds: tuple[int, int, int, int]) -> Box:
39 """Convert the bounds of a Footprint into a Box
40
41 Notes
42 -----
43 Unlike slices, the bounds are _inclusive_ of the end points.
44
45 Parameters
46 ----------
47 bounds:
48 The bounds of the `Footprint` as a `tuple` of
49 ``(bottom, top, left, right)``.
50 Returns
51 -------
52 result:
53 The `Box` created from the bounds
54 """
55 return Box(
56 (bounds[1] + 1 - bounds[0], bounds[3] + 1 - bounds[2]),
57 origin=(bounds[0], bounds[2]),
58 )
59
60
61@continue_class
62class Footprint: # type: ignore # noqa
63 @property
64 def bbox(self) -> Box:
65 """Bounding box for the Footprint
66
67 Returns
68 -------
69 bbox:
70 The minimal `Box` that contains the entire `Footprint`.
71 """
72 return bounds_to_bbox(self.bounds) # type: ignore
73
74 @property
75 def yx0(self) -> tuple[int, int]:
76 """Origin in y, x of the lower left corner of the footprint"""
77 return self.bounds[0], self.bounds[2] # type: ignore
78
79 def intersection(self, other: Footprint) -> Image | None:
80 """The intersection of two footprints
81
82 Parameters
83 ----------
84 other:
85 The other footprint to compare.
86
87 Returns
88 -------
89 intersection:
90 The intersection of two footprints.
91 """
92 footprint1 = Image(self.data, yx0=self.yx0) # type: ignore
93 footprint2 = Image(other.data, yx0=other.yx0) # type: ignore # noqa
94 return footprint1 & footprint2
95
96 def union(self, other: Footprint) -> Image | None:
97 """The intersection of two footprints
98
99 Parameters
100 ----------
101 other:
102 The other footprint to compare.
103
104 Returns
105 -------
106 union:
107 The union of two footprints.
108 """
109 footprint1 = Image(self.data, yx0=self.yx0) # type: ignore
110 footprint2 = Image(other.data, yx0=other.yx0)
111 return footprint1 | footprint2
112
113
114def footprints_to_image(footprints: Sequence[Footprint], shape: tuple[int, int]) -> Image:
115 """Convert a set of scarlet footprints to a pixelized image.
116
117 Parameters
118 ----------
119 footprints:
120 The footprints to convert into an image.
121 shape:
122 The shape of the image that is created from the footprints.
123
124 Returns
125 -------
126 result:
127 The image created from the footprints.
128 """
129 result = Image.from_box(Box(shape), dtype=int)
130 for k, footprint in enumerate(footprints):
131 bbox = bounds_to_bbox(footprint.bounds)
132 fp_image = Image(footprint.data, yx0=cast(tuple[int, int], bbox.origin))
133 result = result + fp_image * (k + 1)
134 return result
135
136
137def get_wavelets(images: np.ndarray, variance: np.ndarray, scales: int | None = None) -> np.ndarray:
138 """Calculate wavelet coefficents given a set of images and their variances
139
140 Parameters
141 ----------
142 images:
143 The array of images with shape `(bands, Ny, Nx)` for which to
144 calculate wavelet coefficients.
145 variance:
146 An array of variances with the same shape as `images`.
147 scales:
148 The maximum number of wavelet scales to use.
149
150 Returns
151 -------
152 coeffs:
153 The array of coefficents with shape `(scales+1, bands, Ny, Nx)`.
154 Note that the result has `scales+1` total arrays,
155 since the last set of coefficients is the image of all
156 flux with frequency greater than the last wavelet scale.
157 """
158 sigma = np.median(np.sqrt(variance), axis=(1, 2))
159 # Create the wavelet coefficients for the significant pixels
160 coeffs = []
161 for b, image in enumerate(images):
162 _coeffs = starlet_transform(image, scales=scales)
163 support = get_multiresolution_support(
164 image=image,
165 starlets=_coeffs,
166 sigma=sigma[b],
167 sigma_scaling=3,
168 epsilon=1e-1,
169 max_iter=20,
170 )
171 coeffs.append((support * _coeffs).astype(images.dtype))
172 return np.array(coeffs)
173
174
175def get_detect_wavelets(images: np.ndarray, variance: np.ndarray, scales: int = 3) -> np.ndarray:
176 """Get an array of wavelet coefficents to use for detection
177
178 Parameters
179 ----------
180 images:
181 The array of images with shape `(bands, Ny, Nx)` for which to
182 calculate wavelet coefficients.
183 variance:
184 An array of variances with the same shape as `images`.
185 scales:
186 The maximum number of wavelet scales to use.
187 Note that the result will have `scales+1` total arrays,
188 where the last set of coefficients is the image of all
189 flux with frequency greater than the last wavelet scale.
190
191 Returns
192 -------
193 starlets:
194 The array of wavelet coefficients for pixels with siignificant
195 amplitude in each scale.
196 """
197 sigma = np.median(np.sqrt(variance))
198 # Create the wavelet coefficients for the significant pixels
199 detect = np.sum(images, axis=0)
200 _coeffs = starlet_transform(detect, scales=scales)
201 support = get_multiresolution_support(
202 image=detect,
203 starlets=_coeffs,
204 sigma=sigma, # type: ignore
205 sigma_scaling=3,
206 epsilon=1e-1,
207 max_iter=20,
208 )
209 return (support * _coeffs).astype(images.dtype)
A class to represent a 2-dimensional array of pixels.
Definition Image.h:51
Image|None intersection(self, Footprint other)
Definition detect.py:79
Image|None union(self, Footprint other)
Definition detect.py:96
tuple[int, int] yx0(self)
Definition detect.py:75
np.ndarray get_detect_wavelets(np.ndarray images, np.ndarray variance, int scales=3)
Definition detect.py:175
np.ndarray get_wavelets(np.ndarray images, np.ndarray variance, int|None scales=None)
Definition detect.py:137
Box bounds_to_bbox(tuple[int, int, int, int] bounds)
Definition detect.py:38
Image footprints_to_image(Sequence[Footprint] footprints, tuple[int, int] shape)
Definition detect.py:114