Coverage for python/lsst/meas/extensions/scarlet/source.py: 18%
71 statements
« prev ^ index » next coverage.py v6.4.4, created at 2022-09-15 03:23 -0700
« prev ^ index » next coverage.py v6.4.4, created at 2022-09-15 03:23 -0700
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/>.
22import logging
24import numpy as np
25from scarlet.bbox import Box
27from lsst.geom import Point2I, Box2I, Extent2I
28from lsst.afw.geom import SpanSet
29from lsst.afw.detection import Footprint, PeakCatalog, makeHeavyFootprint
30from lsst.afw.detection.multiband import MultibandFootprint
31from lsst.afw.image import Mask, MultibandImage, Image, MaskedImage
33__all__ = ["modelToHeavy"]
35logger = logging.getLogger(__name__)
38def scarletBoxToBBox(box, xy0=Point2I()):
39 """Convert a scarlet.Box to a Box2I
41 Parameters
42 ----------
43 box : `scarlet.Box`
44 The scarlet bounding box to convert
45 xy0 : `Point2I`
46 An additional offset to apply to the scarlet box.
47 This is common since scarlet sources have an origin of
48 `(0,0)` at the lower left corner of the blend while
49 the blend itself is likely to have an offset in the
50 `Exposure`.
52 Returns
53 -------
54 bbox : `lsst.geom.box2I`
55 The converted bounding box
56 """
57 xy0 = Point2I(box.origin[-1]+xy0.x, box.origin[-2]+xy0.y)
58 extent = Extent2I(box.shape[-1], box.shape[-2])
59 return Box2I(xy0, extent)
62def bboxToScarletBox(nBands, bbox, xy0=Point2I()):
63 """Convert a Box2I to a scarlet bounding Box
65 Parameters
66 ----------
67 nBands : `int`
68 Number of bands in the full image.
69 bbox : `lsst.geom.Box2I`
70 The Box2I to convert into a scarlet `Box`.
71 xy0 : `lsst.geom.Point2I`.
72 An overall offset to subtract from the `Box2I`.
73 This is common in blends, where `xy0` is the minimum pixel
74 location of the blend and `bbox` is the box containing
75 a source in the blend.
77 Returns
78 -------
79 box : `scarlet.Box`
80 A scarlet `Box` that is more useful for slicing image data
81 as a numpy array.
82 """
83 origin = (0, bbox.getMinY()-xy0.y, bbox.getMinX()-xy0.x)
84 return Box((nBands, bbox.getHeight(), bbox.getWidth()), origin)
87def modelToHeavy(source, mExposure, blend, xy0=Point2I(), dtype=np.float32):
88 """Convert a scarlet model to a `MultibandFootprint`.
90 Parameters
91 ----------
92 source : `scarlet.Source`
93 The source to convert to a `HeavyFootprint`.
94 mExposure : `lsst.image.MultibandExposure`
95 The multiband exposure containing the image,
96 mask, and variance data.
97 blend : `scarlet.Blend`
98 The `Blend` object that contains information about
99 the observation, PSF, etc, used to convolve the
100 scarlet model to the observed seeing in each band.
101 xy0 : `lsst.geom.Point2I`
102 `(x,y)` coordinates of the lower-left pixel of the
103 entire blend.
104 dtype : `numpy.dtype`
105 The data type for the returned `HeavyFootprint`.
107 Returns
108 -------
109 mHeavy : `lsst.detection.MultibandFootprint`
110 The multi-band footprint containing the model for the source.
111 """
112 # We want to convolve the model with the observed PSF,
113 # which means we need to grow the model box by the PSF to
114 # account for all of the flux after convolution.
115 # FYI: The `scarlet.Box` class implements the `&` operator
116 # to take the intersection of two boxes.
118 # Get the PSF size and radii to grow the box
119 py, px = blend.observations[0].psf.get_model().shape[1:]
120 dh = py // 2
121 dw = px // 2
122 shape = (source.bbox.shape[0], source.bbox.shape[1] + py, source.bbox.shape[2] + px)
123 origin = (source.bbox.origin[0], source.bbox.origin[1] - dh, source.bbox.origin[2] - dw)
124 # Create the larger box to fit the model + PSf
125 bbox = Box(shape, origin=origin)
126 # Only use the portion of the convolved model that fits in the image
127 overlap = bbox & source.frame.bbox
128 # Load the full multiband model in the larger box
129 model = source.model_to_box(overlap)
130 # Convolve the model with the PSF in each band
131 # Always use a real space convolution to limit artifacts
132 model = blend.observations[0].renderer.convolve(model, convolution_type="real").astype(dtype)
133 # Update xy0 with the origin of the sources box
134 _xy0 = Point2I(overlap.origin[-1] + xy0.x, overlap.origin[-2] + xy0.y)
135 # Create the spans for the footprint
136 valid = np.max(np.array(model), axis=0) != 0
137 valid = Mask(valid.astype(np.int32), xy0=_xy0)
138 spans = SpanSet.fromMask(valid)
140 # Add the location of the source to the peak catalog
141 peakCat = PeakCatalog(source.detectedPeak.table)
142 peakCat.append(source.detectedPeak)
143 # Create the MultibandHeavyFootprint
144 foot = Footprint(spans)
145 foot.setPeakCatalog(peakCat)
146 model = MultibandImage(mExposure.filters, model, valid.getBBox())
147 mHeavy = MultibandFootprint.fromImages(mExposure.filters, model, footprint=foot)
148 return mHeavy
151def liteModelToHeavy(source, blend, xy0=Point2I(), dtype=np.float32, useFlux=False, filters=None):
152 """Convert a scarlet model to a `MultibandFootprint`.
153 Parameters
154 ----------
155 source : `scarlet.LiteSource`
156 The source to convert to a `HeavyFootprint`.
157 filters : `list` of `str`
158 The names of the filters from the `MultibandExposure`.
159 This is only required if the model is multi-band.
160 blend : `scarlet.Blend`
161 The `Blend` object that contains information about
162 the observation, PSF, etc, used to convolve the
163 scarlet model to the observed seeing in each band.
164 xy0 : `lsst.geom.Point2I`
165 `(x,y)` coordinates of the lower-left pixel of the
166 entire blend.
167 dtype : `numpy.dtype`
168 The data type for the returned `HeavyFootprint`.
169 Returns
170 -------
171 heavy : `lsst.detection.Footprint`
172 The footprint (possibly multiband) containing the model for the source.
173 """
174 # We want to convolve the model with the observed PSF,
175 # which means we need to grow the model box by the PSF to
176 # account for all of the flux after convolution.
177 # FYI: The `scarlet.Box` class implements the `&` operator
178 # to take the intersection of two boxes.
180 # Get the PSF size and radii to grow the box
181 py, px = blend.observation.psfs.shape[1:]
182 dh = py // 2
183 dw = px // 2
185 if useFlux:
186 source_box = source.flux_box
187 shape = source_box.shape
188 origin = source_box.origin
189 else:
190 source_box = source.bbox
191 shape = (source_box.shape[0], source_box.shape[1] + 2*dh, source_box.shape[2] + 2*dw)
192 origin = (source_box.origin[0], source_box.origin[1] - dh, source_box.origin[2] - dw)
193 # Create the larger box to fit the model + PSf
194 bbox = Box(shape, origin=origin)
195 # Only use the portion of the convolved model that fits in the image
196 overlap = bbox & blend.observation.bbox
197 # Load the full multiband model in the larger box
198 if useFlux:
199 # The flux weighted model is already convolved, so we just load it
200 _bbox = overlap - bbox.origin
201 model = source.get_model(use_flux=True)[_bbox.slices]
202 else:
203 model = source.get_model(bbox=overlap)
204 # Convolve the model with the PSF in each band
205 # Always use a real space convolution to limit artifacts
206 model = blend.observation.convolve(model, mode="real").astype(dtype)
208 # Update xy0 with the origin of the sources box
209 xy0 = Point2I(overlap.origin[-1] + xy0.x, overlap.origin[-2] + xy0.y)
210 # Create the spans for the footprint
211 valid = np.max(np.array(model), axis=0) != 0
212 valid = Mask(valid.astype(np.int32), xy0=xy0)
213 spans = SpanSet.fromMask(valid)
215 # Add the location of the source to the peak catalog
216 peakCat = PeakCatalog(source.detectedPeak.table)
217 peakCat.append(source.detectedPeak)
219 # Create the MultibandHeavyFootprint
220 foot = Footprint(spans)
221 foot.setPeakCatalog(peakCat)
222 if len(model) == 1:
223 image = Image(
224 array=model[0].astype(dtype),
225 xy0=valid.getBBox().getMin(),
226 dtype=dtype
227 )
228 maskedImage = MaskedImage(image, dtype=dtype)
229 heavy = makeHeavyFootprint(foot, maskedImage)
230 else:
231 model = MultibandImage(filters, model.astype(dtype), valid.getBBox())
232 heavy = MultibandFootprint.fromImages(filters, model.astype(dtype), footprint=foot)
233 return heavy