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