LSST Applications g0b6bd0c080+a72a5dd7e6,g1182afd7b4+2a019aa3bb,g17e5ecfddb+2b8207f7de,g1d67935e3f+06cf436103,g38293774b4+ac198e9f13,g396055baef+6a2097e274,g3b44f30a73+6611e0205b,g480783c3b1+98f8679e14,g48ccf36440+89c08d0516,g4b93dc025c+98f8679e14,g5c4744a4d9+a302e8c7f0,g613e996a0d+e1c447f2e0,g6c8d09e9e7+25247a063c,g7271f0639c+98f8679e14,g7a9cd813b8+124095ede6,g9d27549199+a302e8c7f0,ga1cf026fa3+ac198e9f13,ga32aa97882+7403ac30ac,ga786bb30fb+7a139211af,gaa63f70f4e+9994eb9896,gabf319e997+ade567573c,gba47b54d5d+94dc90c3ea,gbec6a3398f+06cf436103,gc6308e37c7+07dd123edb,gc655b1545f+ade567573c,gcc9029db3c+ab229f5caf,gd01420fc67+06cf436103,gd877ba84e5+06cf436103,gdb4cecd868+6f279b5b48,ge2d134c3d5+cc4dbb2e3f,ge448b5faa6+86d1ceac1d,gecc7e12556+98f8679e14,gf3ee170dca+25247a063c,gf4ac96e456+ade567573c,gf9f5ea5b4d+ac198e9f13,gff490e6085+8c2580be5c,w.2022.27
LSST Data Management Base Package
forcedPhotCcd.py
Go to the documentation of this file.
1# This file is part of meas_base.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
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 GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
21
22import collections
23import logging
24import pandas as pd
25import numpy as np
26
27import lsst.pex.config
29import lsst.pipe.base
30import lsst.geom
32import lsst.afw.geom
33import lsst.afw.image
34import lsst.afw.table
35import lsst.sphgeom
36
37from lsst.obs.base import ExposureIdInfo
38from lsst.pipe.base import PipelineTaskConnections
39import lsst.pipe.base.connectionTypes as cT
40
41import lsst.pipe.base as pipeBase
42from lsst.skymap import BaseSkyMap
43
44from .references import MultiBandReferencesTask
45from .forcedMeasurement import ForcedMeasurementTask
46from .applyApCorr import ApplyApCorrTask
47from .catalogCalculation import CatalogCalculationTask
48
49try:
50 from lsst.meas.mosaic import applyMosaicResults
51except ImportError:
52 applyMosaicResults = None
53
54__all__ = ("PerTractCcdDataIdContainer", "ForcedPhotCcdConfig", "ForcedPhotCcdTask", "imageOverlapsTract",
55 "ForcedPhotCcdFromDataFrameTask", "ForcedPhotCcdFromDataFrameConfig")
56
57
58class PerTractCcdDataIdContainer(pipeBase.DataIdContainer):
59 """A data ID container which combines raw data IDs with a tract.
60
61 Notes
62 -----
63 Required because we need to add "tract" to the raw data ID keys (defined as
64 whatever we use for ``src``) when no tract is provided (so that the user is
65 not required to know which tracts are spanned by the raw data ID).
66
67 This subclass of `~lsst.pipe.base.DataIdContainer` assumes that a calexp is
68 being measured using the detection information, a set of reference
69 catalogs, from the set of coadds which intersect with the calexp. It needs
70 the calexp id (e.g. visit, raft, sensor), but is also uses the tract to
71 decide what set of coadds to use. The references from the tract whose
72 patches intersect with the calexp are used.
73 """
74
75 def makeDataRefList(self, namespace):
76 """Make self.refList from self.idList
77 """
78 if self.datasetType is None:
79 raise RuntimeError("Must call setDatasetType first")
80 log = logging.getLogger(__name__).getChild("PerTractCcdDataIdContainer")
81 skymap = None
82 visitTract = collections.defaultdict(set) # Set of tracts for each visit
83 visitRefs = collections.defaultdict(list) # List of data references for each visit
84 for dataId in self.idList:
85 if "tract" not in dataId:
86 # Discover which tracts the data overlaps
87 log.info("Reading WCS for components of dataId=%s to determine tracts", dict(dataId))
88 if skymap is None:
89 skymap = namespace.butler.get(namespace.config.coaddName + "Coadd_skyMap")
90
91 for ref in namespace.butler.subset("calexp", dataId=dataId):
92 if not ref.datasetExists("calexp"):
93 continue
94
95 visit = ref.dataId["visit"]
96 visitRefs[visit].append(ref)
97
98 md = ref.get("calexp_md", immediate=True)
101 # Going with just the nearest tract. Since we're throwing all tracts for the visit
102 # together, this shouldn't be a problem unless the tracts are much smaller than a CCD.
103 tract = skymap.findTract(wcs.pixelToSky(box.getCenter()))
104 if imageOverlapsTract(tract, wcs, box):
105 visitTract[visit].add(tract.getId())
106 else:
107 self.refList.extend(ref for ref in namespace.butler.subset(self.datasetType, dataId=dataId))
108
109 # Ensure all components of a visit are kept together by putting them all in the same set of tracts
110 for visit, tractSet in visitTract.items():
111 for ref in visitRefs[visit]:
112 for tract in tractSet:
113 self.refList.append(namespace.butler.dataRef(datasetType=self.datasetType,
114 dataId=ref.dataId, tract=tract))
115 if visitTract:
116 tractCounter = collections.Counter()
117 for tractSet in visitTract.values():
118 tractCounter.update(tractSet)
119 log.info("Number of visits for each tract: %s", dict(tractCounter))
120
121
122def imageOverlapsTract(tract, imageWcs, imageBox):
123 """Return whether the given bounding box overlaps the tract given a WCS.
124
125 Parameters
126 ----------
127 tract : `lsst.skymap.TractInfo`
128 TractInfo specifying a tract.
129 imageWcs : `lsst.afw.geom.SkyWcs`
130 World coordinate system for the image.
131 imageBox : `lsst.geom.Box2I`
132 Bounding box for the image.
133
134 Returns
135 -------
136 overlap : `bool`
137 `True` if the bounding box overlaps the tract; `False` otherwise.
138 """
139 tractPoly = tract.getOuterSkyPolygon()
140
141 imagePixelCorners = lsst.geom.Box2D(imageBox).getCorners()
142 try:
143 imageSkyCorners = imageWcs.pixelToSky(imagePixelCorners)
144 except lsst.pex.exceptions.LsstCppException as e:
145 # Protecting ourselves from awful Wcs solutions in input images
146 if (not isinstance(e.message, lsst.pex.exceptions.DomainErrorException)
147 and not isinstance(e.message, lsst.pex.exceptions.RuntimeErrorException)):
148 raise
149 return False
150
151 imagePoly = lsst.sphgeom.ConvexPolygon.convexHull([coord.getVector() for coord in imageSkyCorners])
152 return tractPoly.intersects(imagePoly) # "intersects" also covers "contains" or "is contained by"
153
154
155class ForcedPhotCcdConnections(PipelineTaskConnections,
156 dimensions=("instrument", "visit", "detector", "skymap", "tract"),
157 defaultTemplates={"inputCoaddName": "deep",
158 "inputName": "calexp",
159 "skyWcsName": "jointcal",
160 "photoCalibName": "fgcm"}):
161 inputSchema = cT.InitInput(
162 doc="Schema for the input measurement catalogs.",
163 name="{inputCoaddName}Coadd_ref_schema",
164 storageClass="SourceCatalog",
165 )
166 outputSchema = cT.InitOutput(
167 doc="Schema for the output forced measurement catalogs.",
168 name="forced_src_schema",
169 storageClass="SourceCatalog",
170 )
171 exposure = cT.Input(
172 doc="Input exposure to perform photometry on.",
173 name="{inputName}",
174 storageClass="ExposureF",
175 dimensions=["instrument", "visit", "detector"],
176 )
177 refCat = cT.Input(
178 doc="Catalog of shapes and positions at which to force photometry.",
179 name="{inputCoaddName}Coadd_ref",
180 storageClass="SourceCatalog",
181 dimensions=["skymap", "tract", "patch"],
182 multiple=True,
183 deferLoad=True,
184 )
185 skyMap = cT.Input(
186 doc="SkyMap dataset that defines the coordinate system of the reference catalog.",
187 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
188 storageClass="SkyMap",
189 dimensions=["skymap"],
190 )
191 skyCorr = cT.Input(
192 doc="Input Sky Correction to be subtracted from the calexp if doApplySkyCorr=True",
193 name="skyCorr",
194 storageClass="Background",
195 dimensions=("instrument", "visit", "detector"),
196 )
197 externalSkyWcsTractCatalog = cT.Input(
198 doc=("Per-tract, per-visit wcs calibrations. These catalogs use the detector "
199 "id for the catalog id, sorted on id for fast lookup."),
200 name="{skyWcsName}SkyWcsCatalog",
201 storageClass="ExposureCatalog",
202 dimensions=["instrument", "visit", "tract"],
203 )
204 externalSkyWcsGlobalCatalog = cT.Input(
205 doc=("Per-visit wcs calibrations computed globally (with no tract information). "
206 "These catalogs use the detector id for the catalog id, sorted on id for "
207 "fast lookup."),
208 name="{skyWcsName}SkyWcsCatalog",
209 storageClass="ExposureCatalog",
210 dimensions=["instrument", "visit"],
211 )
212 externalPhotoCalibTractCatalog = cT.Input(
213 doc=("Per-tract, per-visit photometric calibrations. These catalogs use the "
214 "detector id for the catalog id, sorted on id for fast lookup."),
215 name="{photoCalibName}PhotoCalibCatalog",
216 storageClass="ExposureCatalog",
217 dimensions=["instrument", "visit", "tract"],
218 )
219 externalPhotoCalibGlobalCatalog = cT.Input(
220 doc=("Per-visit photometric calibrations computed globally (with no tract "
221 "information). These catalogs use the detector id for the catalog id, "
222 "sorted on id for fast lookup."),
223 name="{photoCalibName}PhotoCalibCatalog",
224 storageClass="ExposureCatalog",
225 dimensions=["instrument", "visit"],
226 )
227 finalizedPsfApCorrCatalog = cT.Input(
228 doc=("Per-visit finalized psf models and aperture correction maps. "
229 "These catalogs use the detector id for the catalog id, "
230 "sorted on id for fast lookup."),
231 name="finalized_psf_ap_corr_catalog",
232 storageClass="ExposureCatalog",
233 dimensions=["instrument", "visit"],
234 )
235 measCat = cT.Output(
236 doc="Output forced photometry catalog.",
237 name="forced_src",
238 storageClass="SourceCatalog",
239 dimensions=["instrument", "visit", "detector", "skymap", "tract"],
240 )
241
242 def __init__(self, *, config=None):
243 super().__init__(config=config)
244 if not config.doApplySkyCorr:
245 self.inputs.remove("skyCorr")
246 if config.doApplyExternalSkyWcs:
247 if config.useGlobalExternalSkyWcs:
248 self.inputs.remove("externalSkyWcsTractCatalog")
249 else:
250 self.inputs.remove("externalSkyWcsGlobalCatalog")
251 else:
252 self.inputs.remove("externalSkyWcsTractCatalog")
253 self.inputs.remove("externalSkyWcsGlobalCatalog")
254 if config.doApplyExternalPhotoCalib:
255 if config.useGlobalExternalPhotoCalib:
256 self.inputs.remove("externalPhotoCalibTractCatalog")
257 else:
258 self.inputs.remove("externalPhotoCalibGlobalCatalog")
259 else:
260 self.inputs.remove("externalPhotoCalibTractCatalog")
261 self.inputs.remove("externalPhotoCalibGlobalCatalog")
262 if not config.doApplyFinalizedPsf:
263 self.inputs.remove("finalizedPsfApCorrCatalog")
264
265
266class ForcedPhotCcdConfig(pipeBase.PipelineTaskConfig,
267 pipelineConnections=ForcedPhotCcdConnections):
268 """Config class for forced measurement driver task."""
270 target=MultiBandReferencesTask,
271 doc="subtask to retrieve reference source catalog"
272 )
274 target=ForcedMeasurementTask,
275 doc="subtask to do forced measurement"
276 )
277 coaddName = lsst.pex.config.Field(
278 doc="coadd name: typically one of deep or goodSeeing",
279 dtype=str,
280 default="deep",
281 )
282 doApCorr = lsst.pex.config.Field(
283 dtype=bool,
284 default=True,
285 doc="Run subtask to apply aperture corrections"
286 )
288 target=ApplyApCorrTask,
289 doc="Subtask to apply aperture corrections"
290 )
291 catalogCalculation = lsst.pex.config.ConfigurableField(
292 target=CatalogCalculationTask,
293 doc="Subtask to run catalogCalculation plugins on catalog"
294 )
295 doApplyUberCal = lsst.pex.config.Field(
296 dtype=bool,
297 doc="Apply meas_mosaic ubercal results to input calexps?",
298 default=False,
299 deprecated="Deprecated by DM-23352; use doApplyExternalPhotoCalib and doApplyExternalSkyWcs instead",
300 )
301 doApplyExternalPhotoCalib = lsst.pex.config.Field(
302 dtype=bool,
303 default=False,
304 doc=("Whether to apply external photometric calibration via an "
305 "`lsst.afw.image.PhotoCalib` object. Uses the "
306 "``externalPhotoCalibName`` field to determine which calibration "
307 "to load."),
308 )
309 useGlobalExternalPhotoCalib = lsst.pex.config.Field(
310 dtype=bool,
311 default=True,
312 doc=("When using doApplyExternalPhotoCalib, use 'global' calibrations "
313 "that are not run per-tract. When False, use per-tract photometric "
314 "calibration files.")
315 )
316 doApplyExternalSkyWcs = lsst.pex.config.Field(
317 dtype=bool,
318 default=False,
319 doc=("Whether to apply external astrometric calibration via an "
320 "`lsst.afw.geom.SkyWcs` object. Uses ``externalSkyWcsName`` "
321 "field to determine which calibration to load."),
322 )
323 useGlobalExternalSkyWcs = lsst.pex.config.Field(
324 dtype=bool,
325 default=False,
326 doc=("When using doApplyExternalSkyWcs, use 'global' calibrations "
327 "that are not run per-tract. When False, use per-tract wcs "
328 "files.")
329 )
330 doApplyFinalizedPsf = lsst.pex.config.Field(
331 dtype=bool,
332 default=False,
333 doc="Whether to apply finalized psf models and aperture correction map.",
334 )
335 doApplySkyCorr = lsst.pex.config.Field(
336 dtype=bool,
337 default=False,
338 doc="Apply sky correction?",
339 )
340 includePhotoCalibVar = lsst.pex.config.Field(
341 dtype=bool,
342 default=False,
343 doc="Add photometric calibration variance to warp variance plane?",
344 )
345 externalPhotoCalibName = lsst.pex.config.ChoiceField(
346 dtype=str,
347 doc=("Type of external PhotoCalib if ``doApplyExternalPhotoCalib`` is True. "
348 "Unused for Gen3 middleware."),
349 default="jointcal",
350 allowed={
351 "jointcal": "Use jointcal_photoCalib",
352 "fgcm": "Use fgcm_photoCalib",
353 "fgcm_tract": "Use fgcm_tract_photoCalib"
354 },
355 )
356 externalSkyWcsName = lsst.pex.config.ChoiceField(
357 dtype=str,
358 doc="Type of external SkyWcs if ``doApplyExternalSkyWcs`` is True. Unused for Gen3 middleware.",
359 default="jointcal",
360 allowed={
361 "jointcal": "Use jointcal_wcs"
362 },
363 )
364 footprintSource = lsst.pex.config.ChoiceField(
365 dtype=str,
366 doc="Where to obtain footprints to install in the measurement catalog, prior to measurement.",
367 allowed={
368 "transformed": "Transform footprints from the reference catalog (downgrades HeavyFootprints).",
369 "psf": ("Use the scaled shape of the PSF at the position of each source (does not generate "
370 "HeavyFootprints)."),
371 },
372 optional=True,
373 default="transformed",
374 )
375 psfFootprintScaling = lsst.pex.config.Field(
376 dtype=float,
377 doc="Scaling factor to apply to the PSF shape when footprintSource='psf' (ignored otherwise).",
378 default=3.0,
379 )
380
381 def setDefaults(self):
382 # Docstring inherited.
383 # Make catalogCalculation a no-op by default as no modelFlux is setup by default in
384 # ForcedMeasurementTask
385 super().setDefaults()
386 self.measurement.plugins.names |= ['base_LocalPhotoCalib', 'base_LocalWcs']
387 self.catalogCalculation.plugins.names = []
388
389
390class ForcedPhotCcdTask(pipeBase.PipelineTask, pipeBase.CmdLineTask):
391 """A command-line driver for performing forced measurement on CCD images.
392
393 Parameters
394 ----------
395 butler : `lsst.daf.persistence.butler.Butler`, optional
396 A Butler which will be passed to the references subtask to allow it to
397 load its schema from disk. Optional, but must be specified if
398 ``refSchema`` is not; if both are specified, ``refSchema`` takes
399 precedence.
400 refSchema : `lsst.afw.table.Schema`, optional
401 The schema of the reference catalog, passed to the constructor of the
402 references subtask. Optional, but must be specified if ``butler`` is
403 not; if both are specified, ``refSchema`` takes precedence.
404 **kwds
405 Keyword arguments are passed to the supertask constructor.
406
407 Notes
408 -----
409 The `runDataRef` method takes a `~lsst.daf.persistence.ButlerDataRef` argument
410 that corresponds to a single CCD. This should contain the data ID keys that
411 correspond to the ``forced_src`` dataset (the output dataset for this
412 task), which are typically all those used to specify the ``calexp`` dataset
413 (``visit``, ``raft``, ``sensor`` for LSST data) as well as a coadd tract.
414 The tract is used to look up the appropriate coadd measurement catalogs to
415 use as references (e.g. ``deepCoadd_src``; see
417 information). While the tract must be given as part of the dataRef, the
418 patches are determined automatically from the bounding box and WCS of the
419 calexp to be measured, and the filter used to fetch references is set via
420 the ``filter`` option in the configuration of
422 """
423
424 ConfigClass = ForcedPhotCcdConfig
425 RunnerClass = pipeBase.ButlerInitializedTaskRunner
426 _DefaultName = "forcedPhotCcd"
427 dataPrefix = ""
428
429 def __init__(self, butler=None, refSchema=None, initInputs=None, **kwds):
430 super().__init__(**kwds)
431
432 if initInputs is not None:
433 refSchema = initInputs['inputSchema'].schema
434
435 self.makeSubtask("references", butler=butler, schema=refSchema)
436 if refSchema is None:
437 refSchema = self.references.schema
438 self.makeSubtask("measurement", refSchema=refSchema)
439 # It is necessary to get the schema internal to the forced measurement task until such a time
440 # that the schema is not owned by the measurement task, but is passed in by an external caller
441 if self.config.doApCorr:
442 self.makeSubtask("applyApCorr", schema=self.measurement.schema)
443 self.makeSubtask('catalogCalculation', schema=self.measurement.schema)
444 self.outputSchema = lsst.afw.table.SourceCatalog(self.measurement.schema)
445
446 def runQuantum(self, butlerQC, inputRefs, outputRefs):
447 inputs = butlerQC.get(inputRefs)
448
449 tract = butlerQC.quantum.dataId['tract']
450 skyMap = inputs.pop('skyMap')
451 inputs['refWcs'] = skyMap[tract].getWcs()
452
453 # Connections only exist if they are configured to be used.
454 skyCorr = inputs.pop('skyCorr', None)
455 if self.config.useGlobalExternalSkyWcs:
456 externalSkyWcsCatalog = inputs.pop('externalSkyWcsGlobalCatalog', None)
457 else:
458 externalSkyWcsCatalog = inputs.pop('externalSkyWcsTractCatalog', None)
459 if self.config.useGlobalExternalPhotoCalib:
460 externalPhotoCalibCatalog = inputs.pop('externalPhotoCalibGlobalCatalog', None)
461 else:
462 externalPhotoCalibCatalog = inputs.pop('externalPhotoCalibTractCatalog', None)
463 finalizedPsfApCorrCatalog = inputs.pop('finalizedPsfApCorrCatalog', None)
464
465 inputs['exposure'] = self.prepareCalibratedExposure(
466 inputs['exposure'],
467 skyCorr=skyCorr,
468 externalSkyWcsCatalog=externalSkyWcsCatalog,
469 externalPhotoCalibCatalog=externalPhotoCalibCatalog,
470 finalizedPsfApCorrCatalog=finalizedPsfApCorrCatalog
471 )
472
473 inputs['refCat'] = self.mergeAndFilterReferences(inputs['exposure'], inputs['refCat'],
474 inputs['refWcs'])
475
476 inputs['measCat'], inputs['exposureId'] = self.generateMeasCat(inputRefs.exposure.dataId,
477 inputs['exposure'],
478 inputs['refCat'], inputs['refWcs'],
479 "visit_detector")
480 self.attachFootprints(inputs['measCat'], inputs['refCat'], inputs['exposure'], inputs['refWcs'])
481 outputs = self.run(**inputs)
482 butlerQC.put(outputs, outputRefs)
483
484 def prepareCalibratedExposure(self, exposure, skyCorr=None, externalSkyWcsCatalog=None,
485 externalPhotoCalibCatalog=None, finalizedPsfApCorrCatalog=None):
486 """Prepare a calibrated exposure and apply external calibrations
487 and sky corrections if so configured.
488
489 Parameters
490 ----------
492 Input exposure to adjust calibrations.
493 skyCorr : `lsst.afw.math.backgroundList`, optional
494 Sky correction frame to apply if doApplySkyCorr=True.
495 externalSkyWcsCatalog : `lsst.afw.table.ExposureCatalog`, optional
496 Exposure catalog with external skyWcs to be applied
497 if config.doApplyExternalSkyWcs=True. Catalog uses the detector id
498 for the catalog id, sorted on id for fast lookup.
499 externalPhotoCalibCatalog : `lsst.afw.table.ExposureCatalog`, optional
500 Exposure catalog with external photoCalib to be applied
501 if config.doApplyExternalPhotoCalib=True. Catalog uses the detector
502 id for the catalog id, sorted on id for fast lookup.
503 finalizedPsfApCorrCatalog : `lsst.afw.table.ExposureCatalog`, optional
504 Exposure catalog with finalized psf models and aperture correction
505 maps to be applied if config.doApplyFinalizedPsf=True. Catalog uses
506 the detector id for the catalog id, sorted on id for fast lookup.
507
508 Returns
509 -------
511 Exposure with adjusted calibrations.
512 """
513 detectorId = exposure.getInfo().getDetector().getId()
514
515 if externalPhotoCalibCatalog is not None:
516 row = externalPhotoCalibCatalog.find(detectorId)
517 if row is None:
518 self.log.warning("Detector id %s not found in externalPhotoCalibCatalog; "
519 "Using original photoCalib.", detectorId)
520 else:
521 photoCalib = row.getPhotoCalib()
522 if photoCalib is None:
523 self.log.warning("Detector id %s has None for photoCalib in externalPhotoCalibCatalog; "
524 "Using original photoCalib.", detectorId)
525 else:
526 exposure.setPhotoCalib(photoCalib)
527
528 if externalSkyWcsCatalog is not None:
529 row = externalSkyWcsCatalog.find(detectorId)
530 if row is None:
531 self.log.warning("Detector id %s not found in externalSkyWcsCatalog; "
532 "Using original skyWcs.", detectorId)
533 else:
534 skyWcs = row.getWcs()
535 if skyWcs is None:
536 self.log.warning("Detector id %s has None for skyWcs in externalSkyWcsCatalog; "
537 "Using original skyWcs.", detectorId)
538 else:
539 exposure.setWcs(skyWcs)
540
541 if finalizedPsfApCorrCatalog is not None:
542 row = finalizedPsfApCorrCatalog.find(detectorId)
543 if row is None:
544 self.log.warning("Detector id %s not found in finalizedPsfApCorrCatalog; "
545 "Using original psf.", detectorId)
546 else:
547 psf = row.getPsf()
548 apCorrMap = row.getApCorrMap()
549 if psf is None or apCorrMap is None:
550 self.log.warning("Detector id %s has None for psf/apCorrMap in "
551 "finalizedPsfApCorrCatalog; Using original psf.", detectorId)
552 else:
553 exposure.setPsf(psf)
554 exposure.setApCorrMap(apCorrMap)
555
556 if skyCorr is not None:
557 exposure.maskedImage -= skyCorr.getImage()
558
559 return exposure
560
561 def mergeAndFilterReferences(self, exposure, refCats, refWcs):
562 """Filter reference catalog so that all sources are within the
563 boundaries of the exposure.
564
565 Parameters
566 ----------
568 Exposure to generate the catalog for.
569 refCats : sequence of `lsst.daf.butler.DeferredDatasetHandle`
570 Handles for catalogs of shapes and positions at which to force
571 photometry.
572 refWcs : `lsst.afw.image.SkyWcs`
573 Reference world coordinate system.
574
575 Returns
576 -------
577 refSources : `lsst.afw.table.SourceCatalog`
578 Filtered catalog of forced sources to measure.
579
580 Notes
581 -----
582 Filtering the reference catalog is currently handled by Gen2
583 specific methods. To function for Gen3, this method copies
584 code segments to do the filtering and transformation. The
585 majority of this code is based on the methods of
587
588 """
589
590 # Step 1: Determine bounds of the exposure photometry will
591 # be performed on.
592 expWcs = exposure.getWcs()
593 expRegion = exposure.getBBox(lsst.afw.image.PARENT)
594 expBBox = lsst.geom.Box2D(expRegion)
595 expBoxCorners = expBBox.getCorners()
596 expSkyCorners = [expWcs.pixelToSky(corner).getVector() for
597 corner in expBoxCorners]
598 expPolygon = lsst.sphgeom.ConvexPolygon(expSkyCorners)
599
600 # Step 2: Filter out reference catalog sources that are
601 # not contained within the exposure boundaries, or whose
602 # parents are not within the exposure boundaries. Note
603 # that within a single input refCat, the parents always
604 # appear before the children.
605 mergedRefCat = None
606 for refCat in refCats:
607 refCat = refCat.get()
608 if mergedRefCat is None:
609 mergedRefCat = lsst.afw.table.SourceCatalog(refCat.table)
610 containedIds = {0} # zero as a parent ID means "this is a parent"
611 for record in refCat:
612 if expPolygon.contains(record.getCoord().getVector()) and record.getParent() in containedIds:
613 record.setFootprint(record.getFootprint())
614 mergedRefCat.append(record)
615 containedIds.add(record.getId())
616 if mergedRefCat is None:
617 raise RuntimeError("No reference objects for forced photometry.")
618 mergedRefCat.sort(lsst.afw.table.SourceTable.getParentKey())
619 return mergedRefCat
620
621 def generateMeasCat(self, exposureDataId, exposure, refCat, refWcs, idPackerName):
622 """Generate a measurement catalog for Gen3.
623
624 Parameters
625 ----------
626 exposureDataId : `DataId`
627 Butler dataId for this exposure.
629 Exposure to generate the catalog for.
631 Catalog of shapes and positions at which to force photometry.
632 refWcs : `lsst.afw.image.SkyWcs`
633 Reference world coordinate system.
634 This parameter is not currently used.
635 idPackerName : `str`
636 Type of ID packer to construct from the registry.
637
638 Returns
639 -------
641 Catalog of forced sources to measure.
642 expId : `int`
643 Unique binary id associated with the input exposure
644 """
645 exposureIdInfo = ExposureIdInfo.fromDataId(exposureDataId, idPackerName)
646 idFactory = exposureIdInfo.makeSourceIdFactory()
647
648 measCat = self.measurement.generateMeasCat(exposure, refCat, refWcs,
649 idFactory=idFactory)
650 return measCat, exposureIdInfo.expId
651
652 def runDataRef(self, dataRef, psfCache=None):
653 """Perform forced measurement on a single exposure.
654
655 Parameters
656 ----------
658 Passed to the ``references`` subtask to obtain the reference WCS,
659 the ``getExposure`` method (implemented by derived classes) to
660 read the measurment image, and the ``fetchReferences`` method to
661 get the exposure and load the reference catalog (see
663 Refer to derived class documentation for details of the datasets
664 and data ID keys which are used.
665 psfCache : `int`, optional
666 Size of PSF cache, or `None`. The size of the PSF cache can have
667 a significant effect upon the runtime for complicated PSF models.
668
669 Notes
670 -----
671 Sources are generated with ``generateMeasCat`` in the ``measurement``
672 subtask. These are passed to ``measurement``'s ``run`` method, which
673 fills the source catalog with the forced measurement results. The
674 sources are then passed to the ``writeOutputs`` method (implemented by
675 derived classes) which writes the outputs.
676 """
677 refWcs = self.references.getWcs(dataRef)
678 exposure = self.getExposure(dataRef)
679 if psfCache is not None:
680 exposure.getPsf().setCacheSize(psfCache)
681 refCat = self.fetchReferences(dataRef, exposure)
682 measCat = self.measurement.generateMeasCat(exposure, refCat, refWcs,
683 idFactory=self.makeIdFactory(dataRef))
684 self.log.info("Performing forced measurement on %s", dataRef.dataId)
685 self.attachFootprints(measCat, refCat, exposure, refWcs)
686
687 exposureId = self.getExposureId(dataRef)
688
689 forcedPhotResult = self.run(measCat, exposure, refCat, refWcs, exposureId=exposureId)
690
691 self.writeOutput(dataRef, forcedPhotResult.measCat)
692
693 def run(self, measCat, exposure, refCat, refWcs, exposureId=None):
694 """Perform forced measurement on a single exposure.
695
696 Parameters
697 ----------
699 The measurement catalog, based on the sources listed in the
700 reference catalog.
701 exposure : `lsst.afw.image.Exposure`
702 The measurement image upon which to perform forced detection.
704 The reference catalog of sources to measure.
705 refWcs : `lsst.afw.image.SkyWcs`
706 The WCS for the references.
707 exposureId : `int`
708 Optional unique exposureId used for random seed in measurement
709 task.
710
711 Returns
712 -------
713 result : `lsst.pipe.base.Struct`
714 Structure with fields:
715
716 ``measCat``
717 Catalog of forced measurement results
719 """
720 self.measurement.run(measCat, exposure, refCat, refWcs, exposureId=exposureId)
721 if self.config.doApCorr:
722 self.applyApCorr.run(
723 catalog=measCat,
724 apCorrMap=exposure.getInfo().getApCorrMap()
725 )
726 self.catalogCalculation.run(measCat)
727
728 return pipeBase.Struct(measCat=measCat)
729
730 def makeIdFactory(self, dataRef):
731 """Create an object that generates globally unique source IDs.
732
733 Source IDs are created based on a per-CCD ID and the ID of the CCD
734 itself.
735
736 Parameters
737 ----------
739 Butler data reference. The ``ccdExposureId_bits`` and
740 ``ccdExposureId`` datasets are accessed. The data ID must have the
741 keys that correspond to ``ccdExposureId``, which are generally the
742 same as those that correspond to ``calexp`` (``visit``, ``raft``,
743 ``sensor`` for LSST data).
744 """
745 exposureIdInfo = ExposureIdInfo(int(dataRef.get("ccdExposureId")), dataRef.get("ccdExposureId_bits"))
746 return exposureIdInfo.makeSourceIdFactory()
747
748 def getExposureId(self, dataRef):
749 return int(dataRef.get("ccdExposureId", immediate=True))
750
751 def fetchReferences(self, dataRef, exposure):
752 """Get sources that overlap the exposure.
753
754 Parameters
755 ----------
757 Butler data reference corresponding to the image to be measured;
758 should have ``tract``, ``patch``, and ``filter`` keys.
759 exposure : `lsst.afw.image.Exposure`
760 The image to be measured (used only to obtain a WCS and bounding
761 box).
762
763 Returns
764 -------
765 referencs : `lsst.afw.table.SourceCatalog`
766 Catalog of sources that overlap the exposure
767
768 Notes
769 -----
770 The returned catalog is sorted by ID and guarantees that all included
771 children have their parent included and that all Footprints are valid.
772
773 All work is delegated to the references subtask; see
775 for information about the default behavior.
776 """
777 references = lsst.afw.table.SourceCatalog(self.references.schema)
778 badParents = set()
779 unfiltered = self.references.fetchInBox(dataRef, exposure.getBBox(), exposure.getWcs())
780 for record in unfiltered:
781 if record.getFootprint() is None or record.getFootprint().getArea() == 0:
782 if record.getParent() != 0:
783 self.log.warning("Skipping reference %s (child of %s) with bad Footprint",
784 record.getId(), record.getParent())
785 else:
786 self.log.warning("Skipping reference parent %s with bad Footprint", record.getId())
787 badParents.add(record.getId())
788 elif record.getParent() not in badParents:
789 references.append(record)
790 # catalog must be sorted by parent ID for lsst.afw.table.getChildren to work
792 return references
793
794 def attachFootprints(self, sources, refCat, exposure, refWcs):
795 r"""Attach footprints to blank sources prior to measurements.
796
797 Notes
798 -----
799 `~lsst.afw.detection.Footprint`\ s for forced photometry must be in the
800 pixel coordinate system of the image being measured, while the actual
801 detections may start out in a different coordinate system.
802
803 Subclasses of this class may implement this method to define how
804 those `~lsst.afw.detection.Footprint`\ s should be generated.
805
806 This default implementation transforms depends on the
807 ``footprintSource`` configuration parameter.
808 """
809 if self.config.footprintSource == "transformed":
810 return self.measurement.attachTransformedFootprints(sources, refCat, exposure, refWcs)
811 elif self.config.footprintSource == "psf":
812 return self.measurement.attachPsfShapeFootprints(sources, exposure,
813 scaling=self.config.psfFootprintScaling)
814
815 def getExposure(self, dataRef):
816 """Read input exposure for measurement.
817
818 Parameters
819 ----------
821 Butler data reference.
822 """
823 exposure = dataRef.get(self.dataPrefix + "calexp", immediate=True)
824
825 if self.config.doApplyExternalPhotoCalib:
826 source = f"{self.config.externalPhotoCalibName}_photoCalib"
827 self.log.info("Applying external photoCalib from %s", source)
828 photoCalib = dataRef.get(source)
829 exposure.setPhotoCalib(photoCalib) # No need for calibrateImage; having the photoCalib suffices
830
831 if self.config.doApplyExternalSkyWcs:
832 source = f"{self.config.externalSkyWcsName}_wcs"
833 self.log.info("Applying external skyWcs from %s", source)
834 skyWcs = dataRef.get(source)
835 exposure.setWcs(skyWcs)
836
837 if self.config.doApplySkyCorr:
838 self.log.info("Apply sky correction")
839 skyCorr = dataRef.get("skyCorr")
840 exposure.maskedImage -= skyCorr.getImage()
841
842 return exposure
843
844 def writeOutput(self, dataRef, sources):
845 """Write forced source table
846
847 Parameters
848 ----------
850 Butler data reference. The forced_src dataset (with
851 self.dataPrefix prepended) is all that will be modified.
853 Catalog of sources to save.
854 """
855 dataRef.put(sources, self.dataPrefix + "forced_src", flags=lsst.afw.table.SOURCE_IO_NO_FOOTPRINTS)
856
857 def getSchemaCatalogs(self):
858 """The schema catalogs that will be used by this task.
859
860 Returns
861 -------
862 schemaCatalogs : `dict`
863 Dictionary mapping dataset type to schema catalog.
864
865 Notes
866 -----
867 There is only one schema for each type of forced measurement. The
868 dataset type for this measurement is defined in the mapper.
869 """
870 catalog = lsst.afw.table.SourceCatalog(self.measurement.schema)
871 catalog.getTable().setMetadata(self.measurement.algMetadata)
872 datasetType = self.dataPrefix + "forced_src"
873 return {datasetType: catalog}
874
875 def _getConfigName(self):
876 # Documented in superclass.
877 return self.dataPrefix + "forcedPhotCcd_config"
878
879 def _getMetadataName(self):
880 # Documented in superclass
881 return self.dataPrefix + "forcedPhotCcd_metadata"
882
883 @classmethod
884 def _makeArgumentParser(cls):
885 parser = pipeBase.ArgumentParser(name=cls._DefaultName)
886 parser.add_id_argument("--id", "forced_src", help="data ID with raw CCD keys [+ tract optionally], "
887 "e.g. --id visit=12345 ccd=1,2 [tract=0]",
888 ContainerClass=PerTractCcdDataIdContainer)
889 return parser
890
891
892class ForcedPhotCcdFromDataFrameConnections(PipelineTaskConnections,
893 dimensions=("instrument", "visit", "detector", "skymap", "tract"),
894 defaultTemplates={"inputCoaddName": "goodSeeing",
895 "inputName": "calexp",
896 "skyWcsName": "jointcal",
897 "photoCalibName": "fgcm"}):
898 refCat = cT.Input(
899 doc="Catalog of positions at which to force photometry.",
900 name="{inputCoaddName}Diff_fullDiaObjTable",
901 storageClass="DataFrame",
902 dimensions=["skymap", "tract", "patch"],
903 multiple=True,
904 deferLoad=True,
905 )
906 exposure = cT.Input(
907 doc="Input exposure to perform photometry on.",
908 name="{inputName}",
909 storageClass="ExposureF",
910 dimensions=["instrument", "visit", "detector"],
911 )
912 skyCorr = cT.Input(
913 doc="Input Sky Correction to be subtracted from the calexp if doApplySkyCorr=True",
914 name="skyCorr",
915 storageClass="Background",
916 dimensions=("instrument", "visit", "detector"),
917 )
918 externalSkyWcsTractCatalog = cT.Input(
919 doc=("Per-tract, per-visit wcs calibrations. These catalogs use the detector "
920 "id for the catalog id, sorted on id for fast lookup."),
921 name="{skyWcsName}SkyWcsCatalog",
922 storageClass="ExposureCatalog",
923 dimensions=["instrument", "visit", "tract"],
924 )
925 externalSkyWcsGlobalCatalog = cT.Input(
926 doc=("Per-visit wcs calibrations computed globally (with no tract information). "
927 "These catalogs use the detector id for the catalog id, sorted on id for "
928 "fast lookup."),
929 name="{skyWcsName}SkyWcsCatalog",
930 storageClass="ExposureCatalog",
931 dimensions=["instrument", "visit"],
932 )
933 externalPhotoCalibTractCatalog = cT.Input(
934 doc=("Per-tract, per-visit photometric calibrations. These catalogs use the "
935 "detector id for the catalog id, sorted on id for fast lookup."),
936 name="{photoCalibName}PhotoCalibCatalog",
937 storageClass="ExposureCatalog",
938 dimensions=["instrument", "visit", "tract"],
939 )
940 externalPhotoCalibGlobalCatalog = cT.Input(
941 doc=("Per-visit photometric calibrations computed globally (with no tract "
942 "information). These catalogs use the detector id for the catalog id, "
943 "sorted on id for fast lookup."),
944 name="{photoCalibName}PhotoCalibCatalog",
945 storageClass="ExposureCatalog",
946 dimensions=["instrument", "visit"],
947 )
948 finalizedPsfApCorrCatalog = cT.Input(
949 doc=("Per-visit finalized psf models and aperture correction maps. "
950 "These catalogs use the detector id for the catalog id, "
951 "sorted on id for fast lookup."),
952 name="finalized_psf_ap_corr_catalog",
953 storageClass="ExposureCatalog",
954 dimensions=["instrument", "visit"],
955 )
956 measCat = cT.Output(
957 doc="Output forced photometry catalog.",
958 name="forced_src_diaObject",
959 storageClass="SourceCatalog",
960 dimensions=["instrument", "visit", "detector", "skymap", "tract"],
961 )
962 outputSchema = cT.InitOutput(
963 doc="Schema for the output forced measurement catalogs.",
964 name="forced_src_diaObject_schema",
965 storageClass="SourceCatalog",
966 )
967
968 def __init__(self, *, config=None):
969 super().__init__(config=config)
970 if not config.doApplySkyCorr:
971 self.inputs.remove("skyCorr")
972 if config.doApplyExternalSkyWcs:
973 if config.useGlobalExternalSkyWcs:
974 self.inputs.remove("externalSkyWcsTractCatalog")
975 else:
976 self.inputs.remove("externalSkyWcsGlobalCatalog")
977 else:
978 self.inputs.remove("externalSkyWcsTractCatalog")
979 self.inputs.remove("externalSkyWcsGlobalCatalog")
980 if config.doApplyExternalPhotoCalib:
981 if config.useGlobalExternalPhotoCalib:
982 self.inputs.remove("externalPhotoCalibTractCatalog")
983 else:
984 self.inputs.remove("externalPhotoCalibGlobalCatalog")
985 else:
986 self.inputs.remove("externalPhotoCalibTractCatalog")
987 self.inputs.remove("externalPhotoCalibGlobalCatalog")
988 if not config.doApplyFinalizedPsf:
989 self.inputs.remove("finalizedPsfApCorrCatalog")
990
991
992class ForcedPhotCcdFromDataFrameConfig(ForcedPhotCcdConfig,
993 pipelineConnections=ForcedPhotCcdFromDataFrameConnections):
994 def setDefaults(self):
995 super().setDefaults()
996 self.footprintSource = "psf"
997 self.measurement.doReplaceWithNoise = False
998 self.measurement.plugins = ["base_TransformedCentroidFromCoord", "base_PsfFlux", "base_PixelFlags"]
999 self.measurement.copyColumns = {'id': 'diaObjectId', 'coord_ra': 'coord_ra', 'coord_dec': 'coord_dec'}
1000 self.measurement.slots.centroid = "base_TransformedCentroidFromCoord"
1001 self.measurement.slots.psfFlux = "base_PsfFlux"
1002 self.measurement.slots.shape = None
1003
1004 def validate(self):
1005 super().validate()
1006 if self.footprintSource == "transformed":
1007 raise ValueError("Cannot transform footprints from reference catalog, "
1008 "because DataFrames can't hold footprints.")
1009
1010
1011class ForcedPhotCcdFromDataFrameTask(ForcedPhotCcdTask):
1012 """Force Photometry on a per-detector exposure with coords from a DataFrame
1013
1014 Uses input from a DataFrame instead of SourceCatalog
1015 like the base class ForcedPhotCcd does.
1016 Writes out a SourceCatalog so that the downstream
1017 WriteForcedSourceTableTask can be reused with output from this Task.
1018 """
1019 _DefaultName = "forcedPhotCcdFromDataFrame"
1020 ConfigClass = ForcedPhotCcdFromDataFrameConfig
1021
1022 def __init__(self, butler=None, refSchema=None, initInputs=None, **kwds):
1023 # Parent's init assumes that we have a reference schema; Cannot reuse
1024 pipeBase.PipelineTask.__init__(self, **kwds)
1025
1026 self.makeSubtask("measurement", refSchema=lsst.afw.table.SourceTable.makeMinimalSchema())
1027
1028 if self.config.doApCorr:
1029 self.makeSubtask("applyApCorr", schema=self.measurement.schema)
1030 self.makeSubtask('catalogCalculation', schema=self.measurement.schema)
1031 self.outputSchema = lsst.afw.table.SourceCatalog(self.measurement.schema)
1032
1033 def runQuantum(self, butlerQC, inputRefs, outputRefs):
1034 inputs = butlerQC.get(inputRefs)
1035
1036 # When run with dataframes, we do not need a reference wcs.
1037 inputs['refWcs'] = None
1038
1039 # Connections only exist if they are configured to be used.
1040 skyCorr = inputs.pop('skyCorr', None)
1041 if self.config.useGlobalExternalSkyWcs:
1042 externalSkyWcsCatalog = inputs.pop('externalSkyWcsGlobalCatalog', None)
1043 else:
1044 externalSkyWcsCatalog = inputs.pop('externalSkyWcsTractCatalog', None)
1045 if self.config.useGlobalExternalPhotoCalib:
1046 externalPhotoCalibCatalog = inputs.pop('externalPhotoCalibGlobalCatalog', None)
1047 else:
1048 externalPhotoCalibCatalog = inputs.pop('externalPhotoCalibTractCatalog', None)
1049 finalizedPsfApCorrCatalog = inputs.pop('finalizedPsfApCorrCatalog', None)
1050
1051 inputs['exposure'] = self.prepareCalibratedExposure(
1052 inputs['exposure'],
1053 skyCorr=skyCorr,
1054 externalSkyWcsCatalog=externalSkyWcsCatalog,
1055 externalPhotoCalibCatalog=externalPhotoCalibCatalog,
1056 finalizedPsfApCorrCatalog=finalizedPsfApCorrCatalog
1057 )
1058
1059 self.log.info("Filtering ref cats: %s", ','.join([str(i.dataId) for i in inputs['refCat']]))
1060 refCat = self.df2RefCat([i.get(parameters={"columns": ['diaObjectId', 'ra', 'decl']})
1061 for i in inputs['refCat']],
1062 inputs['exposure'].getBBox(), inputs['exposure'].getWcs())
1063 inputs['refCat'] = refCat
1064 # generateMeasCat does not use the refWcs.
1065 inputs['measCat'], inputs['exposureId'] = self.generateMeasCat(inputRefs.exposure.dataId,
1066 inputs['exposure'], inputs['refCat'],
1067 inputs['refWcs'],
1068 "visit_detector")
1069 # attachFootprints only uses refWcs in ``transformed`` mode, which is not
1070 # supported in the DataFrame-backed task.
1071 self.attachFootprints(inputs["measCat"], inputs["refCat"], inputs["exposure"], inputs["refWcs"])
1072 outputs = self.run(**inputs)
1073
1074 butlerQC.put(outputs, outputRefs)
1075
1076 def df2RefCat(self, dfList, exposureBBox, exposureWcs):
1077 """Convert list of DataFrames to reference catalog
1078
1079 Concatenate list of DataFrames presumably from multiple patches and
1080 downselect rows that overlap the exposureBBox using the exposureWcs.
1081
1082 Parameters
1083 ----------
1084 dfList : `list` of `pandas.DataFrame`
1085 Each element containst diaObjects with ra/decl position in degrees
1086 Columns 'diaObjectId', 'ra', 'decl' are expected
1087 exposureBBox : `lsst.geom.Box2I`
1088 Bounding box on which to select rows that overlap
1089 exposureWcs : `lsst.afw.geom.SkyWcs`
1090 World coordinate system to convert sky coords in ref cat to
1091 pixel coords with which to compare with exposureBBox
1092
1093 Returns
1094 -------
1096 Source Catalog with minimal schema that overlaps exposureBBox
1097 """
1098 df = pd.concat(dfList)
1099 # translate ra/decl coords in dataframe to detector pixel coords
1100 # to down select rows that overlap the detector bbox
1101 mapping = exposureWcs.getTransform().getMapping()
1102 x, y = mapping.applyInverse(np.array(df[['ra', 'decl']].values*2*np.pi/360).T)
1103 inBBox = lsst.geom.Box2D(exposureBBox).contains(x, y)
1104 refCat = self.df2SourceCat(df[inBBox])
1105 return refCat
1106
1107 def df2SourceCat(self, df):
1108 """Create minimal schema SourceCatalog from a pandas DataFrame.
1109
1110 The forced measurement subtask expects this as input.
1111
1112 Parameters
1113 ----------
1114 df : `pandas.DataFrame`
1115 DiaObjects with locations and ids.
1116
1117 Returns
1118 -------
1119 outputCatalog : `lsst.afw.table.SourceTable`
1120 Output catalog with minimal schema.
1121 """
1123 outputCatalog = lsst.afw.table.SourceCatalog(schema)
1124 outputCatalog.reserve(len(df))
1125
1126 for diaObjectId, ra, decl in df[['ra', 'decl']].itertuples():
1127 outputRecord = outputCatalog.addNew()
1128 outputRecord.setId(diaObjectId)
1129 outputRecord.setCoord(lsst.geom.SpherePoint(ra, decl, lsst.geom.degrees))
1130 return outputCatalog
table::Key< int > to
Class to describe the properties of a detected object from an image.
Definition: Footprint.h:63
A 2-dimensional celestial WCS that transform pixels to ICRS RA/Dec, using the LSST standard for pixel...
Definition: SkyWcs.h:117
A class to contain the data, WCS, and other information needed to describe an image of the sky.
Definition: Exposure.h:72
Custom catalog class for ExposureRecord/Table.
Definition: Exposure.h:311
Defines the fields and offsets for a table.
Definition: Schema.h:51
Table class that contains measurements made on a single exposure.
Definition: Source.h:217
static Schema makeMinimalSchema()
Return a minimal schema for Source tables and records.
Definition: Source.h:258
static Key< RecordId > getParentKey()
Key for the parent ID.
Definition: Source.h:273
A floating-point coordinate rectangle geometry.
Definition: Box.h:413
An integer coordinate rectangle.
Definition: Box.h:55
Point in an unspecified spherical coordinate system.
Definition: SpherePoint.h:57
ConvexPolygon is a closed convex polygon on the unit sphere.
Definition: ConvexPolygon.h:57
static ConvexPolygon convexHull(std::vector< UnitVector3d > const &points)
convexHull returns the convex hull of the given set of points if it exists and throws an exception ot...
Definition: ConvexPolygon.h:65
daf::base::PropertySet * set
Definition: fits.cc:912
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
Definition: functional.cc:33
std::shared_ptr< SkyWcs > makeSkyWcs(daf::base::PropertySet &metadata, bool strip=false)
Construct a SkyWcs from FITS keywords.
Definition: SkyWcs.cc:521
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
lsst::geom::Box2I bboxFromMetadata(daf::base::PropertySet &metadata)
Determine the image bounding box from its metadata (FITS header)
Definition: Image.cc:721
def run(self, coaddExposures, bbox, wcs, dataIds, **kwargs)
Definition: getTemplate.py:596
def imageOverlapsTract(tract, imageWcs, imageBox)
def attachFootprints(self, sources, refCat, exposure, refWcs, dataRef)
def writeOutput(self, dataRef, sources)
def fetchReferences(self, dataRef, exposure)