LSSTApplications  16.0-10-g9d3e444,16.0-11-g09ed895+3,16.0-11-g12e47bd+4,16.0-11-g9bb73b2+10,16.0-12-g5c924a4+10,16.0-15-g7af1f30,16.0-15-gdd5ca33+2,16.0-16-gf0259e2+1,16.0-17-g31abd91+11,16.0-17-g5cf0468+3,16.0-18-g51a54b3+3,16.0-18-ga4d4bcb+5,16.0-18-gcf94535+2,16.0-19-g9d290d5+2,16.0-2-g0febb12+22,16.0-2-g9d5294e+73,16.0-2-ga8830df+7,16.0-21-g3d035912+2,16.0-26-g8e79609,16.0-28-gfc9ea6c+9,16.0-29-ge8801f9+4,16.0-3-ge00e371+38,16.0-4-g18f3627+17,16.0-4-g5f3a788+21,16.0-4-ga3eb747+11,16.0-4-gabf74b7+33,16.0-4-gb13d127+7,16.0-5-g27fb78a+11,16.0-5-g6a53317+38,16.0-5-gb3f8a4b+91,16.0-51-gbbe9c988+3,16.0-6-g9321be7+5,16.0-6-gcbc7b31+47,16.0-6-gf49912c+33,16.0-75-gbf7a9a820,16.0-8-g21fd5fe+34,16.0-8-g3a9f023+24,16.0-8-gc11f1cf,16.0-9-gf3bc169+2,16.0-9-gf5c1f43+12,master-gc237143d49,w.2019.02
LSSTDataManagementBasePackage
multiBand.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 #
3 # LSST Data Management System
4 # Copyright 2008-2015 AURA/LSST.
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 <https://www.lsstcorp.org/LegalNotices/>.
22 #
23 from lsst.coadd.utils.coaddDataIdContainer import ExistingCoaddDataIdContainer
24 from lsst.pipe.base import (CmdLineTask, Struct, ArgumentParser, ButlerInitializedTaskRunner,
25  PipelineTask, InitOutputDatasetField, InputDatasetField, OutputDatasetField,
26  QuantumConfig)
27 from lsst.pex.config import Config, Field, ConfigurableField
28 from lsst.meas.algorithms import DynamicDetectionTask
29 from lsst.meas.base import SingleFrameMeasurementTask, ApplyApCorrTask, CatalogCalculationTask
30 from lsst.meas.deblender import SourceDeblendTask, MultibandDeblendTask
31 from lsst.pipe.tasks.coaddBase import getSkyInfo
32 from lsst.pipe.tasks.scaleVariance import ScaleVarianceTask
33 from lsst.meas.astrom import DirectMatchTask, denormalizeMatches
34 from lsst.pipe.tasks.fakes import BaseFakeSourcesTask
35 from lsst.pipe.tasks.setPrimaryFlags import SetPrimaryFlagsTask
36 from lsst.pipe.tasks.propagateVisitFlags import PropagateVisitFlagsTask
37 import lsst.afw.image as afwImage
38 import lsst.afw.table as afwTable
39 import lsst.afw.math as afwMath
40 from lsst.daf.base import PropertyList
41 
42 from .mergeDetections import MergeDetectionsConfig, MergeDetectionsTask # noqa: F401
43 from .mergeMeasurements import MergeMeasurementsConfig, MergeMeasurementsTask # noqa: F401
44 from .multiBandUtils import MergeSourcesRunner, CullPeaksConfig, _makeGetSchemaCatalogs # noqa: F401
45 from .multiBandUtils import getInputSchema, getShortFilterName, readCatalog, _makeMakeIdFactory # noqa: F401
46 from .deblendCoaddSourcesPipeline import DeblendCoaddSourcesSingleConfig # noqa: F401
47 from .deblendCoaddSourcesPipeline import DeblendCoaddSourcesSingleTask # noqa: F401
48 from .deblendCoaddSourcesPipeline import DeblendCoaddSourcesMultiConfig # noqa: F401
49 from .deblendCoaddSourcesPipeline import DeblendCoaddSourcesMultiTask # noqa: F401
50 
51 
52 """
53 New set types:
54 * deepCoadd_det: detections from what used to be processCoadd (tract, patch, filter)
55 * deepCoadd_mergeDet: merged detections (tract, patch)
56 * deepCoadd_meas: measurements of merged detections (tract, patch, filter)
57 * deepCoadd_ref: reference sources (tract, patch)
58 All of these have associated *_schema catalogs that require no data ID and hold no records.
59 
60 In addition, we have a schema-only dataset, which saves the schema for the PeakRecords in
61 the mergeDet, meas, and ref dataset Footprints:
62 * deepCoadd_peak_schema
63 """
64 
65 
66 
67 
69  """!
70  @anchor DetectCoaddSourcesConfig_
71 
72  @brief Configuration parameters for the DetectCoaddSourcesTask
73  """
74  doScaleVariance = Field(dtype=bool, default=True, doc="Scale variance plane using empirical noise?")
75  scaleVariance = ConfigurableField(target=ScaleVarianceTask, doc="Variance rescaling")
76  detection = ConfigurableField(target=DynamicDetectionTask, doc="Source detection")
77  coaddName = Field(dtype=str, default="deep", doc="Name of coadd")
78  doInsertFakes = Field(dtype=bool, default=False,
79  doc="Run fake sources injection task")
80  insertFakes = ConfigurableField(target=BaseFakeSourcesTask,
81  doc="Injection of fake sources for testing "
82  "purposes (must be retargeted)")
83  detectionSchema = InitOutputDatasetField(
84  doc="Schema of the detection catalog",
85  name="{}Coadd_det_schema",
86  storageClass="SourceCatalog",
87  )
88  exposure = InputDatasetField(
89  doc="Exposure on which detections are to be performed",
90  name="deepCoadd",
91  scalar=True,
92  storageClass="ExposureF",
93  dimensions=("Tract", "Patch", "AbstractFilter", "SkyMap")
94  )
95  outputBackgrounds = OutputDatasetField(
96  doc="Output Backgrounds used in detection",
97  name="{}Coadd_calexp_background",
98  scalar=True,
99  storageClass="Background",
100  dimensions=("Tract", "Patch", "AbstractFilter", "SkyMap")
101  )
102  outputSources = OutputDatasetField(
103  doc="Detected sources catalog",
104  name="{}Coadd_det",
105  scalar=True,
106  storageClass="SourceCatalog",
107  dimensions=("Tract", "Patch", "AbstractFilter", "SkyMap")
108  )
109  outputExposure = OutputDatasetField(
110  doc="Exposure post detection",
111  name="{}Coadd_calexp",
112  scalar=True,
113  storageClass="ExposureF",
114  dimensions=("Tract", "Patch", "AbstractFilter", "SkyMap")
115  )
116  quantum = QuantumConfig(
117  dimensions=("Tract", "Patch", "AbstractFilter", "SkyMap")
118  )
119 
120  def setDefaults(self):
121  Config.setDefaults(self)
122  self.detection.thresholdType = "pixel_stdev"
123  self.detection.isotropicGrow = True
124  # Coadds are made from background-subtracted CCDs, so any background subtraction should be very basic
125  self.detection.reEstimateBackground = False
126  self.detection.background.useApprox = False
127  self.detection.background.binSize = 4096
128  self.detection.background.undersampleStyle = 'REDUCE_INTERP_ORDER'
129  self.detection.doTempWideBackground = True # Suppress large footprints that overwhelm the deblender
130 
131 
137 
138 
139 class DetectCoaddSourcesTask(PipelineTask, CmdLineTask):
140  r"""!
141  @anchor DetectCoaddSourcesTask_
142 
143  @brief Detect sources on a coadd
144 
145  @section pipe_tasks_multiBand_Contents Contents
146 
147  - @ref pipe_tasks_multiBand_DetectCoaddSourcesTask_Purpose
148  - @ref pipe_tasks_multiBand_DetectCoaddSourcesTask_Initialize
149  - @ref pipe_tasks_multiBand_DetectCoaddSourcesTask_Run
150  - @ref pipe_tasks_multiBand_DetectCoaddSourcesTask_Config
151  - @ref pipe_tasks_multiBand_DetectCoaddSourcesTask_Debug
152  - @ref pipe_tasks_multiband_DetectCoaddSourcesTask_Example
153 
154  @section pipe_tasks_multiBand_DetectCoaddSourcesTask_Purpose Description
155 
156  Command-line task that detects sources on a coadd of exposures obtained with a single filter.
157 
158  Coadding individual visits requires each exposure to be warped. This introduces covariance in the noise
159  properties across pixels. Before detection, we correct the coadd variance by scaling the variance plane
160  in the coadd to match the observed variance. This is an approximate approach -- strictly, we should
161  propagate the full covariance matrix -- but it is simple and works well in practice.
162 
163  After scaling the variance plane, we detect sources and generate footprints by delegating to the @ref
164  SourceDetectionTask_ "detection" subtask.
165 
166  @par Inputs:
167  deepCoadd{tract,patch,filter}: ExposureF
168  @par Outputs:
169  deepCoadd_det{tract,patch,filter}: SourceCatalog (only parent Footprints)
170  @n deepCoadd_calexp{tract,patch,filter}: Variance scaled, background-subtracted input
171  exposure (ExposureF)
172  @n deepCoadd_calexp_background{tract,patch,filter}: BackgroundList
173  @par Data Unit:
174  tract, patch, filter
175 
176  DetectCoaddSourcesTask delegates most of its work to the @ref SourceDetectionTask_ "detection" subtask.
177  You can retarget this subtask if you wish.
178 
179  @section pipe_tasks_multiBand_DetectCoaddSourcesTask_Initialize Task initialization
180 
181  @copydoc \_\_init\_\_
182 
183  @section pipe_tasks_multiBand_DetectCoaddSourcesTask_Run Invoking the Task
184 
185  @copydoc run
186 
187  @section pipe_tasks_multiBand_DetectCoaddSourcesTask_Config Configuration parameters
188 
189  See @ref DetectCoaddSourcesConfig_ "DetectSourcesConfig"
190 
191  @section pipe_tasks_multiBand_DetectCoaddSourcesTask_Debug Debug variables
192 
193  The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a
194  flag @c -d to import @b debug.py from your @c PYTHONPATH; see @ref baseDebug for more about @b debug.py
195  files.
196 
197  DetectCoaddSourcesTask has no debug variables of its own because it relegates all the work to
198  @ref SourceDetectionTask_ "SourceDetectionTask"; see the documetation for
199  @ref SourceDetectionTask_ "SourceDetectionTask" for further information.
200 
201  @section pipe_tasks_multiband_DetectCoaddSourcesTask_Example A complete example
202  of using DetectCoaddSourcesTask
203 
204  DetectCoaddSourcesTask is meant to be run after assembling a coadded image in a given band. The purpose of
205  the task is to update the background, detect all sources in a single band and generate a set of parent
206  footprints. Subsequent tasks in the multi-band processing procedure will merge sources across bands and,
207  eventually, perform forced photometry. Command-line usage of DetectCoaddSourcesTask expects a data
208  reference to the coadd to be processed. A list of the available optional arguments can be obtained by
209  calling detectCoaddSources.py with the `--help` command line argument:
210  @code
211  detectCoaddSources.py --help
212  @endcode
213 
214  To demonstrate usage of the DetectCoaddSourcesTask in the larger context of multi-band processing, we
215  will process HSC data in the [ci_hsc](https://github.com/lsst/ci_hsc) package. Assuming one has followed
216  steps 1 - 4 at @ref pipeTasks_multiBand, one may detect all the sources in each coadd as follows:
217  @code
218  detectCoaddSources.py $CI_HSC_DIR/DATA --id patch=5,4 tract=0 filter=HSC-I
219  @endcode
220  that will process the HSC-I band data. The results are written to
221  `$CI_HSC_DIR/DATA/deepCoadd-results/HSC-I`.
222 
223  It is also necessary to run:
224  @code
225  detectCoaddSources.py $CI_HSC_DIR/DATA --id patch=5,4 tract=0 filter=HSC-R
226  @endcode
227  to generate the sources catalogs for the HSC-R band required by the next step in the multi-band
228  processing procedure: @ref MergeDetectionsTask_ "MergeDetectionsTask".
229  """
230  _DefaultName = "detectCoaddSources"
231  ConfigClass = DetectCoaddSourcesConfig
232  getSchemaCatalogs = _makeGetSchemaCatalogs("det")
233  makeIdFactory = _makeMakeIdFactory("CoaddId")
234 
235  @classmethod
236  def _makeArgumentParser(cls):
237  parser = ArgumentParser(name=cls._DefaultName)
238  parser.add_id_argument("--id", "deepCoadd", help="data ID, e.g. --id tract=12345 patch=1,2 filter=r",
239  ContainerClass=ExistingCoaddDataIdContainer)
240  return parser
241 
242  @classmethod
243  def getOutputDatasetTypes(cls, config):
244  coaddName = config.coaddName
245  for name in ("outputBackgrounds", "outputSources", "outputExposure"):
246  attr = getattr(config, name)
247  setattr(attr, "name", attr.name.format(coaddName))
248  outputTypeDict = super().getOutputDatasetTypes(config)
249  return outputTypeDict
250 
251  @classmethod
252  def getInitOutputDatasetTypes(cls, config):
253  coaddName = config.coaddName
254  attr = config.detectionSchema
255  setattr(attr, "name", attr.name.format(coaddName))
256  output = super().getInitOutputDatasetTypes(config)
257  return output
258 
259  def __init__(self, schema=None, **kwargs):
260  """!
261  @brief Initialize the task. Create the @ref SourceDetectionTask_ "detection" subtask.
262 
263  Keyword arguments (in addition to those forwarded to CmdLineTask.__init__):
264 
265  @param[in] schema: initial schema for the output catalog, modified-in place to include all
266  fields set by this task. If None, the source minimal schema will be used.
267  @param[in] **kwargs: keyword arguments to be passed to lsst.pipe.base.task.Task.__init__
268  """
269  # N.B. Super is used here to handle the multiple inheritance of PipelineTasks, the init tree
270  # call structure has been reviewed carefully to be sure super will work as intended.
271  super().__init__(**kwargs)
272  if schema is None:
273  schema = afwTable.SourceTable.makeMinimalSchema()
274  if self.config.doInsertFakes:
275  self.makeSubtask("insertFakes")
276  self.schema = schema
277  self.makeSubtask("detection", schema=self.schema)
278  if self.config.doScaleVariance:
279  self.makeSubtask("scaleVariance")
280 
282  return {"detectionSchema": afwTable.SourceCatalog(self.schema)}
283 
284  def runDataRef(self, patchRef):
285  """!
286  @brief Run detection on a coadd.
287 
288  Invokes @ref run and then uses @ref write to output the
289  results.
290 
291  @param[in] patchRef: data reference for patch
292  """
293  exposure = patchRef.get(self.config.coaddName + "Coadd", immediate=True)
294  expId = int(patchRef.get(self.config.coaddName + "CoaddId"))
295  results = self.run(exposure, self.makeIdFactory(patchRef), expId=expId)
296  self.write(results, patchRef)
297  return results
298 
299  def adaptArgsAndRun(self, inputData, inputDataIds, outputDataIds, butler):
300  # FINDME: DM-15843 needs to come back and address these next two lines with a final solution
301  inputData["idFactory"] = afwTable.IdFactory.makeSimple()
302  inputData["expId"] = 0
303  return self.run(**inputData)
304 
305  def run(self, exposure, idFactory, expId):
306  """!
307  @brief Run detection on an exposure.
308 
309  First scale the variance plane to match the observed variance
310  using @ref ScaleVarianceTask. Then invoke the @ref SourceDetectionTask_ "detection" subtask to
311  detect sources.
312 
313  @param[in,out] exposure: Exposure on which to detect (may be backround-subtracted and scaled,
314  depending on configuration).
315  @param[in] idFactory: IdFactory to set source identifiers
316  @param[in] expId: Exposure identifier (integer) for RNG seed
317 
318  @return a pipe.base.Struct with fields
319  - sources: catalog of detections
320  - backgrounds: list of backgrounds
321  """
322  if self.config.doScaleVariance:
323  varScale = self.scaleVariance.run(exposure.maskedImage)
324  exposure.getMetadata().add("variance_scale", varScale)
325  backgrounds = afwMath.BackgroundList()
326  if self.config.doInsertFakes:
327  self.insertFakes.run(exposure, background=backgrounds)
328  table = afwTable.SourceTable.make(self.schema, idFactory)
329  detections = self.detection.makeSourceCatalog(table, exposure, expId=expId)
330  sources = detections.sources
331  fpSets = detections.fpSets
332  if hasattr(fpSets, "background") and fpSets.background:
333  for bg in fpSets.background:
334  backgrounds.append(bg)
335  return Struct(outputSources=sources, outputBackgrounds=backgrounds, outputExposure=exposure)
336 
337  def write(self, results, patchRef):
338  """!
339  @brief Write out results from runDetection.
340 
341  @param[in] exposure: Exposure to write out
342  @param[in] results: Struct returned from runDetection
343  @param[in] patchRef: data reference for patch
344  """
345  coaddName = self.config.coaddName + "Coadd"
346  patchRef.put(results.outputBackgrounds, coaddName + "_calexp_background")
347  patchRef.put(results.outputSources, coaddName + "_det")
348  patchRef.put(results.outputExposure, coaddName + "_calexp")
349 
350 
351 
352 
354  """DeblendCoaddSourcesConfig
355 
356  Configuration parameters for the `DeblendCoaddSourcesTask`.
357  """
358  singleBandDeblend = ConfigurableField(target=SourceDeblendTask,
359  doc="Deblend sources separately in each band")
360  multiBandDeblend = ConfigurableField(target=MultibandDeblendTask,
361  doc="Deblend sources simultaneously across bands")
362  simultaneous = Field(dtype=bool, default=False, doc="Simultaneously deblend all bands?")
363  coaddName = Field(dtype=str, default="deep", doc="Name of coadd")
364 
365  def setDefaults(self):
366  Config.setDefaults(self)
367  self.singleBandDeblend.propagateAllPeaks = True
368 
369 
371  """Task runner for the `MergeSourcesTask`
372 
373  Required because the run method requires a list of
374  dataRefs rather than a single dataRef.
375  """
376  @staticmethod
377  def getTargetList(parsedCmd, **kwargs):
378  """Provide a list of patch references for each patch, tract, filter combo.
379 
380  Parameters
381  ----------
382  parsedCmd:
383  The parsed command
384  kwargs:
385  Keyword arguments passed to the task
386 
387  Returns
388  -------
389  targetList: list
390  List of tuples, where each tuple is a (dataRef, kwargs) pair.
391  """
392  refDict = MergeSourcesRunner.buildRefDict(parsedCmd)
393  kwargs["psfCache"] = parsedCmd.psfCache
394  return [(list(p.values()), kwargs) for t in refDict.values() for p in t.values()]
395 
396 
397 class DeblendCoaddSourcesTask(CmdLineTask):
398  """Deblend the sources in a merged catalog
399 
400  Deblend sources from master catalog in each coadd.
401  This can either be done separately in each band using the HSC-SDSS deblender
402  (`DeblendCoaddSourcesTask.config.simultaneous==False`)
403  or use SCARLET to simultaneously fit the blend in all bands
404  (`DeblendCoaddSourcesTask.config.simultaneous==True`).
405  The task will set its own `self.schema` atribute to the `Schema` of the
406  output deblended catalog.
407  This will include all fields from the input `Schema`, as well as additional fields
408  from the deblender.
409 
410  `pipe.tasks.multiband.DeblendCoaddSourcesTask Description
411  ---------------------------------------------------------
412  `
413 
414  Parameters
415  ----------
416  butler: `Butler`
417  Butler used to read the input schemas from disk or
418  construct the reference catalog loader, if `schema` or `peakSchema` or
419  schema: `Schema`
420  The schema of the merged detection catalog as an input to this task.
421  peakSchema: `Schema`
422  The schema of the `PeakRecord`s in the `Footprint`s in the merged detection catalog
423  """
424  ConfigClass = DeblendCoaddSourcesConfig
425  RunnerClass = DeblendCoaddSourcesRunner
426  _DefaultName = "deblendCoaddSources"
427  makeIdFactory = _makeMakeIdFactory("MergedCoaddId")
428 
429  @classmethod
430  def _makeArgumentParser(cls):
431  parser = ArgumentParser(name=cls._DefaultName)
432  parser.add_id_argument("--id", "deepCoadd_calexp",
433  help="data ID, e.g. --id tract=12345 patch=1,2 filter=g^r^i",
434  ContainerClass=ExistingCoaddDataIdContainer)
435  parser.add_argument("--psfCache", type=int, default=100, help="Size of CoaddPsf cache")
436  return parser
437 
438  def __init__(self, butler=None, schema=None, peakSchema=None, **kwargs):
439  CmdLineTask.__init__(self, **kwargs)
440  if schema is None:
441  assert butler is not None, "Neither butler nor schema is defined"
442  schema = butler.get(self.config.coaddName + "Coadd_mergeDet_schema", immediate=True).schema
444  self.schemaMapper.addMinimalSchema(schema)
445  self.schema = self.schemaMapper.getOutputSchema()
446  if peakSchema is None:
447  assert butler is not None, "Neither butler nor peakSchema is defined"
448  peakSchema = butler.get(self.config.coaddName + "Coadd_peak_schema", immediate=True).schema
449 
450  if self.config.simultaneous:
451  self.makeSubtask("multiBandDeblend", schema=self.schema, peakSchema=peakSchema)
452  else:
453  self.makeSubtask("singleBandDeblend", schema=self.schema, peakSchema=peakSchema)
454 
455  def getSchemaCatalogs(self):
456  """Return a dict of empty catalogs for each catalog dataset produced by this task.
457 
458  Returns
459  -------
460  result: dict
461  Dictionary of empty catalogs, with catalog names as keys.
462  """
463  catalog = afwTable.SourceCatalog(self.schema)
464  return {self.config.coaddName + "Coadd_deblendedFlux": catalog,
465  self.config.coaddName + "Coadd_deblendedModel": catalog}
466 
467  def runDataRef(self, patchRefList, psfCache=100):
468  """Deblend the patch
469 
470  Deblend each source simultaneously or separately
471  (depending on `DeblendCoaddSourcesTask.config.simultaneous`).
472  Set `is-primary` and related flags.
473  Propagate flags from individual visits.
474  Write the deblended sources out.
475 
476  Parameters
477  ----------
478  patchRefList: list
479  List of data references for each filter
480  """
481  if self.config.simultaneous:
482  # Use SCARLET to simultaneously deblend across filters
483  filters = []
484  exposures = []
485  for patchRef in patchRefList:
486  exposure = patchRef.get(self.config.coaddName + "Coadd_calexp", immediate=True)
487  filters.append(patchRef.dataId["filter"])
488  exposures.append(exposure)
489  # The input sources are the same for all bands, since it is a merged catalog
490  sources = self.readSources(patchRef)
491  exposure = afwImage.MultibandExposure.fromExposures(filters, exposures)
492  fluxCatalogs, templateCatalogs = self.multiBandDeblend.run(exposure, sources)
493  for n in range(len(patchRefList)):
494  self.write(patchRefList[n], fluxCatalogs[filters[n]], templateCatalogs[filters[n]])
495  else:
496  # Use the singeband deblender to deblend each band separately
497  for patchRef in patchRefList:
498  exposure = patchRef.get(self.config.coaddName + "Coadd_calexp", immediate=True)
499  exposure.getPsf().setCacheCapacity(psfCache)
500  sources = self.readSources(patchRef)
501  self.singleBandDeblend.run(exposure, sources)
502  self.write(patchRef, sources)
503 
504  def readSources(self, dataRef):
505  """Read merged catalog
506 
507  Read the catalog of merged detections and create a catalog
508  in a single band.
509 
510  Parameters
511  ----------
512  dataRef: data reference
513  Data reference for catalog of merged detections
514 
515  Returns
516  -------
517  sources: `SourceCatalog`
518  List of sources in merged catalog
519 
520  We also need to add columns to hold the measurements we're about to make
521  so we can measure in-place.
522  """
523  merged = dataRef.get(self.config.coaddName + "Coadd_mergeDet", immediate=True)
524  self.log.info("Read %d detections: %s" % (len(merged), dataRef.dataId))
525  idFactory = self.makeIdFactory(dataRef)
526  for s in merged:
527  idFactory.notify(s.getId())
528  table = afwTable.SourceTable.make(self.schema, idFactory)
529  sources = afwTable.SourceCatalog(table)
530  sources.extend(merged, self.schemaMapper)
531  return sources
532 
533  def write(self, dataRef, flux_sources, template_sources=None):
534  """Write the source catalog(s)
535 
536  Parameters
537  ----------
538  dataRef: Data Reference
539  Reference to the output catalog.
540  flux_sources: `SourceCatalog`
541  Flux conserved sources to write to file.
542  If using the single band deblender, this is the catalog
543  generated.
544  template_sources: `SourceCatalog`
545  Source catalog using the multiband template models
546  as footprints.
547  """
548  # The multiband deblender does not have to conserve flux,
549  # so only write the flux conserved catalog if it exists
550  if flux_sources is not None:
551  assert not self.config.simultaneous or self.config.multiBandDeblend.conserveFlux
552  dataRef.put(flux_sources, self.config.coaddName + "Coadd_deblendedFlux")
553  # Only the multiband deblender has the option to output the
554  # template model catalog, which can optionally be used
555  # in MeasureMergedCoaddSources
556  if template_sources is not None:
557  assert self.config.multiBandDeblend.saveTemplates
558  dataRef.put(template_sources, self.config.coaddName + "Coadd_deblendedModel")
559  self.log.info("Wrote %d sources: %s" % (len(flux_sources), dataRef.dataId))
560 
561  def writeMetadata(self, dataRefList):
562  """Write the metadata produced from processing the data.
563  Parameters
564  ----------
565  dataRefList
566  List of Butler data references used to write the metadata.
567  The metadata is written to dataset type `CmdLineTask._getMetadataName`.
568  """
569  for dataRef in dataRefList:
570  try:
571  metadataName = self._getMetadataName()
572  if metadataName is not None:
573  dataRef.put(self.getFullMetadata(), metadataName)
574  except Exception as e:
575  self.log.warn("Could not persist metadata for dataId=%s: %s", dataRef.dataId, e)
576 
577  def getExposureId(self, dataRef):
578  """Get the ExposureId from a data reference
579  """
580  return int(dataRef.get(self.config.coaddName + "CoaddId"))
581 
582 
584  """!
585  @anchor MeasureMergedCoaddSourcesConfig_
586 
587  @brief Configuration parameters for the MeasureMergedCoaddSourcesTask
588  """
589  inputCatalog = Field(dtype=str, default="deblendedFlux",
590  doc=("Name of the input catalog to use."
591  "If the single band deblender was used this should be 'deblendedFlux."
592  "If the multi-band deblender was used this should be 'deblendedModel."
593  "If no deblending was performed this should be 'mergeDet'"))
594  measurement = ConfigurableField(target=SingleFrameMeasurementTask, doc="Source measurement")
595  setPrimaryFlags = ConfigurableField(target=SetPrimaryFlagsTask, doc="Set flags for primary tract/patch")
596  doPropagateFlags = Field(
597  dtype=bool, default=True,
598  doc="Whether to match sources to CCD catalogs to propagate flags (to e.g. identify PSF stars)"
599  )
600  propagateFlags = ConfigurableField(target=PropagateVisitFlagsTask, doc="Propagate visit flags to coadd")
601  doMatchSources = Field(dtype=bool, default=True, doc="Match sources to reference catalog?")
602  match = ConfigurableField(target=DirectMatchTask, doc="Matching to reference catalog")
603  doWriteMatchesDenormalized = Field(
604  dtype=bool,
605  default=False,
606  doc=("Write reference matches in denormalized format? "
607  "This format uses more disk space, but is more convenient to read."),
608  )
609  coaddName = Field(dtype=str, default="deep", doc="Name of coadd")
610  checkUnitsParseStrict = Field(
611  doc="Strictness of Astropy unit compatibility check, can be 'raise', 'warn' or 'silent'",
612  dtype=str,
613  default="raise",
614  )
615  doApCorr = Field(
616  dtype=bool,
617  default=True,
618  doc="Apply aperture corrections"
619  )
620  applyApCorr = ConfigurableField(
621  target=ApplyApCorrTask,
622  doc="Subtask to apply aperture corrections"
623  )
624  doRunCatalogCalculation = Field(
625  dtype=bool,
626  default=True,
627  doc='Run catalogCalculation task'
628  )
629  catalogCalculation = ConfigurableField(
630  target=CatalogCalculationTask,
631  doc="Subtask to run catalogCalculation plugins on catalog"
632  )
633 
634  def setDefaults(self):
635  Config.setDefaults(self)
636  self.measurement.plugins.names |= ['base_InputCount', 'base_Variance']
637  self.measurement.plugins['base_PixelFlags'].masksFpAnywhere = ['CLIPPED', 'SENSOR_EDGE',
638  'INEXACT_PSF']
639  self.measurement.plugins['base_PixelFlags'].masksFpCenter = ['CLIPPED', 'SENSOR_EDGE',
640  'INEXACT_PSF']
641 
642 
648 
649 
650 class MeasureMergedCoaddSourcesRunner(ButlerInitializedTaskRunner):
651  """Get the psfCache setting into MeasureMergedCoaddSourcesTask"""
652  @staticmethod
653  def getTargetList(parsedCmd, **kwargs):
654  return ButlerInitializedTaskRunner.getTargetList(parsedCmd, psfCache=parsedCmd.psfCache)
655 
656 
657 class MeasureMergedCoaddSourcesTask(CmdLineTask):
658  r"""!
659  @anchor MeasureMergedCoaddSourcesTask_
660 
661  @brief Deblend sources from master catalog in each coadd seperately and measure.
662 
663  @section pipe_tasks_multiBand_Contents Contents
664 
665  - @ref pipe_tasks_multiBand_MeasureMergedCoaddSourcesTask_Purpose
666  - @ref pipe_tasks_multiBand_MeasureMergedCoaddSourcesTask_Initialize
667  - @ref pipe_tasks_multiBand_MeasureMergedCoaddSourcesTask_Run
668  - @ref pipe_tasks_multiBand_MeasureMergedCoaddSourcesTask_Config
669  - @ref pipe_tasks_multiBand_MeasureMergedCoaddSourcesTask_Debug
670  - @ref pipe_tasks_multiband_MeasureMergedCoaddSourcesTask_Example
671 
672  @section pipe_tasks_multiBand_MeasureMergedCoaddSourcesTask_Purpose Description
673 
674  Command-line task that uses peaks and footprints from a master catalog to perform deblending and
675  measurement in each coadd.
676 
677  Given a master input catalog of sources (peaks and footprints) or deblender outputs
678  (including a HeavyFootprint in each band), measure each source on the
679  coadd. Repeating this procedure with the same master catalog across multiple coadds will generate a
680  consistent set of child sources.
681 
682  The deblender retains all peaks and deblends any missing peaks (dropouts in that band) as PSFs. Source
683  properties are measured and the @c is-primary flag (indicating sources with no children) is set. Visit
684  flags are propagated to the coadd sources.
685 
686  Optionally, we can match the coadd sources to an external reference catalog.
687 
688  @par Inputs:
689  deepCoadd_mergeDet{tract,patch} or deepCoadd_deblend{tract,patch}: SourceCatalog
690  @n deepCoadd_calexp{tract,patch,filter}: ExposureF
691  @par Outputs:
692  deepCoadd_meas{tract,patch,filter}: SourceCatalog
693  @par Data Unit:
694  tract, patch, filter
695 
696  MeasureMergedCoaddSourcesTask delegates most of its work to a set of sub-tasks:
697 
698  <DL>
699  <DT> @ref SingleFrameMeasurementTask_ "measurement"
700  <DD> Measure source properties of deblended sources.</DD>
701  <DT> @ref SetPrimaryFlagsTask_ "setPrimaryFlags"
702  <DD> Set flag 'is-primary' as well as related flags on sources. 'is-primary' is set for sources that are
703  not at the edge of the field and that have either not been deblended or are the children of deblended
704  sources</DD>
705  <DT> @ref PropagateVisitFlagsTask_ "propagateFlags"
706  <DD> Propagate flags set in individual visits to the coadd.</DD>
707  <DT> @ref DirectMatchTask_ "match"
708  <DD> Match input sources to a reference catalog (optional).
709  </DD>
710  </DL>
711  These subtasks may be retargeted as required.
712 
713  @section pipe_tasks_multiBand_MeasureMergedCoaddSourcesTask_Initialize Task initialization
714 
715  @copydoc \_\_init\_\_
716 
717  @section pipe_tasks_multiBand_MeasureMergedCoaddSourcesTask_Run Invoking the Task
718 
719  @copydoc run
720 
721  @section pipe_tasks_multiBand_MeasureMergedCoaddSourcesTask_Config Configuration parameters
722 
723  See @ref MeasureMergedCoaddSourcesConfig_
724 
725  @section pipe_tasks_multiBand_MeasureMergedCoaddSourcesTask_Debug Debug variables
726 
727  The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a
728  flag @c -d to import @b debug.py from your @c PYTHONPATH; see @ref baseDebug for more about @b debug.py
729  files.
730 
731  MeasureMergedCoaddSourcesTask has no debug variables of its own because it delegates all the work to
732  the various sub-tasks. See the documetation for individual sub-tasks for more information.
733 
734  @section pipe_tasks_multiband_MeasureMergedCoaddSourcesTask_Example A complete example of using
735  MeasureMergedCoaddSourcesTask
736 
737  After MeasureMergedCoaddSourcesTask has been run on multiple coadds, we have a set of per-band catalogs.
738  The next stage in the multi-band processing procedure will merge these measurements into a suitable
739  catalog for driving forced photometry.
740 
741  Command-line usage of MeasureMergedCoaddSourcesTask expects a data reference to the coadds
742  to be processed.
743  A list of the available optional arguments can be obtained by calling measureCoaddSources.py with the
744  `--help` command line argument:
745  @code
746  measureCoaddSources.py --help
747  @endcode
748 
749  To demonstrate usage of the DetectCoaddSourcesTask in the larger context of multi-band processing, we
750  will process HSC data in the [ci_hsc](https://github.com/lsst/ci_hsc) package. Assuming one has finished
751  step 6 at @ref pipeTasks_multiBand, one may perform deblending and measure sources in the HSC-I band
752  coadd as follows:
753  @code
754  measureCoaddSources.py $CI_HSC_DIR/DATA --id patch=5,4 tract=0 filter=HSC-I
755  @endcode
756  This will process the HSC-I band data. The results are written in
757  `$CI_HSC_DIR/DATA/deepCoadd-results/HSC-I/0/5,4/meas-HSC-I-0-5,4.fits
758 
759  It is also necessary to run
760  @code
761  measureCoaddSources.py $CI_HSC_DIR/DATA --id patch=5,4 tract=0 filter=HSC-R
762  @endcode
763  to generate the sources catalogs for the HSC-R band required by the next step in the multi-band
764  procedure: @ref MergeMeasurementsTask_ "MergeMeasurementsTask".
765  """
766  _DefaultName = "measureCoaddSources"
767  ConfigClass = MeasureMergedCoaddSourcesConfig
768  RunnerClass = MeasureMergedCoaddSourcesRunner
769  getSchemaCatalogs = _makeGetSchemaCatalogs("meas")
770  makeIdFactory = _makeMakeIdFactory("MergedCoaddId") # The IDs we already have are of this type
771 
772  @classmethod
773  def _makeArgumentParser(cls):
774  parser = ArgumentParser(name=cls._DefaultName)
775  parser.add_id_argument("--id", "deepCoadd_calexp",
776  help="data ID, e.g. --id tract=12345 patch=1,2 filter=r",
777  ContainerClass=ExistingCoaddDataIdContainer)
778  parser.add_argument("--psfCache", type=int, default=100, help="Size of CoaddPsf cache")
779  return parser
780 
781  def __init__(self, butler=None, schema=None, peakSchema=None, refObjLoader=None, **kwargs):
782  """!
783  @brief Initialize the task.
784 
785  Keyword arguments (in addition to those forwarded to CmdLineTask.__init__):
786  @param[in] schema: the schema of the merged detection catalog used as input to this one
787  @param[in] peakSchema: the schema of the PeakRecords in the Footprints in the merged detection catalog
788  @param[in] refObjLoader: an instance of LoadReferenceObjectsTasks that supplies an external reference
789  catalog. May be None if the loader can be constructed from the butler argument or all steps
790  requiring a reference catalog are disabled.
791  @param[in] butler: a butler used to read the input schemas from disk or construct the reference
792  catalog loader, if schema or peakSchema or refObjLoader is None
793 
794  The task will set its own self.schema attribute to the schema of the output measurement catalog.
795  This will include all fields from the input schema, as well as additional fields for all the
796  measurements.
797  """
798  CmdLineTask.__init__(self, **kwargs)
799  self.deblended = self.config.inputCatalog.startswith("deblended")
800  self.inputCatalog = "Coadd_" + self.config.inputCatalog
801  if schema is None:
802  assert butler is not None, "Neither butler nor schema is defined"
803  schema = butler.get(self.config.coaddName + self.inputCatalog + "_schema", immediate=True).schema
805  self.schemaMapper.addMinimalSchema(schema)
806  self.schema = self.schemaMapper.getOutputSchema()
808  self.makeSubtask("measurement", schema=self.schema, algMetadata=self.algMetadata)
809  self.makeSubtask("setPrimaryFlags", schema=self.schema)
810  if self.config.doMatchSources:
811  if refObjLoader is None:
812  assert butler is not None, "Neither butler nor refObjLoader is defined"
813  self.makeSubtask("match", butler=butler, refObjLoader=refObjLoader)
814  if self.config.doPropagateFlags:
815  self.makeSubtask("propagateFlags", schema=self.schema)
816  self.schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
817  if self.config.doApCorr:
818  self.makeSubtask("applyApCorr", schema=self.schema)
819  if self.config.doRunCatalogCalculation:
820  self.makeSubtask("catalogCalculation", schema=self.schema)
821 
822  def runDataRef(self, patchRef, psfCache=100):
823  """!
824  @brief Deblend and measure.
825 
826  @param[in] patchRef: Patch reference.
827 
828  Set 'is-primary' and related flags. Propagate flags
829  from individual visits. Optionally match the sources to a reference catalog and write the matches.
830  Finally, write the deblended sources and measurements out.
831  """
832  exposure = patchRef.get(self.config.coaddName + "Coadd_calexp", immediate=True)
833  exposure.getPsf().setCacheCapacity(psfCache)
834  sources = self.readSources(patchRef)
835  table = sources.getTable()
836  table.setMetadata(self.algMetadata) # Capture algorithm metadata to write out to the source catalog.
837 
838  self.measurement.run(sources, exposure, exposureId=self.getExposureId(patchRef))
839 
840  if self.config.doApCorr:
841  self.applyApCorr.run(
842  catalog=sources,
843  apCorrMap=exposure.getInfo().getApCorrMap()
844  )
845 
846  # TODO DM-11568: this contiguous check-and-copy could go away if we
847  # reserve enough space during SourceDetection and/or SourceDeblend.
848  # NOTE: sourceSelectors require contiguous catalogs, so ensure
849  # contiguity now, so views are preserved from here on.
850  if not sources.isContiguous():
851  sources = sources.copy(deep=True)
852 
853  if self.config.doRunCatalogCalculation:
854  self.catalogCalculation.run(sources)
855 
856  skyInfo = getSkyInfo(coaddName=self.config.coaddName, patchRef=patchRef)
857  self.setPrimaryFlags.run(sources, skyInfo.skyMap, skyInfo.tractInfo, skyInfo.patchInfo,
858  includeDeblend=self.deblended)
859  if self.config.doPropagateFlags:
860  self.propagateFlags.run(patchRef.getButler(), sources, self.propagateFlags.getCcdInputs(exposure),
861  exposure.getWcs())
862  if self.config.doMatchSources:
863  self.writeMatches(patchRef, exposure, sources)
864  self.write(patchRef, sources)
865 
866  def readSources(self, dataRef):
867  """!
868  @brief Read input sources.
869 
870  @param[in] dataRef: Data reference for catalog of merged detections
871  @return List of sources in merged catalog
872 
873  We also need to add columns to hold the measurements we're about to make
874  so we can measure in-place.
875  """
876  merged = dataRef.get(self.config.coaddName + self.inputCatalog, immediate=True)
877  self.log.info("Read %d detections: %s" % (len(merged), dataRef.dataId))
878  idFactory = self.makeIdFactory(dataRef)
879  for s in merged:
880  idFactory.notify(s.getId())
881  table = afwTable.SourceTable.make(self.schema, idFactory)
882  sources = afwTable.SourceCatalog(table)
883  sources.extend(merged, self.schemaMapper)
884  return sources
885 
886  def writeMatches(self, dataRef, exposure, sources):
887  """!
888  @brief Write matches of the sources to the astrometric reference catalog.
889 
890  We use the Wcs in the exposure to match sources.
891 
892  @param[in] dataRef: data reference
893  @param[in] exposure: exposure with Wcs
894  @param[in] sources: source catalog
895  """
896  result = self.match.run(sources, exposure.getInfo().getFilter().getName())
897  if result.matches:
898  matches = afwTable.packMatches(result.matches)
899  matches.table.setMetadata(result.matchMeta)
900  dataRef.put(matches, self.config.coaddName + "Coadd_measMatch")
901  if self.config.doWriteMatchesDenormalized:
902  denormMatches = denormalizeMatches(result.matches, result.matchMeta)
903  dataRef.put(denormMatches, self.config.coaddName + "Coadd_measMatchFull")
904 
905  def write(self, dataRef, sources):
906  """!
907  @brief Write the source catalog.
908 
909  @param[in] dataRef: data reference
910  @param[in] sources: source catalog
911  """
912  dataRef.put(sources, self.config.coaddName + "Coadd_meas")
913  self.log.info("Wrote %d sources: %s" % (len(sources), dataRef.dataId))
914 
915  def getExposureId(self, dataRef):
916  return int(dataRef.get(self.config.coaddName + "CoaddId"))
def runDataRef(self, patchRef)
Run detection on a coadd.
Definition: multiBand.py:284
Class for storing ordered metadata with comments.
Definition: PropertyList.h:68
A mapping between the keys of two Schemas, used to copy data between them.
Definition: SchemaMapper.h:21
Configuration parameters for the DetectCoaddSourcesTask.
Definition: multiBand.py:68
def denormalizeMatches(matches, matchMeta=None)
def runDataRef(self, patchRefList, psfCache=100)
Definition: multiBand.py:467
def __init__(self, schema=None, kwargs)
Initialize the task.
Definition: multiBand.py:259
def adaptArgsAndRun(self, inputData, inputDataIds, outputDataIds, butler)
Definition: multiBand.py:299
Deblend sources from master catalog in each coadd seperately and measure.
Definition: multiBand.py:657
def writeMatches(self, dataRef, exposure, sources)
Write matches of the sources to the astrometric reference catalog.
Definition: multiBand.py:886
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations...
def __init__(self, butler=None, schema=None, peakSchema=None, kwargs)
Definition: multiBand.py:438
def readSources(self, dataRef)
Read input sources.
Definition: multiBand.py:866
def write(self, results, patchRef)
Write out results from runDetection.
Definition: multiBand.py:337
template BaseCatalog packMatches(SourceMatchVector const &)
def write(self, dataRef, flux_sources, template_sources=None)
Definition: multiBand.py:533
def write(self, dataRef, sources)
Write the source catalog.
Definition: multiBand.py:905
Configuration parameters for the MeasureMergedCoaddSourcesTask.
Definition: multiBand.py:583
def getSkyInfo(coaddName, patchRef)
Return the SkyMap, tract and patch information, wcs, and outer bbox of the patch to be coadded...
Definition: coaddBase.py:231
def __init__(self, butler=None, schema=None, peakSchema=None, refObjLoader=None, kwargs)
Initialize the task.
Definition: multiBand.py:781
def run(self, exposure, idFactory, expId)
Run detection on an exposure.
Definition: multiBand.py:305
daf::base::PropertyList * list
Definition: fits.cc:833
def runDataRef(self, patchRef, psfCache=100)
Deblend and measure.
Definition: multiBand.py:822