LSSTApplications  1.1.2+25,10.0+13,10.0+132,10.0+133,10.0+224,10.0+41,10.0+8,10.0-1-g0f53050+14,10.0-1-g4b7b172+19,10.0-1-g61a5bae+98,10.0-1-g7408a83+3,10.0-1-gc1e0f5a+19,10.0-1-gdb4482e+14,10.0-11-g3947115+2,10.0-12-g8719d8b+2,10.0-15-ga3f480f+1,10.0-2-g4f67435,10.0-2-gcb4bc6c+26,10.0-28-gf7f57a9+1,10.0-3-g1bbe32c+14,10.0-3-g5b46d21,10.0-4-g027f45f+5,10.0-4-g86f66b5+2,10.0-4-gc4fccf3+24,10.0-40-g4349866+2,10.0-5-g766159b,10.0-5-gca2295e+25,10.0-6-g462a451+1
LSSTDataManagementBasePackage
calibrate.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008, 2009, 2010, 2011 LSST Corporation.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <http://www.lsstcorp.org/LegalNotices/>.
21 #
22 import math
23 
24 import lsst.daf.base as dafBase
25 import lsst.pex.config as pexConfig
26 import lsst.afw.math as afwMath
27 import lsst.afw.table as afwTable
28 import lsst.meas.algorithms as measAlg
29 import lsst.meas.base
30 import lsst.pipe.base as pipeBase
31 from lsst.meas.photocal import PhotoCalTask
32 from lsst.meas.astrom import ANetAstrometryTask
33 from .repair import RepairTask
34 from .measurePsf import MeasurePsfTask
35 
36 class InitialPsfConfig(pexConfig.Config):
37  """!Describes the initial PSF used for detection and measurement before we do PSF determination."""
38 
39  model = pexConfig.ChoiceField(
40  dtype = str,
41  doc = "PSF model type",
42  default = "SingleGaussian",
43  allowed = {
44  "SingleGaussian": "Single Gaussian model",
45  "DoubleGaussian": "Double Gaussian model",
46  },
47  )
48  pixelScale = pexConfig.Field(
49  dtype = float,
50  doc = "Pixel size (arcsec). Only needed if no Wcs is provided",
51  default = 0.25,
52  )
53  fwhm = pexConfig.Field(
54  dtype = float,
55  doc = "FWHM of PSF model (arcsec)",
56  default = 1.0,
57  )
58  size = pexConfig.Field(
59  dtype = int,
60  doc = "Size of PSF model (pixels)",
61  default = 15,
62  )
63 
64 class CalibrateConfig(pexConfig.Config):
65  initialPsf = pexConfig.ConfigField(dtype=InitialPsfConfig, doc=InitialPsfConfig.__doc__)
66  doBackground = pexConfig.Field(
67  dtype = bool,
68  doc = "Subtract background (after computing it, if not supplied)?",
69  default = True,
70  )
71  doPsf = pexConfig.Field(
72  dtype = bool,
73  doc = "Perform PSF fitting?",
74  default = True,
75  )
76  doAstrometry = pexConfig.Field(
77  dtype = bool,
78  doc = "Compute astrometric solution?",
79  default = True,
80  )
81  doPhotoCal = pexConfig.Field(
82  dtype = bool,
83  doc = "Compute photometric zeropoint?",
84  default = True,
85  )
86  background = pexConfig.ConfigField(
87  dtype = measAlg.estimateBackground.ConfigClass,
88  doc = "Background estimation configuration"
89  )
90  repair = pexConfig.ConfigurableField(target = RepairTask, doc = "")
91  detection = pexConfig.ConfigurableField(
92  target = measAlg.SourceDetectionTask,
93  doc = "Initial (high-threshold) detection phase for calibration",
94  )
95  initialMeasurement = pexConfig.ConfigurableField(
96  target = lsst.meas.base.SingleFrameMeasurementTask,
97  doc = "Initial measurements used to feed PSF determination and aperture correction determination",
98  )
99  measurePsf = pexConfig.ConfigurableField(target = MeasurePsfTask, doc = "")
100  measurement = pexConfig.ConfigurableField(
101  target = lsst.meas.base.SingleFrameMeasurementTask,
102  doc = "Post-PSF-determination measurements used to feed other calibrations",
103  )
104  astrometry = pexConfig.ConfigurableField(
105  target = ANetAstrometryTask,
106  doc = "fit WCS of exposure",
107  )
108  photocal = pexConfig.ConfigurableField(
109  target = PhotoCalTask,
110  doc = "peform photometric calibration",
111  )
112 
113  def validate(self):
114  pexConfig.Config.validate(self)
115  if self.doPhotoCal and not self.doAstrometry:
116  raise ValueError("Cannot do photometric calibration without doing astrometric matching")
117 
118  def setDefaults(self):
119  self.detection.includeThresholdMultiplier = 10.0
120  self.initialMeasurement.algorithms.names -= ["correctfluxes", "classification.extendedness"]
121  initflags = [x for x in self.measurePsf.starSelector["catalog"].badStarPixelFlags]
122  self.measurePsf.starSelector["catalog"].badStarPixelFlags.extend(initflags)
123  self.background.binSize = 1024
124 
125 ## \addtogroup LSST_task_documentation
126 ## \{
127 ## \page CalibrateTask
128 ## \ref CalibrateTask_ "CalibrateTask"
129 ## \copybrief CalibrateTask
130 ## \}
131 
132 class CalibrateTask(pipeBase.Task):
133  """!
134 \anchor CalibrateTask_
135 
136 \brief Calibrate an exposure: measure PSF, subtract background, measure astrometry and photometry
137 
138 \section pipe_tasks_calibrate_Contents Contents
139 
140  - \ref pipe_tasks_calibrate_Purpose
141  - \ref pipe_tasks_calibrate_Initialize
142  - \ref pipe_tasks_calibrate_IO
143  - \ref pipe_tasks_calibrate_Config
144  - \ref pipe_tasks_calibrate_Metadata
145  - \ref pipe_tasks_calibrate_Debug
146  - \ref pipe_tasks_calibrate_Example
147 
148 \section pipe_tasks_calibrate_Purpose Description
149 
150 \copybrief CalibrateTask
151 
152 Calculate an Exposure's zero-point given a set of flux measurements of stars matched to an input catalogue.
153 The type of flux to use is specified by CalibrateConfig.fluxField.
154 
155 The algorithm clips outliers iteratively, with parameters set in the configuration.
156 
157 \note This task can adds fields to the schema, so any code calling this task must ensure that
158 these columns are indeed present in the input match list; see \ref pipe_tasks_calibrate_Example
159 
160 \section pipe_tasks_calibrate_Initialize Task initialisation
161 
162 \copydoc \_\_init\_\_
163 
164 CalibrateTask delegates most of its work to a set of sub-Tasks:
165 <DL>
166 <DT> repair \ref RepairTask_ "RepairTask"
167 <DD> Interpolate over defects such as bad columns and cosmic rays. This task is called twice; once
168 before the %measurePsf step and again after the PSF has been measured.
169 <DT> detection \ref SourceDetectionTask_ "SourceDetectionTask"
170 <DD> Initial (high-threshold) detection phase for calibration
171 <DT> initialMeasurement \ref SourceMeasurementTask_ "SourceMeasurementTask"
172 <DD> Make the initial measurements used to feed PSF determination and aperture correction determination
173 <DT> astrometry \ref AstrometryTask_ "AstrometryTask"
174 <DD> Solve the astrometry. May be disabled by setting CalibrateTaskConfig.doAstrometry to be False.
175 This task is called twice; once before the %measurePsf step and again after the PSF has been measured.
176 <DT> %measurePsf \ref MeasurePsfTask_ "MeasurePsfTask"
177 <DD> Estimate the PSF. May be disabled by setting CalibrateTaskConfig.doPsf to be False. If requested
178 the astrometry is solved before this is called, so if you disable the astrometry the %measurePsf
179 task won't have access to objects positions.
180 <DT> measurement \ref SourceMeasurementTask_ "SourceMeasurementTask"
181 <DD> Post-PSF-determination measurements used to feed other calibrations
182 <DT> photocal \ref PhotoCalTask_ "PhotoCalTask"
183 <DD> Solve for the photometric zeropoint.
184 May be disabled by setting CalibrateTaskConfig.doPhotoCal to be False.
185 \em N.b. Requires that \c astrometry was successfully run.
186 </DL>
187 
188 You can replace any of these subtasks if you wish, see \ref calibrate_MyAstrometryTask.
189 \note These task APIs are not well controlled, so replacing a task is a matter of matching
190 a poorly specified interface. We will be working on this over the first year of construction.
191 
192 \section pipe_tasks_calibrate_IO Invoking the Task
193 
194 \copydoc run
195 
196 \section pipe_tasks_calibrate_Config Configuration parameters
197 
198 See \ref CalibrateConfig
199 
200 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
201 
202 \section pipe_tasks_calibrate_Metadata Quantities set in Metadata
203 
204 <DL>
205 <DT>Task metadata
206 <DD>
207 <DL>
208 <DT> MAGZERO <DD> Measured zeropoint (DN per second)
209 </DL>
210 
211 <DT> Exposure metadata
212 <DD>
213 <DL>
214 <DT> MAGZERO_RMS <DD> MAGZERO's RMS == return.sigma
215 <DT> MAGZERO_NOBJ <DD> Number of stars used == return.ngood
216 <DT> COLORTERM1 <DD> ?? (always 0.0)
217 <DT> COLORTERM2 <DD> ?? (always 0.0)
218 <DT> COLORTERM3 <DD> ?? (always 0.0)
219 </DL>
220 </DL>
221 
222 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
223 
224 \section pipe_tasks_calibrate_Debug Debug variables
225 
226 The \link lsst.pipe.base.cmdLineTask.CmdLineTask command line task\endlink interface supports a
227 flag \c -d to import \b debug.py from your \c PYTHONPATH; see \ref baseDebug for more about \b debug.py files.
228 
229 The calibrate task has a debug dictionary with keys which correspond to stages of the CalibrationTask
230 processing:
231 <DL>
232 <DT>repair
233 <DD> Fixed defects and masked cosmic rays with a guessed PSF. Action: show the exposure.
234 <DT>background
235 <DD> Subtracted background (no sources masked). Action: show the exposure
236 <DT>PSF_repair
237 <DD> Fixed defects and removed cosmic rays with an estimated PSF. Action: show the exposure
238 <DT>PSF_background
239 <DD> Subtracted background (calibration sources masked). Action: show the exposure
240 <DT>calibrate
241 <DD> Just before astro/photo calibration. Action: show the exposure, and
242  - sources as smallish green o
243  - matches (if exposure has a Wcs).
244  - catalog position as a largish yellow +
245  - source position as a largish red x
246 </DL>
247 The values are the \c ds9 frame to display in (if >= 1); if <= 0, nothing's displayed.
248 There's an example \ref pipe_tasks_calibrate_Debug_example "here".
249 
250 Some subtasks may also have their own debug information; see individual Task documentation.
251 
252 \section pipe_tasks_calibrate_Example A complete example of using CalibrateTask
253 
254 This code is in \link calibrateTask.py\endlink in the examples directory, and can be run as \em e.g.
255 \code
256 examples/calibrateTask.py --ds9
257 \endcode
258 \dontinclude calibrateTask.py
259 
260 Import the task (there are some other standard imports; read the file if you're curious)
261 \skipline CalibrateTask
262 
263 Create the detection task
264 \skip CalibrateTask.ConfigClass
265 \until config=config
266 Note that we're using a custom AstrometryTask (because we don't have a valid astrometric catalogue handy);
267 see \ref calibrate_MyAstrometryTask.
268 
269 We're now ready to process the data (we could loop over multiple exposures/catalogues using the same
270 task objects) and unpack the results
271 \skip loadData
272 \until sources
273 
274 We then might plot the results (\em e.g. if you set \c --ds9 on the command line)
275 \skip display
276 \until dot
277 
278 \subsection calibrate_MyAstrometryTask Using a Custom Astrometry Task
279 
280 The first thing to do is define my own task:
281 \dontinclude calibrateTask.py
282 \skip MyAstrometryTask
283 \skip MyAstrometryTask
284 \until super
285 
286 Then we need our own \c run method. First unpack the filtername and wcs
287 \skip run
288 \until wcs
289 Then build a "reference catalog" by shamelessly copying the catalog of detected sources
290 \skip schema
291 \until get("photometric")
292 (you need to set "flux" as well as \c filterName due to a bug in the photometric calibration code;
293 <A HREF=https://jira.lsstcorp.org/browse/DM-933>DM-933</A>).
294 
295 Then "match" by zipping up the two catalogs,
296 \skip matches
297 \until append
298 and finally return the desired results.
299 \skip return
300 \until )
301 
302 <HR>
303 \anchor pipe_tasks_calibrate_Debug_example
304 To investigate the \ref pipe_tasks_calibrate_Debug, put something like
305 \code{.py}
306  import lsstDebug
307  def DebugInfo(name):
308  di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
309  if name == "lsst.pipe.tasks.calibrate":
310  di.display = dict(
311  repair = 1,
312  calibrate = 2,
313  )
314 
315  return di
316 
317  lsstDebug.Info = DebugInfo
318 \endcode
319 into your debug.py file and run calibrateTask.py with the \c --debug flag.
320  """
321  ConfigClass = CalibrateConfig
322  _DefaultName = "calibrate"
323 
324  def __init__(self, **kwargs):
325  """!
326  Create the calibration task
327 
328  \param **kwargs keyword arguments to be passed to lsst.pipe.base.task.Task.__init__
329  """
330  pipeBase.Task.__init__(self, **kwargs)
331 
332  # the calibrate Source Catalog is divided into two catalogs to allow measurement to be run twice
333  # schema1 contains everything except what is added by the second measurement task.
334  # Before the second measurement task is run, self.schemaMapper transforms the sources into
335  # the final output schema, at the same time renaming the measurement fields to "initial_"
336  self.schema1 = afwTable.SourceTable.makeMinimalSchema()
338  self.makeSubtask("repair")
339  self.makeSubtask("detection", schema=self.schema1)
340  beginInitial = self.schema1.getFieldCount()
341  self.makeSubtask("initialMeasurement", schema=self.schema1, algMetadata=self.algMetadata)
342  endInitial = self.schema1.getFieldCount()
343  self.makeSubtask("measurePsf", schema=self.schema1)
344  self.makeSubtask("astrometry", schema=self.schema1)
345  self.makeSubtask("photocal", schema=self.schema1)
346 
347  # create a schemaMapper to map schema1 into schema2
349  separator = "_"
350  count = 0
351  for item in self.schema1:
352  count = count + 1
353  field = item.getField()
354  name = field.getName()
355  if count > beginInitial and count <= endInitial:
356  name = "initial" + separator + name
357  self.schemaMapper.addMapping(item.key, name)
358 
359  # measurements fo the second measurement step done with a second schema
360  schema = self.schemaMapper.editOutputSchema()
361  self.makeSubtask("measurement", schema=schema, algMetadata=self.algMetadata)
362 
363  # the final schema is the same as the schemaMapper output
364  self.schema = self.schemaMapper.getOutputSchema()
365 
366  def getCalibKeys(self):
367  """!
368  Return a sequence of schema keys that represent fields that should be propagated from
369  icSrc to src by ProcessCcdTask.
370  """
371  if self.config.doPsf:
372  return (self.measurePsf.candidateKey, self.measurePsf.usedKey)
373  else:
374  return ()
375 
376  @pipeBase.timeMethod
377  def run(self, exposure, defects=None, idFactory=None):
378  """!Run the calibration task on an exposure
379 
380  \param[in,out] exposure Exposure to calibrate; measured PSF will be installed there as well
381  \param[in] defects List of defects on exposure
382  \param[in] idFactory afw.table.IdFactory to use for source catalog.
383  \return a pipeBase.Struct with fields:
384  - exposure: Repaired exposure
385  - backgrounds: A list of background models applied in the calibration phase
386  - psf: Point spread function
387  - sources: Sources used in calibration
388  - matches: Astrometric matches
389  - matchMeta: Metadata for astrometric matches
390  - photocal: Output of photocal subtask
391 
392  It is moderately important to provide a decent initial guess for the seeing if you want to
393  deal with cosmic rays. If there's a PSF in the exposure it'll be used; failing that the
394  CalibrateConfig.initialPsf is consulted (although the pixel scale will be taken from the
395  WCS if available).
396 
397  If the exposure contains an lsst.afw.image.Calib object with the exposure time set, MAGZERO
398  will be set in the task metadata.
399  """
400  assert exposure is not None, "No exposure provided"
401 
402  if not exposure.hasPsf():
403  self.installInitialPsf(exposure)
404  if idFactory is None:
405  idFactory = afwTable.IdFactory.makeSimple()
406  backgrounds = afwMath.BackgroundList()
407  keepCRs = True # At least until we know the PSF
408  self.repair.run(exposure, defects=defects, keepCRs=keepCRs)
409  self.display('repair', exposure=exposure)
410  if self.config.doBackground:
411  with self.timer("background"):
412  bg, exposure = measAlg.estimateBackground(exposure, self.config.background, subtract=True)
413  backgrounds.append(bg)
414  self.display('background', exposure=exposure)
415 
416  # Make both tables from the same detRet, since detRet can only be run once
417  table1 = afwTable.SourceTable.make(self.schema1, idFactory)
418  table1.setMetadata(self.algMetadata)
419  detRet = self.detection.makeSourceCatalog(table1, exposure)
420  sources1 = detRet.sources
421 
422 
423  if detRet.fpSets.background:
424  backgrounds.append(detRet.fpSets.background)
425 
426  # do the initial measurement. This is normally done for star selection, but do it
427  # even if the psf is not going to be calculated for consistency
428  self.initialMeasurement.measure(exposure, sources1)
429 
430  if self.config.doPsf:
431  if self.config.doAstrometry:
432  astromRet = self.astrometry.run(exposure, sources1)
433  matches = astromRet.matches
434  else:
435  # If doAstrometry is False, we force the Star Selector to either make them itself
436  # or hope it doesn't need them.
437  matches = None
438  psfRet = self.measurePsf.run(exposure, sources1, matches=matches)
439  psf = psfRet.psf
440  elif exposure.hasPsf():
441  psf = exposure.getPsf()
442  else:
443  psf = None
444 
445  # Wash, rinse, repeat with proper PSF
446 
447  if self.config.doPsf:
448  self.repair.run(exposure, defects=defects, keepCRs=None)
449  self.display('PSF_repair', exposure=exposure)
450 
451  if self.config.doBackground:
452  # Background estimation ignores (by default) pixels with the
453  # DETECTED bit set, so now we re-estimate the background,
454  # ignoring sources. (see BackgroundConfig.ignoredPixelMask)
455  with self.timer("background"):
456  # Subtract background
457  bg, exposure = measAlg.estimateBackground(
458  exposure, self.config.background, subtract=True,
459  statsKeys=('BGMEAN2', 'BGVAR2'))
460  self.log.info("Fit and subtracted background")
461  backgrounds.append(bg)
462 
463  self.display('PSF_background', exposure=exposure)
464 
465  # make a second table with which to do the second measurement
466  # the schemaMapper will copy the footprints and ids, which is all we need.
467  table2 = afwTable.SourceTable.make(self.schema, idFactory)
468  table2.setMetadata(self.algMetadata)
469  sources = afwTable.SourceCatalog(table2)
470  # transfer to a second table -- note that the slots do not have to be reset here
471  # as long as measurement.run follows immediately
472  sources.extend(sources1, self.schemaMapper)
473  self.measurement.run(exposure, sources)
474 
475  if self.config.doAstrometry:
476  astromRet = self.astrometry.run(exposure, sources)
477  matches = astromRet.matches
478  matchMeta = astromRet.matchMeta
479  else:
480  matches, matchMeta = None, None
481 
482  if self.config.doPhotoCal:
483  assert(matches is not None)
484  try:
485  photocalRet = self.photocal.run(exposure, matches)
486  except Exception, e:
487  raise
488  self.log.warn("Failed to determine photometric zero-point: %s" % e)
489  photocalRet = None
490  self.metadata.set('MAGZERO', float("NaN"))
491 
492  if photocalRet:
493  self.log.info("Photometric zero-point: %f" % photocalRet.calib.getMagnitude(1.0))
494  exposure.getCalib().setFluxMag0(photocalRet.calib.getFluxMag0())
495  metadata = exposure.getMetadata()
496  # convert to (mag/sec/adu) for metadata
497  try:
498  magZero = photocalRet.zp - 2.5 * math.log10(exposure.getCalib().getExptime() )
499  metadata.set('MAGZERO', magZero)
500  except:
501  self.log.warn("Could not set normalized MAGZERO in header: no exposure time")
502  metadata.set('MAGZERO_RMS', photocalRet.sigma)
503  metadata.set('MAGZERO_NOBJ', photocalRet.ngood)
504  metadata.set('COLORTERM1', 0.0)
505  metadata.set('COLORTERM2', 0.0)
506  metadata.set('COLORTERM3', 0.0)
507  else:
508  photocalRet = None
509  self.display('calibrate', exposure=exposure, sources=sources, matches=matches)
510  return pipeBase.Struct(
511  exposure = exposure,
512  backgrounds = backgrounds,
513  psf = psf,
514  sources = sources,
515  matches = matches,
516  matchMeta = matchMeta,
517  photocal = photocalRet,
518  )
519 
520  def installInitialPsf(self, exposure):
521  """!Initialise the calibration procedure by setting the PSF to a configuration-defined guess.
522 
523  \param[in,out] exposure Exposure to process; fake PSF will be installed here.
524  \throws AssertionError If exposure or exposure.getWcs() are None
525  """
526  assert exposure, "No exposure provided"
527 
528  wcs = exposure.getWcs()
529  if wcs:
530  pixelScale = wcs.pixelScale().asArcseconds()
531  else:
532  pixelScale = self.config.initialPsf.pixelScale
533 
534  cls = getattr(measAlg, self.config.initialPsf.model + "Psf")
535 
536  fwhm = self.config.initialPsf.fwhm/pixelScale
537  size = self.config.initialPsf.size
538  self.log.info("installInitialPsf fwhm=%s pixels; size=%s pixels" % (fwhm, size))
539  psf = cls(size, size, fwhm/(2*math.sqrt(2*math.log(2))))
540  exposure.setPsf(psf)
def __init__
Create the calibration task.
Definition: calibrate.py:324
def getCalibKeys
Return a sequence of schema keys that represent fields that should be propagated from icSrc to src by...
Definition: calibrate.py:366
Class for storing ordered metadata with comments.
Definition: PropertyList.h:81
A mapping between the keys of two Schemas, used to copy data between them.
Definition: SchemaMapper.h:19
Describes the initial PSF used for detection and measurement before we do PSF determination.
Definition: calibrate.py:36
def installInitialPsf
Initialise the calibration procedure by setting the PSF to a configuration-defined guess...
Definition: calibrate.py:520
Custom catalog class for record/table subclasses that are guaranteed to have an ID, and should generally be sorted by that ID.
Definition: fwd.h:55
def run
Run the calibration task on an exposure.
Definition: calibrate.py:377
Calibrate an exposure: measure PSF, subtract background, measure astrometry and photometry.
Definition: calibrate.py:132