lsst.meas.modelfit  15.0-3-g150fc43+8
psfContinued.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 #
3 # LSST Data Management System
4 # Copyright 2008-2014 LSST Corporation.
5 #
6 # This product includes software developed by the
7 # LSST Project (http://www.lsst.org/).
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 LSST License Statement and
20 # the GNU General Public License along with this program. If not,
21 # see <http://www.lsstcorp.org/LegalNotices/>.
22 #
23 
24 from __future__ import absolute_import, division, print_function
25 
26 # all new classes here are accessed via registries, not direct imports.
27 __all__ = (
28  "GeneralPsfFitterComponentConfig",
29  "GeneralPsfFitterConfig"
30 )
31 
32 import lsst.pex.config
33 import lsst.meas.base
34 from .psf import (
35  GeneralPsfFitterControl, GeneralPsfFitterComponentControl,
36  GeneralPsfFitter, GeneralPsfFitterAlgorithm,
37  DoubleShapeletPsfApproxAlgorithm, DoubleShapeletPsfApproxControl
38 )
39 
40 
41 lsst.meas.base.wrapSimpleAlgorithm(
42  DoubleShapeletPsfApproxAlgorithm,
43  Control=DoubleShapeletPsfApproxControl,
44  module='lsst.meas.modelfit',
45  name='modelfit_DoubleShapeletPsfApprox',
46  executionOrder=lsst.meas.base.BasePlugin.SHAPE_ORDER
47 )
48 
49 
50 GeneralPsfFitterComponentConfig = lsst.pex.config.makeConfigClass(
51  GeneralPsfFitterComponentControl,
52  module='lsst.meas.modelfit'
53 )
54 GeneralPsfFitterConfig = lsst.pex.config.makeConfigClass(
55  GeneralPsfFitterControl,
56  module='lsst.meas.modelfit'
57 )
58 GeneralPsfFitter.ConfigClass = GeneralPsfFitterConfig
59 
60 
61 class GeneralShapeletPsfApproxConfig(lsst.pex.config.Config):
62  models = lsst.pex.config.ConfigDictField(
63  keytype=str,
64  itemtype=GeneralPsfFitterConfig,
65  doc="a dictionary of models that can be used to fit the PSF",
66  default={} # populated in setDefaults; can't do it on a single line
67  )
68  sequence = lsst.pex.config.ListField(
69  dtype=str,
70  doc=("a sequence of model names indicating which models should be fit,"
71  " and their order"),
72  default=["DoubleShapelet"]
73  )
74 
75  def setDefaults(self):
76  super(GeneralShapeletPsfApproxConfig, self).setDefaults()
77  self.models["SingleGaussian"] = GeneralPsfFitterConfig()
78  self.models["SingleGaussian"].inner.order = -1
79  self.models["SingleGaussian"].primary.order = 0
80  self.models["SingleGaussian"].wings.order = -1
81  self.models["SingleGaussian"].outer.order = -1
82  self.models["DoubleGaussian"] = GeneralPsfFitterConfig()
83  self.models["DoubleGaussian"].inner.order = -1
84  self.models["DoubleGaussian"].primary.order = 0
85  self.models["DoubleGaussian"].wings.order = 0
86  self.models["DoubleGaussian"].outer.order = -1
87  self.models["DoubleShapelet"] = GeneralPsfFitterConfig()
88  self.models["DoubleShapelet"].inner.order = -1
89  self.models["DoubleShapelet"].primary.order = 2
90  self.models["DoubleShapelet"].wings.order = 1
91  self.models["DoubleShapelet"].outer.order = -1
92  self.models["Full"] = GeneralPsfFitterConfig()
93  self.models["Full"].inner.order = 0
94  self.models["Full"].primary.order = 4
95  self.models["Full"].wings.order = 4
96  self.models["Full"].outer.order = 0
97 
98  def validate(self):
99  super(GeneralShapeletPsfApproxConfig, self).validate()
100  if len(self.sequence) < 1:
101  raise ValueError("sequence must have at least one element")
102  for m in self.sequence:
103  if m not in self.models:
104  raise KeyError(
105  "All elements in sequence must be keys in models dict"
106  )
107 
108 
110  """Mixin base class for fitting shapelet approximations to the PSF model
111 
112  This class does almost all of the work for its two derived classes,
113  GeneralShapeletPsfApproxSingleFramePlugin and
114  GeneralShapeletPsfApproxForcedPlugin, which simply adapt it to the
115  slightly different interfaces for single-frame and forced measurement. It
116  in turn delegates its work to the C++ GeneralPsfFitter class; it holds
117  sequence of these corresponding to different models (generally with
118  increasing complexity). Each GeneralPsfFitter starts with the result of
119  the previous one as an input, using GeneralPsfFitter::adapt to hopefully
120  allow these previous fits to reduce the time spent on the next one.
121 
122  At present, this plugin does not define any failure flags, which will
123  almost certainly have to be changed in the future. So far, however, I
124  haven't actually seen it fail on any PSFs I've given it, so I'll wait
125  until we can run on large enough data volumes to see what the actual
126  failure modes are, instead of trying to guess them in advance.
127  """
128 
129  def __init__(self, config, name, schema):
130  """Initialize the plugin, creating a sequence of GeneralPsfFitter
131  instances to do the fitting and MultiShapeletFunctionKey instances to
132  save the results to a record.
133  """
134  self.sequence = []
135  for m in config.sequence:
136  fitter = GeneralPsfFitterAlgorithm(
137  config.models[m].makeControl(),
138  schema,
139  schema[name][m].getPrefix()
140  )
141  self.sequence.append((fitter, schema[name][m].getPrefix()))
142 
143  def measure(self, measRecord, exposure):
144  """Fit the configured sequence of models the given Exposure's Psf, as
145  evaluated at measRecord.getCentroid(), then save the results to
146  measRecord.
147  """
148  if not exposure.hasPsf():
150  "GeneralShapeletPsfApprox requires Exposure to have a Psf")
151  psf = exposure.getPsf()
152  psfImage = psf.computeKernelImage(measRecord.getCentroid())
153  psfShape = psf.computeShape(measRecord.getCentroid())
154  lastError = None
155  lastModel = None
156  # Fit the first element in the sequence, using the PSFs moments to
157  # initialize the parameters For every other element in the fitting
158  # sequence, use the previous fit to initialize the parameters
159  lastResult = None
160  for fitter, name in self.sequence:
161  try:
162  if lastModel is None:
163  fitter.measure(measRecord, psfImage, psfShape)
164  else:
165  fitter.measure(measRecord, psfImage,
166  fitter.adapt(lastResult, lastModel))
167  lastResult = measRecord.get(fitter.getKey())
168  lastModel = fitter.getModel()
169  except lsst.meas.base.baseMeasurement.FATAL_EXCEPTIONS:
170  raise
171  except lsst.meas.base.MeasurementError as error:
172  fitter.fail(measRecord, error.cpp)
173  lastError = error
174  except Exception as error:
175  fitter.fail(measRecord)
176  lastError = error
177  # When we are done with all the fitters, raise the last error if there
178  # was one. This gives the calling task a chance to do whatever it
179  # wants
180  if lastError is not None:
181  raise lastError
182 
183  # This plugin doesn't need to set a flag on fail, because it should have
184  # been done already by the individual fitters in the sequence
185  def fail(self, measRecord, error=None):
186  pass
187 
188 
189 class GeneralShapeletPsfApproxSingleFrameConfig(
190  lsst.meas.base.SingleFramePluginConfig,
191  GeneralShapeletPsfApproxConfig
192 ):
193 
194  def setDefaults(self):
195  lsst.meas.base.SingleFramePluginConfig.setDefaults(self)
196  GeneralShapeletPsfApproxConfig.setDefaults(self)
197 
198 
199 @lsst.meas.base.register("modelfit_GeneralShapeletPsfApprox")
201  lsst.meas.base.SingleFramePlugin,
202  GeneralShapeletPsfApproxMixin
203 ):
204  """Minimal subclass of GeneralShapeletPsfApproxMixin to conform to the
205  single-frame measurement API.
206 
207  This class simply provides __init__ and measure methods that matched the
208  SingleFramePlugin signatures and delegate to the
209  GeneralShapeletPsfApproxMixin's implementations.
210  """
211  ConfigClass = GeneralShapeletPsfApproxSingleFrameConfig
212 
213  @staticmethod
215  return 1.0
216 
217  def __init__(self, config, name, schema, metadata):
218  GeneralShapeletPsfApproxMixin.__init__(self, config, name, schema)
219  lsst.meas.base.SingleFramePlugin.__init__(self, config, name, schema,
220  metadata)
221 
222  def measure(self, measRecord, exposure):
223  GeneralShapeletPsfApproxMixin.measure(self, measRecord, exposure)
224 
225  def fail(self, measRecord, error=None):
226  GeneralShapeletPsfApproxMixin.fail(self, measRecord, error)
227 
228 
230  lsst.meas.base.ForcedPluginConfig,
231  GeneralShapeletPsfApproxConfig
232 ):
233 
234  def setDefaults(self):
235  lsst.meas.base.ForcedPluginConfig.setDefaults(self)
236  GeneralShapeletPsfApproxConfig.setDefaults(self)
237 
238 
239 @lsst.meas.base.register("modelfit_GeneralShapeletPsfApprox")
241  lsst.meas.base.ForcedPlugin,
242  GeneralShapeletPsfApproxMixin
243 ):
244  """Minimal subclass of GeneralShapeletPsfApproxMixin to conform to the
245  forced measurement API.
246 
247  This class simply provides __init__ and measure methods that matched the
248  ForcedPlugin signatures and delegate to the
249  GeneralShapeletPsfApproxMixin's implementations.
250  """
251  ConfigClass = GeneralShapeletPsfApproxForcedConfig
252 
253  @staticmethod
255  return 1.0
256 
257  def __init__(self, config, name, schemaMapper, metadata):
258  GeneralShapeletPsfApproxMixin.__init__(self, config, name,
259  schemaMapper.editOutputSchema())
260  lsst.meas.base.ForcedPlugin.__init__(self, config, name, schemaMapper,
261  metadata)
262 
263  def measure(self, measRecord, exposure, refRecord, refWcs):
264  GeneralShapeletPsfApproxMixin.measure(self, measRecord, exposure)
265 
266  def fail(self, measRecord, error=None):
267  GeneralShapeletPsfApproxMixin.fail(self, measRecord, error)
def __init__(self, config, name, schemaMapper, metadata)
def measure(self, measRecord, exposure, refRecord, refWcs)