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