LSSTApplications  11.0-13-gbb96280,12.1+18,12.1+7,12.1-1-g14f38d3+72,12.1-1-g16c0db7+5,12.1-1-g5961e7a+84,12.1-1-ge22e12b+23,12.1-11-g06625e2+4,12.1-11-g0d7f63b+4,12.1-19-gd507bfc,12.1-2-g7dda0ab+38,12.1-2-gc0bc6ab+81,12.1-21-g6ffe579+2,12.1-21-gbdb6c2a+4,12.1-24-g941c398+5,12.1-3-g57f6835+7,12.1-3-gf0736f3,12.1-37-g3ddd237,12.1-4-gf46015e+5,12.1-5-g06c326c+20,12.1-5-g648ee80+3,12.1-5-gc2189d7+4,12.1-6-ga608fc0+1,12.1-7-g3349e2a+5,12.1-7-gfd75620+9,12.1-9-g577b946+5,12.1-9-gc4df26a+10
LSSTDataManagementBasePackage
diffimTools.py
Go to the documentation of this file.
1 from builtins import range
2 from builtins import object
3 #
4 # LSST Data Management System
5 # Copyright 2008-2016 LSST Corporation.
6 #
7 # This product includes software developed by the
8 # LSST Project (http://www.lsst.org/).
9 #
10 # This program is free software: you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation, either version 3 of the License, or
13 # (at your option) any later version.
14 #
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
19 #
20 # You should have received a copy of the LSST License Statement and
21 # the GNU General Public License along with this program. If not,
22 # see <http://www.lsstcorp.org/LegalNotices/>.
23 #
24 
25 # python
26 import time
27 import os
28 from collections import Counter
29 import numpy as np
30 
31 # all the c++ level classes and routines
32 from . import diffimLib
33 
34 # all the other LSST packages
35 import lsst.afw.geom as afwGeom
36 import lsst.afw.image as afwImage
37 import lsst.afw.table as afwTable
38 import lsst.afw.detection as afwDetect
39 import lsst.afw.math.mathLib as afwMath
40 from lsst.log import Log
41 import lsst.pex.config as pexConfig
42 from .makeKernelBasisList import makeKernelBasisList
43 
44 # Helper functions for ipDiffim; mostly viewing of results and writing
45 # debugging info to disk.
46 
47 #######
48 # Add noise
49 #######
50 
51 
52 def makeFlatNoiseImage(mi, seedStat=afwMath.MAX):
53  img = mi.getImage()
54  seed = int(10. * afwMath.makeStatistics(mi.getImage(), seedStat).getValue() + 1)
55  rdm = afwMath.Random(afwMath.Random.MT19937, seed)
56  rdmImage = img.Factory(img.getDimensions())
57  afwMath.randomGaussianImage(rdmImage, rdm)
58  return rdmImage
59 
60 
62  """Return a Poisson noise image based on im
63 
64  Uses numpy.random; you may wish to call numpy.random.seed first.
65 
66  @warning This uses an undocumented numpy API (the documented API
67  uses a single float expectation value instead of an array).
68 
69  @param[in] im image; the output image has the same dimensions and shape
70  and its expectation value is the value of im at each pixel
71  """
72  import numpy.random as rand
73  imArr = im.getArray()
74  noiseIm = im.Factory(im.getBBox())
75  noiseArr = noiseIm.getArray()
76 
77  intNoiseArr = rand.poisson(imArr)
78  noiseArr[:, :] = intNoiseArr.astype(noiseArr.dtype)
79  return noiseIm
80 
81 #######
82 # Make fake images for testing; one is a delta function (or narrow
83 # gaussian) and the other is a convolution of this with a spatially
84 # varying kernel.
85 #######
86 
87 
88 def fakeCoeffs():
89  kCoeffs = ((1.0, 0.0, 0.0),
90  (0.005, -0.000001, 0.000001),
91  (0.005, 0.000004, 0.000004),
92  (-0.001, -0.000030, 0.000030),
93  (-0.001, 0.000015, 0.000015),
94  (-0.005, -0.000050, 0.000050))
95  return kCoeffs
96 
97 
98 def makeFakeKernelSet(sizeCell=128, nCell=3,
99  deltaFunctionCounts=1.e4, tGaussianWidth=1.0,
100  addNoise=True, bgValue=100., display=False):
101 
102  from . import imagePsfMatch
103  configFake = imagePsfMatch.ImagePsfMatchConfig()
104  configFake.kernel.name = "AL"
105  subconfigFake = configFake.kernel.active
106  subconfigFake.alardNGauss = 1
107  subconfigFake.alardSigGauss = [2.5, ]
108  subconfigFake.alardDegGauss = [2, ]
109  subconfigFake.sizeCellX = sizeCell
110  subconfigFake.sizeCellY = sizeCell
111  subconfigFake.spatialKernelOrder = 1
112  subconfigFake.spatialModelType = "polynomial"
113  subconfigFake.singleKernelClipping = False # variance is a hack
114  subconfigFake.spatialKernelClipping = False # variance is a hack
115  if bgValue > 0.0:
116  subconfigFake.fitForBackground = True
117 
118  policyFake = pexConfig.makePolicy(subconfigFake)
119 
120  basisList = makeKernelBasisList(subconfigFake)
121  kSize = subconfigFake.kernelSize
122 
123  # This sets the final extent of each convolved delta function
124  gaussKernelWidth = sizeCell//2
125 
126  # This sets the scale over which pixels are correlated in the
127  # spatial convolution; should be at least as big as the kernel you
128  # are trying to fit for
129  spatialKernelWidth = kSize
130 
131  # Number of bad pixels due to convolutions
132  border = (gaussKernelWidth + spatialKernelWidth)//2
133 
134  # Make a fake image with a matrix of delta functions
135  totalSize = nCell * sizeCell + 2*border
136  tim = afwImage.ImageF(afwGeom.Extent2I(totalSize, totalSize))
137  for x in range(nCell):
138  for y in range(nCell):
139  tim.set(x*sizeCell + sizeCell//2 + border - 1,
140  y*sizeCell + sizeCell//2 + border - 1,
141  deltaFunctionCounts)
142 
143  # Turn this into stars with a narrow width; conserve counts
144  gaussFunction = afwMath.GaussianFunction2D(tGaussianWidth, tGaussianWidth)
145  gaussKernel = afwMath.AnalyticKernel(gaussKernelWidth, gaussKernelWidth, gaussFunction)
146  cim = afwImage.ImageF(tim.getDimensions())
147  afwMath.convolve(cim, tim, gaussKernel, True)
148  tim = cim
149 
150  # Trim off border pixels
151  bbox = gaussKernel.shrinkBBox(tim.getBBox(afwImage.LOCAL))
152  tim = afwImage.ImageF(tim, bbox, afwImage.LOCAL)
153 
154  # Now make a science image which is this convolved with some
155  # spatial function. Use input basis list.
156  polyFunc = afwMath.PolynomialFunction2D(1)
157  kCoeffs = fakeCoeffs()
158  nToUse = min(len(kCoeffs), len(basisList))
159 
160  # Make the full convolved science image
161  sKernel = afwMath.LinearCombinationKernel(afwMath.KernelList(basisList[:nToUse]), polyFunc)
162  sKernel.setSpatialParameters(kCoeffs[:nToUse])
163  sim = afwImage.ImageF(tim.getDimensions())
164  afwMath.convolve(sim, tim, sKernel, True)
165 
166  # Get the good subregion
167  bbox = sKernel.shrinkBBox(sim.getBBox(afwImage.LOCAL))
168 
169  # Add background
170  sim += bgValue
171 
172  # Watch out for negative values
173  tim += 2 * np.abs(np.min(tim.getArray()))
174 
175  # Add noise?
176  if addNoise:
177  sim = makePoissonNoiseImage(sim)
178  tim = makePoissonNoiseImage(tim)
179 
180  # And turn into MaskedImages
181  sim = afwImage.ImageF(sim, bbox, afwImage.LOCAL)
182  svar = afwImage.ImageF(sim, True)
183  smask = afwImage.MaskU(sim.getDimensions())
184  smask.set(0x0)
185  sMi = afwImage.MaskedImageF(sim, smask, svar)
186 
187  tim = afwImage.ImageF(tim, bbox, afwImage.LOCAL)
188  tvar = afwImage.ImageF(tim, True)
189  tmask = afwImage.MaskU(tim.getDimensions())
190  tmask.set(0x0)
191  tMi = afwImage.MaskedImageF(tim, tmask, tvar)
192 
193  if display:
194  import lsst.afw.display.ds9 as ds9
195  ds9.mtv(tMi, frame=1)
196  ds9.mtv(sMi, frame=2)
197 
198  # Finally, make a kernelSet from these 2 images
200  afwGeom.Extent2I(sizeCell * nCell,
201  sizeCell * nCell)),
202  sizeCell,
203  sizeCell)
204  stampHalfWidth = 2 * kSize
205  for x in range(nCell):
206  for y in range(nCell):
207  xCoord = x * sizeCell + sizeCell // 2
208  yCoord = y * sizeCell + sizeCell // 2
209  p0 = afwGeom.Point2I(xCoord - stampHalfWidth,
210  yCoord - stampHalfWidth)
211  p1 = afwGeom.Point2I(xCoord + stampHalfWidth,
212  yCoord + stampHalfWidth)
213  bbox = afwGeom.Box2I(p0, p1)
214  tsi = afwImage.MaskedImageF(tMi, bbox, afwImage.LOCAL)
215  ssi = afwImage.MaskedImageF(sMi, bbox, afwImage.LOCAL)
216 
217  kc = diffimLib.makeKernelCandidate(xCoord, yCoord, tsi, ssi, policyFake)
218  kernelCellSet.insertCandidate(kc)
219 
220  tMi.setXY0(0, 0)
221  sMi.setXY0(0, 0)
222  return tMi, sMi, sKernel, kernelCellSet, configFake
223 
224 
225 #######
226 # Background subtraction for ip_diffim
227 #######
228 
229 def backgroundSubtract(config, maskedImages):
230  backgrounds = []
231  t0 = time.time()
232  algorithm = config.algorithm
233  binsize = config.binSize
234  undersample = config.undersampleStyle
235  bctrl = afwMath.BackgroundControl(algorithm)
236  bctrl.setUndersampleStyle(undersample)
237  for maskedImage in maskedImages:
238  bctrl.setNxSample(maskedImage.getWidth()//binsize + 1)
239  bctrl.setNySample(maskedImage.getHeight()//binsize + 1)
240  image = maskedImage.getImage()
241  backobj = afwMath.makeBackground(image, bctrl)
242 
243  image -= backobj.getImageF()
244  backgrounds.append(backobj.getImageF())
245  del backobj
246 
247  t1 = time.time()
248  logger = Log.getLogger("ip.diffim.backgroundSubtract")
249  logger.debug("Total time for background subtraction : %.2f s", (t1-t0))
250  return backgrounds
251 
252 #######
253 # More coarse debugging
254 #######
255 
256 
257 def writeKernelCellSet(kernelCellSet, psfMatchingKernel, backgroundModel, outdir):
258  if not os.path.isdir(outdir):
259  os.makedirs(outdir)
260 
261  for cell in kernelCellSet.getCellList():
262  for cand in cell.begin(False): # False = include bad candidates
263  cand = diffimLib.KernelCandidateF.cast(cand)
264  if cand.getStatus() == afwMath.SpatialCellCandidate.GOOD:
265  xCand = int(cand.getXCenter())
266  yCand = int(cand.getYCenter())
267  idCand = cand.getId()
268  diffIm = cand.getDifferenceImage(diffimLib.KernelCandidateF.ORIG)
269  kernel = cand.getKernelImage(diffimLib.KernelCandidateF.ORIG)
270  diffIm.writeFits(os.path.join(outdir, 'diffim_c%d_x%d_y%d.fits' % (idCand, xCand, yCand)))
271  kernel.writeFits(os.path.join(outdir, 'kernel_c%d_x%d_y%d.fits' % (idCand, xCand, yCand)))
272 
273  # Diffim from spatial model
274  ski = afwImage.ImageD(kernel.getDimensions())
275  psfMatchingKernel.computeImage(ski, False, xCand, yCand)
276  sk = afwMath.FixedKernel(ski)
277  sbg = backgroundModel(xCand, yCand)
278  sdmi = cand.getDifferenceImage(sk, sbg)
279  sdmi.writeFits(os.path.join(outdir, 'sdiffim_c%d_x%d_y%d.fits' % (idCand, xCand, yCand)))
280 
281 #######
282 # Converting types
283 #######
284 
285 
286 def sourceToFootprintList(candidateInList, templateExposure, scienceExposure, kernelSize, config, log):
287  """ Takes an input list of Sources that were selected to constrain
288  the Psf-matching Kernel and turns them into a List of Footprints,
289  which are used to seed a set of KernelCandidates. The function
290  checks both the template and science image for masked pixels,
291  rejecting the Source if certain Mask bits (defined in config) are
292  set within the Footprint.
293 
294  @param candidateInList: Input list of Sources
295  @param templateExposure: Template image, to be checked for Mask bits in Source Footprint
296  @param scienceExposure: Science image, to be checked for Mask bits in Source Footprint
297  @param config: Config that defines the Mask planes that indicate an invalid Source and Bbox grow radius
298  @param log: Log for output
299 
300  @return a list of dicts having a "source" and "footprint" field, to be used for Psf-matching
301  """
302 
303  candidateOutList = []
304  fsb = diffimLib.FindSetBitsU()
305  badBitMask = 0
306  for mp in config.badMaskPlanes:
307  badBitMask |= afwImage.MaskU.getPlaneBitMask(mp)
308  bbox = scienceExposure.getBBox()
309 
310  # Size to grow Sources
311  if config.scaleByFwhm:
312  fpGrowPix = int(config.fpGrowKernelScaling * kernelSize + 0.5)
313  else:
314  fpGrowPix = config.fpGrowPix
315  log.info("Growing %d kernel candidate stars by %d pixels", len(candidateInList), fpGrowPix)
316 
317  for kernelCandidate in candidateInList:
318  if not type(kernelCandidate) == afwTable.SourceRecord:
319  raise RuntimeError("Candiate not of type afwTable.SourceRecord")
320  bm1 = 0
321  bm2 = 0
322  center = afwGeom.Point2I(scienceExposure.getWcs().skyToPixel(kernelCandidate.getCoord()))
323  if center[0] < bbox.getMinX() or center[0] > bbox.getMaxX():
324  continue
325  if center[1] < bbox.getMinY() or center[1] > bbox.getMaxY():
326  continue
327 
328  xmin = center[0] - fpGrowPix
329  xmax = center[0] + fpGrowPix
330  ymin = center[1] - fpGrowPix
331  ymax = center[1] + fpGrowPix
332 
333  # Keep object centered
334  if (xmin - bbox.getMinX()) < 0:
335  xmax += (xmin - bbox.getMinX())
336  xmin -= (xmin - bbox.getMinX())
337  if (ymin - bbox.getMinY()) < 0:
338  ymax += (ymin - bbox.getMinY())
339  ymin -= (ymin - bbox.getMinY())
340  if (bbox.getMaxX() - xmax) < 0:
341  xmin -= (bbox.getMaxX() - xmax)
342  xmax += (bbox.getMaxX() - xmax)
343  if (bbox.getMaxY() - ymax) < 0:
344  ymin -= (bbox.getMaxY() - ymax)
345  ymax += (bbox.getMaxY() - ymax)
346  if xmin > xmax or ymin > ymax:
347  continue
348 
349  kbbox = afwGeom.Box2I(afwGeom.Point2I(xmin, ymin), afwGeom.Point2I(xmax, ymax))
350  try:
351  fsb.apply(afwImage.MaskedImageF(templateExposure.getMaskedImage(), kbbox, False).getMask())
352  bm1 = fsb.getBits()
353  fsb.apply(afwImage.MaskedImageF(scienceExposure.getMaskedImage(), kbbox, False).getMask())
354  bm2 = fsb.getBits()
355  except Exception:
356  pass
357  else:
358  if not((bm1 & badBitMask) or (bm2 & badBitMask)):
359  candidateOutList.append({'source': kernelCandidate, 'footprint': afwDetect.Footprint(kbbox)})
360  log.info("Selected %d / %d sources for KernelCandidacy", len(candidateOutList), len(candidateInList))
361  return candidateOutList
362 
363 
364 def sourceTableToCandidateList(sourceTable, templateExposure, scienceExposure, kConfig, dConfig, log,
365  basisList, doBuild=False):
366  """Takes an input list of Sources, and turns them into
367  KernelCandidates for fitting of the Psf-matching kernel."""
368  kernelSize = basisList[0].getWidth()
369  footprintList = sourceToFootprintList(list(sourceTable), templateExposure, scienceExposure,
370  kernelSize, dConfig, log)
371  candList = []
372 
373  if doBuild and not basisList:
374  doBuild = False
375  else:
376  policy = pexConfig.makePolicy(kConfig)
377  visitor = diffimLib.BuildSingleKernelVisitorF(basisList, policy)
378 
379  policy = pexConfig.makePolicy(kConfig)
380  for cand in footprintList:
381  bbox = cand['footprint'].getBBox()
382  tmi = afwImage.MaskedImageF(templateExposure.getMaskedImage(), bbox)
383  smi = afwImage.MaskedImageF(scienceExposure.getMaskedImage(), bbox)
384  kCand = diffimLib.makeKernelCandidate(cand['source'], tmi, smi, policy)
385  if doBuild:
386  visitor.processCandidate(kCand)
387  kCand.setStatus(afwMath.SpatialCellCandidate.UNKNOWN)
388  candList.append(kCand)
389  return candList
390 
391 
392 #######
393 #
394 #######
395 
396 
397 class NbasisEvaluator(object):
398  """A functor to evaluate the Bayesian Information Criterion for the number of basis sets
399  going into the kernel fitting"""
400 
401  def __init__(self, psfMatchConfig, psfFwhmPixTc, psfFwhmPixTnc):
402  self.psfMatchConfig = psfMatchConfig
403  self.psfFwhmPixTc = psfFwhmPixTc
404  self.psfFwhmPixTnc = psfFwhmPixTnc
405  if not self.psfMatchConfig.kernelBasisSet == "alard-lupton":
406  raise RuntimeError("BIC only implemnted for AL (alard lupton) basis")
407 
408  def __call__(self, kernelCellSet, log):
409  d1, d2, d3 = self.psfMatchConfig.alardDegGauss
410  bicArray = {}
411  for d1i in range(1, d1+1):
412  for d2i in range(1, d2+1):
413  for d3i in range(1, d3+1):
414  dList = [d1i, d2i, d3i]
415  bicConfig = type(self.psfMatchConfig)(self.psfMatchConfig, alardDegGauss=dList)
416  kList = makeKernelBasisList(bicConfig, self.psfFwhmPixTc, self.psfFwhmPixTnc)
417  k = len(kList)
418  visitor = diffimLib.BuildSingleKernelVisitorF(kList, pexConfig.makePolicy(bicConfig))
419  visitor.setSkipBuilt(False)
420  kernelCellSet.visitCandidates(visitor, bicConfig.nStarPerCell)
421 
422  for cell in kernelCellSet.getCellList():
423  for cand in cell.begin(False): # False = include bad candidates
424  cand = diffimLib.KernelCandidateF.cast(cand)
425  if cand.getStatus() != afwMath.SpatialCellCandidate.GOOD:
426  continue
427  diffIm = cand.getDifferenceImage(diffimLib.KernelCandidateF.RECENT)
428  bbox = cand.getKernel(diffimLib.KernelCandidateF.RECENT).shrinkBBox(
429  diffIm.getBBox(afwImage.LOCAL))
430  diffIm = type(diffIm)(diffIm, bbox, True)
431  chi2 = diffIm.getImage().getArray()**2 / diffIm.getVariance().getArray()
432  n = chi2.shape[0] * chi2.shape[1]
433  bic = np.sum(chi2) + k * np.log(n)
434  if cand.getId() not in bicArray:
435  bicArray[cand.getId()] = {}
436  bicArray[cand.getId()][(d1i, d2i, d3i)] = bic
437 
438  bestConfigs = []
439  for candId in bicArray:
440  cconfig, cvals = list(bicArray[candId].keys()), list(bicArray[candId].values())
441  idx = np.argsort(cvals)
442  bestConfig = cconfig[idx[0]]
443  bestConfigs.append(bestConfig)
444 
445  counter = Counter(bestConfigs).most_common(3)
446  log.info("B.I.C. prefers basis complexity %s %d times; %s %d times; %s %d times",
447  counter[0][0], counter[0][1],
448  counter[1][0], counter[1][1],
449  counter[2][0], counter[2][1])
450  return counter[0][0], counter[1][0], counter[2][0]
boost::shared_ptr< Background > makeBackground(ImageT const &img, BackgroundControl const &bgCtrl)
A convenience function that uses function overloading to make the correct type of Background...
Definition: Background.h:467
def backgroundSubtract
Background subtraction for ip_diffim.
Definition: diffimTools.py:229
Configuration for image-to-image Psf matching.
Pass parameters to a Background object.
Definition: Background.h:61
An integer coordinate rectangle.
Definition: Box.h:53
A collection of SpatialCells covering an entire image.
Definition: SpatialCell.h:304
def writeKernelCellSet
More coarse debugging.
Definition: diffimTools.py:257
A kernel that is a linear combination of fixed basis kernels.
Definition: Kernel.h:811
void randomGaussianImage(ImageT *image, Random &rand)
Set image to random numbers with a gaussian N(0, 1) distribution.
Definition: RandomImage.cc:155
A set of pixels in an Image.
Definition: Footprint.h:62
def fakeCoeffs
Make fake images for testing; one is a delta function (or narrow gaussian) and the other is a convolu...
Definition: diffimTools.py:88
Statistics makeStatistics(afwImage::Mask< afwImage::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl)
Specialization to handle Masks.
Definition: Statistics.cc:1107
def makeFlatNoiseImage
Add noise.
Definition: diffimTools.py:52
Record class that contains measurements made on a single exposure.
Definition: Source.h:80
void convolve(OutImageT &convolvedImage, InImageT const &inImage, KernelT const &kernel, bool doNormalize, bool doCopyEdge=false)
Old, deprecated version of convolve.
A kernel described by a function.
Definition: Kernel.h:625
def sourceToFootprintList
Converting types.
Definition: diffimTools.py:286
A class that can be used to generate sequences of random numbers according to a number of different a...
Definition: Random.h:62
A kernel created from an Image.
Definition: Kernel.h:548
std::vector< boost::shared_ptr< Kernel > > KernelList
Definition: Kernel.h:539