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