Coverage for python/lsst/meas/extensions/scarlet/source.py : 15%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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 numpy as np
23from scarlet.source import PointSource, ExtendedSource, MultiComponentSource
25import lsst.afw.image as afwImage
26from lsst.afw.geom import SpanSet
27from lsst.geom import Point2I
28import lsst.log
29import lsst.afw.detection as afwDet
31__all__ = ["init_source", "morphToHeavy", "modelToHeavy"]
33logger = lsst.log.Log.getLogger("meas.deblender.deblend")
36def init_source(frame, peak, observation, bbox,
37 symmetric=False, monotonic=True,
38 thresh=5, components=1):
39 """Initialize a Source
41 The user can specify the number of desired components
42 for the modeled source. If scarlet cannot initialize a
43 model with the desired number of components it continues
44 to attempt initialization of one fewer component until
45 it finds a model that can be initialized.
47 It is possible that scarlet will be unable to initialize a
48 source with the desired number of components, for example
49 a two component source might have degenerate components,
50 a single component source might not have enough signal in
51 the joint coadd (all bands combined together into
52 single signal-to-noise weighted image for initialization)
53 to initialize, and a true spurious detection will not have
54 enough signal to initialize as a point source.
55 If all of the models fail, including a `PointSource` model,
56 then this source is skipped.
58 Parameters
59 ----------
60 frame : `LsstFrame`
61 The model frame for the scene
62 peak : `PeakRecord`
63 Record for a peak in the parent `PeakCatalog`
64 observation : `LsstObservation`
65 The images, psfs, etc, of the observed data.
66 bbox : `lsst.geom.Box2I`
67 The bounding box of the parent footprint.
68 symmetric : `bool`
69 Whether or not the object is symmetric
70 monotonic : `bool`
71 Whether or not the object has flux monotonically
72 decreasing from its center
73 thresh : `float`
74 Fraction of the background to use as a threshold for
75 each pixel in the initialization
76 components : int
77 The number of components for the source.
78 If `components=0` then a `PointSource` model is used.
79 """
80 assert components <= 2
81 xmin = bbox.getMinX()
82 ymin = bbox.getMinY()
83 center = np.array([peak.getIy()-ymin, peak.getIx()-xmin], dtype=int)
85 while components > 1:
86 try:
87 source = MultiComponentSource(frame, center, observation, symmetric=symmetric,
88 monotonic=monotonic, thresh=thresh)
89 if (np.any([np.any(np.isnan(c.sed)) for c in source.components]) or
90 np.any([np.all(c.sed <= 0) for c in source.components])):
91 logger.warning("Could not initialize")
92 raise ValueError("Could not initialize source")
93 break
94 except Exception:
95 # If the MultiComponentSource failed to initialize
96 # try an ExtendedSource
97 components -= 1
99 if components == 1:
100 try:
101 source = ExtendedSource(frame, center, observation, thresh=thresh,
102 symmetric=symmetric, monotonic=monotonic)
103 if np.any(np.isnan(source.sed)) or np.all(source.sed <= 0) or np.sum(source.morph) == 0:
104 logger.warning("Could not initialize")
105 raise ValueError("Could not initialize source")
106 except Exception:
107 # If the source is too faint for background detection,
108 # initialize it as a PointSource
109 components -= 1
111 if components == 0:
112 try:
113 source = PointSource(frame, center, observation)
114 except Exception:
115 # None of the models worked to initialize the source,
116 # so skip this source
117 return None
119 source.detectedPeak = peak
120 return source
123def morphToHeavy(source, peakSchema, xy0=Point2I()):
124 """Convert the morphology to a `HeavyFootprint`
126 Parameters
127 ----------
128 source : `scarlet.Component`
129 The scarlet source with a morphology to convert to
130 a `HeavyFootprint`.
131 peakSchema : `lsst.daf.butler.Schema`
132 The schema for the `PeakCatalog` of the `HeavyFootprint`.
133 xy0 : `tuple`
134 `(x,y)` coordinates of the bounding box containing the
135 `HeavyFootprint`.
137 Returns
138 -------
139 heavy : `lsst.afw.detection.HeavyFootprint`
140 """
141 mask = afwImage.MaskX(np.array(source.morph > 0, dtype=np.int32), xy0=xy0)
142 ss = SpanSet.fromMask(mask)
144 if len(ss) == 0:
145 return None
147 tfoot = afwDet.Footprint(ss, peakSchema=peakSchema)
148 cy, cx = source.pixel_center
149 xmin, ymin = xy0
150 # HeavyFootprints are not defined for 64 bit floats
151 morph = source.morph.astype(np.float32)
152 peakFlux = morph[cy, cx]
153 tfoot.addPeak(cx+xmin, cy+ymin, peakFlux)
154 timg = afwImage.ImageF(morph, xy0=xy0)
155 timg = timg[tfoot.getBBox()]
156 heavy = afwDet.makeHeavyFootprint(tfoot, afwImage.MaskedImageF(timg))
157 return heavy
160def modelToHeavy(source, filters, xy0=Point2I(), observation=None, dtype=np.float32):
161 """Convert the model to a `MultibandFootprint`
163 Parameters
164 ----------
165 source : `scarlet.Component`
166 The source to convert to a `HeavyFootprint`.
167 filters : `iterable`
168 A "list" of names for each filter.
169 xy0 : `lsst.geom.Point2I`
170 `(x,y)` coordinates of the bounding box containing the
171 `HeavyFootprint`.
172 observation : `scarlet.Observation`
173 The scarlet observation, used to convolve the image with
174 the origin PSF. If `observation`` is `None` then the
175 `HeavyFootprint` will exist in the model frame.
176 dtype : `numpy.dtype`
177 The data type for the returned `HeavyFootprint`.
179 Returns
180 -------
181 mHeavy : `lsst.detection.MultibandFootprint`
182 The multi-band footprint containing the model for the source.
183 """
184 if observation is not None:
185 model = observation.render(source.get_model()).astype(dtype)
186 else:
187 model = source.get_model().astype(dtype)
188 mHeavy = afwDet.MultibandFootprint.fromArrays(filters, model, xy0=xy0)
189 peakCat = afwDet.PeakCatalog(source.detectedPeak.table)
190 peakCat.append(source.detectedPeak)
191 for footprint in mHeavy:
192 footprint.setPeakCatalog(peakCat)
193 return mHeavy