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

71 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-03-28 05:12 -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/>. 

21 

22import logging 

23 

24import numpy as np 

25from scarlet.bbox import Box 

26 

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 

32 

33__all__ = ["modelToHeavy"] 

34 

35logger = logging.getLogger(__name__) 

36 

37 

38def scarletBoxToBBox(box, xy0=Point2I()): 

39 """Convert a scarlet.Box to a Box2I 

40 

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`. 

51 

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) 

60 

61 

62def bboxToScarletBox(nBands, bbox, xy0=Point2I()): 

63 """Convert a Box2I to a scarlet bounding Box 

64 

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. 

76 

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) 

85 

86 

87def modelToHeavy(source, mExposure, blend, xy0=Point2I(), dtype=np.float32): 

88 """Convert a scarlet model to a `MultibandFootprint`. 

89 

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`. 

106 

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. 

117 

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) 

139 

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 

149 

150 

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. 

179 

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 

184 

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) 

207 

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) 

214 

215 # Add the location of the source to the peak catalog 

216 peakCat = PeakCatalog(source.detectedPeak.table) 

217 peakCat.append(source.detectedPeak) 

218 

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