lsst.ip.diffim  14.0-12-g5218728
diaSourceAnalysis.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
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 from __future__ import absolute_import, division, print_function
25 
26 from optparse import OptionParser
27 
28 from builtins import input
29 from builtins import range
30 from builtins import object
31 
32 import lsst.afw.image as afwImage
33 import lsst.afw.geom as afwGeom
34 import lsst.afw.detection as afwDet
35 from lsst.log import Log
36 import lsst.pex.policy as pexPolicy
37 import lsst.daf.persistence as dafPersist
38 import lsst.daf.base as dafBase
39 import numpy as num
40 import lsst.afw.display.ds9 as ds9
41 import lsst.pex.config as pexConfig
42 
43 scaling = 5
44 
45 
47  """Parse the command line options."""
48  parser = OptionParser(
49  usage="""%prog cdDiffSources crDiffExposure
50 
51 Read in sources and test for junk""")
52  options, args = parser.parse_args()
53  if len(args) != 2:
54  parser.error("incorrect number of arguments")
55  return options, args
56 
57 
58 def readSourceSet(boostFile):
59  loc = dafPersist.LogicalLocation(boostFile)
60  storageList = dafPersist.StorageList()
61  additionalData = dafBase.PropertySet()
62  persistence = dafPersist.Persistence.getPersistence(pexPolicy.Policy())
63  storageList.append(persistence.getRetrieveStorage("BoostStorage", loc))
64  psv = persistence.unsafeRetrieve("PersistableSourceVector", storageList, additionalData)
65  return psv.getSources()
66 
67 
68 class DiaSourceAnalystConfig(pexConfig.Config):
69  srcBadMaskPlanes = pexConfig.ListField(
70  dtype=str,
71  doc="""Mask planes that lead to an invalid detection.
72  Options: NO_DATA EDGE SAT BAD CR INTRP
73  E.g. : NO_DATA SAT BAD allows CR-masked and interpolated pixels""",
74  default=("NO_DATA", "EDGE", "SAT", "BAD")
75  )
76  fBadPixels = pexConfig.Field(
77  dtype=float,
78  doc="Fraction of bad pixels allowed in footprint",
79  default=0.1
80  )
81  fluxPolarityRatio = pexConfig.Field(
82  dtype=float,
83  doc="Minimum fraction of flux in correct-polarity pixels",
84  default=0.75
85  )
86  nPolarityRatio = pexConfig.Field(
87  dtype=float,
88  doc="Minimum fraction of correct-polarity pixels in unmasked subset",
89  default=0.7
90  )
91  nMaskedRatio = pexConfig.Field(
92  dtype=float,
93  doc="Minimum fraction of correct-polarity unmasked to masked pixels",
94  default=0.6,
95  )
96  nGoodRatio = pexConfig.Field(
97  dtype=float,
98  doc="Minimum fraction of correct-polarity unmasked to all pixels",
99  default=0.5
100  )
101 
102 
103 class DiaSourceAnalyst(object):
104 
105  def __init__(self, config):
106  self.config = config
107  self.log = Log.getLogger("ip.diffim.DiaSourceAnalysis")
108 
109  self.bitMask = 0
110  srcBadMaskPlanes = self.config.srcBadMaskPlanes
111  for maskPlane in srcBadMaskPlanes:
112  self.bitMask |= afwImage.Mask.getPlaneBitMask(maskPlane)
113 
114  def countDetected(self, mask):
115  idxP = num.where(mask & afwImage.Mask.getPlaneBitMask("DETECTED"))
116  idxN = num.where(mask & afwImage.Mask.getPlaneBitMask("DETECTED_NEGATIVE"))
117  return len(idxP[0]), len(idxN[0])
118 
119  def countMasked(self, mask):
120  idxM = num.where(mask & self.bitMask)
121  return len(idxM[0])
122 
123  def countPolarity(self, mask, pixels):
124  unmasked = ((mask & self.bitMask) == 0)
125  idxP = num.where((pixels >= 0) & unmasked)
126  idxN = num.where((pixels < 0) & unmasked)
127  fluxP = num.sum(pixels[idxP])
128  fluxN = num.sum(pixels[idxN])
129  return len(idxP[0]), len(idxN[0]), fluxP, fluxN
130 
131  def testSource(self, source, subMi):
132  imArr, maArr, varArr = subMi.getArrays()
133  flux = source.getApFlux()
134 
135  nPixels = subMi.getWidth() * subMi.getHeight()
136  nPos, nNeg, fPos, fNeg = self.countPolarity(maArr, imArr)
137  nDetPos, nDetNeg = self.countDetected(maArr)
138  nMasked = self.countMasked(maArr)
139  assert(nPixels == (nMasked + nPos + nNeg))
140 
141  # 1) Too many pixels in the detection are masked
142  fMasked = (nMasked / nPixels)
143  fMaskedTol = self.config.fBadPixels
144  if fMasked > fMaskedTol:
145  self.log.debug("Candidate %d : BAD fBadPixels %.2f > %.2f", source.getId(), fMasked, fMaskedTol)
146  return False
147 
148  if flux > 0:
149  # positive-going source
150  fluxRatio = fPos / (fPos + abs(fNeg))
151  ngoodRatio = nPos / nPixels
152  maskRatio = nPos / (nPos + nMasked)
153  npolRatio = nPos / (nPos + nNeg)
154  else:
155  # negative-going source
156  fluxRatio = abs(fNeg) / (fPos + abs(fNeg))
157  ngoodRatio = nNeg / nPixels
158  maskRatio = nNeg / (nNeg + nMasked)
159  npolRatio = nNeg / (nNeg + nPos)
160 
161  # 2) Not enough flux in unmasked correct-polarity pixels
162  fluxRatioTolerance = self.config.fluxPolarityRatio
163  if fluxRatio < fluxRatioTolerance:
164  self.log.debug("Candidate %d : BAD flux polarity %.2f < %.2f (pos=%.2f neg=%.2f)",
165  source.getId(), fluxRatio, fluxRatioTolerance, fPos, fNeg)
166  return False
167 
168  # 3) Not enough unmasked pixels of correct polarity
169  polarityTolerance = self.config.nPolarityRatio
170  if npolRatio < polarityTolerance:
171  self.log.debug("Candidate %d : BAD polarity count %.2f < %.2f (pos=%d neg=%d)",
172  source.getId(), npolRatio, polarityTolerance, nPos, nNeg)
173  return False
174 
175  # 4) Too many masked vs. correct polarity pixels
176  maskedTolerance = self.config.nMaskedRatio
177  if maskRatio < maskedTolerance:
178  self.log.debug("Candidate %d : BAD unmasked count %.2f < %.2f (pos=%d neg=%d mask=%d)",
179  source.getId(), maskRatio, maskedTolerance, nPos, nNeg, nMasked)
180  return False
181 
182  # 5) Too few unmasked, correct polarity pixels
183  ngoodTolerance = self.config.nGoodRatio
184  if ngoodRatio < ngoodTolerance:
185  self.log.debug("Candidate %d : BAD good pixel count %.2f < %.2f (pos=%d neg=%d tot=%d)",
186  source.getId(), ngoodRatio, ngoodTolerance, nPos, nNeg, nPixels)
187  return False
188 
189  self.log.debug("Candidate %d : OK flux=%.2f nPos=%d nNeg=%d nTot=%d nDetPos=%d nDetNeg=%d fPos=%.2f fNeg=%2f",
190  source.getId(), flux, nPos, nNeg, nPixels, nDetPos, nDetNeg, fPos, fNeg)
191  return True
192 
193 
194 def main():
195  """Main program"""
196  options, args = parseOptions()
197  (crDiffSourceFile, crDiffExposureFile) = args
198 
199  crDiffSources = readSourceSet(crDiffSourceFile)
200  crDiffExposure = afwImage.ExposureF(crDiffExposureFile)
201 
202  analyst = DiaSourceAnalyst()
203 
204  expX0 = crDiffExposure.getX0()
205  expY0 = crDiffExposure.getY0()
206  expX1 = expX0 + crDiffExposure.getWidth() - 1
207  expY1 = expY0 + crDiffExposure.getHeight() - 1
208 
209  for i in range(crDiffSources.size()):
210  crDiffSource = crDiffSources[i]
211 
212  # TODO This segfaults; revisit once the stack stabilizes
213  # footprint = crDiffSource.getFootprint()
214  # bbox = footprint.getBBox()
215 
216  xAstrom = crDiffSource.getXAstrom()
217  yAstrom = crDiffSource.getYAstrom()
218  Ixx = max(1.0, crDiffSource.getIxx())
219  Iyy = max(1.0, crDiffSource.getIyy())
220  x0 = max(expX0, int(xAstrom - scaling * Ixx))
221  x1 = min(expX1, int(xAstrom + scaling * Ixx))
222  y0 = max(expY0, int(yAstrom - scaling * Iyy))
223  y1 = min(expY1, int(yAstrom + scaling * Iyy))
224  bbox = afwGeom.Box2I(afwGeom.Point2I(x0, y0),
225  afwGeom.Point2I(x1, y1))
226  subExp = afwImage.ExposureF(crDiffExposure, bbox)
227  subMi = subExp.getMaskedImage()
228  imArr, maArr, varArr = subMi.getArrays()
229 
230  if analyst.testSource(crDiffSource, subMi):
231  ds9.mtv(subExp, frame=1)
232  input('Next: ')
233 if __name__ == "__main__":
234  main()