Coverage for python/lsst/ip/diffim/snapPsfMatch.py: 50%

30 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-10-12 09:53 +0000

1# This file is part of ip_diffim. 

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 

22__all__ = ["SnapPsfMatchConfigDF", "SnapPsfMatchConfigAL", "SnapPsfMatchConfig", "SnapPsfMatchTask"] 

23 

24import lsst.pex.config as pexConfig 

25from .psfMatch import PsfMatchConfigDF, PsfMatchConfigAL 

26from .imagePsfMatch import ImagePsfMatchTask, ImagePsfMatchConfig 

27 

28 

29class SnapPsfMatchConfigDF(PsfMatchConfigDF): 

30 """Delta-function Psf-matching config optimized for snap subtraction""" 

31 

32 def setDefaults(self): 

33 PsfMatchConfigDF.setDefaults(self) 

34 

35 # No regularization 

36 self.useRegularization = False 

37 

38 # Pca 

39 self.usePcaForSpatialKernel = True 

40 self.subtractMeanForPca = True 

41 self.numPrincipalComponents = 5 

42 

43 

44class SnapPsfMatchConfigAL(PsfMatchConfigAL): 

45 """Sum-of-Gaussian (Alard-Lupton) Psf-matching config optimized for snap subtraction""" 

46 

47 def setDefaults(self): 

48 PsfMatchConfigAL.setDefaults(self) 

49 

50 # Simple basis set 

51 self.alardNGauss = 2 

52 self.alardDegGauss = (4, 2) 

53 self.alardSigGauss = (1.0, 2.5) 

54 

55 

56class SnapPsfMatchConfig(ImagePsfMatchConfig): 

57 kernel = pexConfig.ConfigChoiceField( 

58 doc="kernel type", 

59 typemap=dict( 

60 AL=SnapPsfMatchConfigAL, 

61 DF=SnapPsfMatchConfigDF 

62 ), 

63 default="AL", 

64 ) 

65 

66 doWarping = pexConfig.Field( 

67 dtype=bool, 

68 doc="Warp the snaps?", 

69 default=False 

70 ) 

71 

72 def setDefaults(self): 

73 ImagePsfMatchConfig.setDefaults(self) 

74 

75 # No spatial variation in model 

76 self.kernel.active.spatialKernelOrder = 0 

77 

78 # Don't fit for differential background 

79 self.kernel.active.fitForBackground = False 

80 

81 # Small kernel size 

82 self.kernel.active.kernelSize = 7 

83 

84 # With zero spatial order don't worry about spatial clipping 

85 self.kernel.active.spatialKernelClipping = False 

86 

87 

88class SnapPsfMatchTask(ImagePsfMatchTask): 

89 """Image-based Psf-matching of two subsequent snaps from the same visit 

90 

91 Notes 

92 ----- 

93 This Task differs from ImagePsfMatchTask in that it matches two Exposures assuming that the images have 

94 been acquired very closely in time. Under this assumption, the astrometric misalignments and/or 

95 relative distortions should be within a pixel, and the Psf-shapes should be very similar. As a 

96 consequence, the default configurations for this class assume a very simple solution. 

97 

98 - The spatial variation in the kernel (SnapPsfMatchConfig.spatialKernelOrder) is assumed to be zero 

99 

100 - With no spatial variation, we turn of the spatial 

101 clipping loops (SnapPsfMatchConfig.spatialKernelClipping) 

102 

103 - The differential background is not fit for (SnapPsfMatchConfig.fitForBackground) 

104 

105 - The kernel is expected to be appx. 

106 a delta function, and has a small size (SnapPsfMatchConfig.kernelSize) 

107 

108 The sub-configurations for the Alard-Lupton (SnapPsfMatchConfigAL) 

109 and delta-function (SnapPsfMatchConfigDF) 

110 bases also are designed to generate a small, simple kernel. 

111 

112 Task initialization 

113 

114 Initialization is the same as base class ImagePsfMatch.__init__, 

115 with the difference being that the Task's 

116 ConfigClass is SnapPsfMatchConfig. 

117 

118 Invoking the Task 

119 

120 The Task is only configured to have a subtractExposures method, which in turn calls 

121 ImagePsfMatchTask.subtractExposures. 

122 

123 Configuration parameters 

124 

125 See SnapPsfMatchConfig, which uses either SnapPsfMatchConfigDF and SnapPsfMatchConfigAL 

126 as its active configuration. 

127 

128 Debug variables 

129 

130 The ``pipetask`` command line interface supports a 

131 flag --debug to import @b debug.py from your PYTHONPATH. The relevant contents of debug.py 

132 for this Task include: 

133 

134 .. code-block:: py 

135 

136 import sys 

137 import lsstDebug 

138 def DebugInfo(name): 

139 di = lsstDebug.getInfo(name) 

140 if name == "lsst.ip.diffim.psfMatch": 

141 di.display = True # enable debug output 

142 di.maskTransparency = 80 # display mask transparency 

143 di.displayCandidates = True # show all the candidates and residuals 

144 di.displayKernelBasis = False # show kernel basis functions 

145 di.displayKernelMosaic = True # show kernel realized across the image 

146 di.plotKernelSpatialModel = False # show coefficients of spatial model 

147 di.showBadCandidates = True # show the bad candidates (red) along with good (green) 

148 elif name == "lsst.ip.diffim.imagePsfMatch": 

149 di.display = True # enable debug output 

150 di.maskTransparency = 30 # display mask transparency 

151 di.displayTemplate = True # show full (remapped) template 

152 di.displaySciIm = True # show science image to match to 

153 di.displaySpatialCells = True # show spatial cells 

154 di.displayDiffIm = True # show difference image 

155 di.showBadCandidates = True # show the bad candidates (red) along with good (green) 

156 elif name == "lsst.ip.diffim.diaCatalogSourceSelector": 

157 di.display = False # enable debug output 

158 di.maskTransparency = 30 # display mask transparency 

159 di.displayExposure = True # show exposure with candidates indicated 

160 di.pauseAtEnd = False # pause when done 

161 return di 

162 lsstDebug.Info = DebugInfo 

163 lsstDebug.frame = 1 

164 

165 Note that if you want addional logging info, you may add to your scripts: 

166 

167 .. code-block:: py 

168 

169 import lsst.utils.logging as logUtils 

170 logUtils.trace_set_at("lsst.ip.diffim", 4) 

171 

172 Examples 

173 -------- 

174 This code is snapPsfMatchTask.py in the examples directory, and can be run as e.g. 

175 

176 .. code-block:: py 

177 

178 examples/snapPsfMatchTask.py 

179 examples/snapPsfMatchTask.py --debug 

180 examples/snapPsfMatchTask.py --debug --template /path/to/templateExp.fits 

181 --science /path/to/scienceExp.fits 

182 

183 First, create a subclass of SnapPsfMatchTask that accepts two exposures. 

184 Ideally these exposures would have been taken back-to-back, 

185 such that the pointing/background/Psf does not vary substantially between the two: 

186 

187 .. code-block:: py 

188 

189 class MySnapPsfMatchTask(SnapPsfMatchTask): 

190 def __init__(self, *args, **kwargs): 

191 SnapPsfMatchTask.__init__(self, *args, **kwargs) 

192 def run(self, templateExp, scienceExp): 

193 return self.subtractExposures(templateExp, scienceExp) 

194 

195 And allow the user the freedom to either run the script in default mode, 

196 or point to their own images on disk. Note that these images must be 

197 readable as an lsst.afw.image.Exposure 

198 

199 .. code-block:: py 

200 

201 if __name__ == "__main__": 

202 import argparse 

203 parser = argparse.ArgumentParser(description="Demonstrate the use of ImagePsfMatchTask") 

204 parser.add_argument("--debug", "-d", action="store_true", help="Load debug.py?", default=False) 

205 parser.add_argument("--template", "-t", help="Template Exposure to use", default=None) 

206 parser.add_argument("--science", "-s", help="Science Exposure to use", default=None) 

207 args = parser.parse_args() 

208 

209 We have enabled some minor display debugging in this script via the –debug option. However, 

210 if you have an lsstDebug debug.in your PYTHONPATH you will get additional debugging displays. 

211 The following block checks for this script 

212 

213 .. code-block:: py 

214 

215 if args.debug: 

216 try: 

217 import debug 

218 # Since I am displaying 2 images here, set the starting frame number for the LSST debug LSST 

219 debug.lsstDebug.frame = 3 

220 except ImportError as e: 

221 print(e, file=sys.stderr) 

222 

223 Finally, we call a run method that we define below. 

224 First set up a Config and choose the basis set to use: 

225 

226 .. code-block:: py 

227 

228 def run(args): 

229 # 

230 # Create the Config and use sum of gaussian basis 

231 # 

232 config = SnapPsfMatchTask.ConfigClass() 

233 config.doWarping = True 

234 config.kernel.name = "AL" 

235 

236 Make sure the images (if any) that were sent to the script exist on disk and are readable. 

237 If no images are sent, make some fake data up for the sake of this example script 

238 (have a look at the code if you want more details on generateFakeImages; 

239 as a detail of how the fake images were made, you do have to fit for a differential background): 

240 

241 .. code-block:: py 

242 

243 # Run the requested method of the Task 

244 if args.template is not None and args.science is not None: 

245 if not os.path.isfile(args.template): 

246 raise FileNotFoundError("Template image %s does not exist" % (args.template)) 

247 if not os.path.isfile(args.science): 

248 raise FileNotFoundError("Science image %s does not exist" % (args.science)) 

249 try: 

250 templateExp = afwImage.ExposureF(args.template) 

251 except Exception as e: 

252 raise RuntimeError("Cannot read template image %s" % (args.template)) 

253 try: 

254 scienceExp = afwImage.ExposureF(args.science) 

255 except Exception as e: 

256 raise RuntimeError("Cannot read science image %s" % (args.science)) 

257 else: 

258 templateExp, scienceExp = generateFakeImages() 

259 config.kernel.active.fitForBackground = True 

260 config.kernel.active.spatialBgOrder = 0 

261 config.kernel.active.sizeCellX = 128 

262 config.kernel.active.sizeCellY = 128 

263 

264 Display the two images if -debug 

265 

266 .. code-block:: py 

267 

268 if args.debug: 

269 afwDisplay.Display(frame=1).mtv(templateExp, title="Example script: Input Template") 

270 afwDisplay.Display(frame=2).mtv(scienceExp, title="Example script: Input Science Image") 

271 

272 Create and run the Task 

273 

274 .. code-block:: py 

275 

276 # Create the Task 

277 psfMatchTask = MySnapPsfMatchTask(config=config) 

278 # Run the Task 

279 result = psfMatchTask.run(templateExp, scienceExp) 

280 

281 And finally provide optional debugging display of the Psf-matched (via the Psf models) science image: 

282 

283 .. code-block:: py 

284 

285 if args.debug: 

286 # See if the LSST debug has incremented the frame number; if not start with frame 3 

287 try: 

288 frame = debug.lsstDebug.frame + 1 

289 except Exception: 

290 frame = 3 

291 afwDisplay.Display(frame=frame).mtv(result.matchedExposure, 

292 title="Example script: Matched Template Image") 

293 if "subtractedExposure" in result.getDict(): 

294 afwDisplay.Display(frame=frame + 1).mtv(result.subtractedExposure, 

295 title="Example script: Subtracted Image") 

296 

297 """ 

298 

299 ConfigClass = SnapPsfMatchConfig 

300 

301 # Override ImagePsfMatchTask.subtractExposures to set doWarping on config.doWarping 

302 def subtractExposures(self, templateExposure, scienceExposure, 

303 templateFwhmPix=None, scienceFwhmPix=None, 

304 candidateList=None): 

305 return ImagePsfMatchTask.subtractExposures(self, 

306 templateExposure=templateExposure, 

307 scienceExposure=scienceExposure, 

308 templateFwhmPix=templateFwhmPix, 

309 scienceFwhmPix=scienceFwhmPix, 

310 candidateList=candidateList, 

311 doWarping=self.config.doWarping, 

312 )