LSST Applications g1653933729+a8ce1bb630,g1a997c3884+a8ce1bb630,g1b393d1bc7+82476ad7c1,g28da252d5a+3d3e1c4204,g2bbee38e9b+97aa061eef,g2bc492864f+97aa061eef,g2cdde0e794+3ad5f2bb52,g2f1216ac18+8615c5b65f,g3156d2b45e+07302053f8,g347aa1857d+97aa061eef,g35bb328faa+a8ce1bb630,g3a166c0a6a+97aa061eef,g3e281a1b8c+693a468c5f,g4005a62e65+17cd334064,g414038480c+56e3b84a79,g41af890bb2+e5200c8fd9,g65afce507f+0106b0cffc,g80478fca09+e9b577042c,g82479be7b0+a273c6d073,g858d7b2824+b43ab392d2,g9125e01d80+a8ce1bb630,ga5288a1d22+3199fccd69,gae0086650b+a8ce1bb630,gb58c049af0+d64f4d3760,gbb4f38f987+b43ab392d2,gc28159a63d+97aa061eef,gcd3f1c0c93+2e89b03209,gcf0d15dbbd+a0207f3e71,gd35896b8e2+3e8344a67c,gda3e153d99+b43ab392d2,gda6a2b7d83+a0207f3e71,gdaeeff99f8+1711a396fd,ge2409df99d+e6e587e663,ge33fd446bb+b43ab392d2,ge79ae78c31+97aa061eef,gf0baf85859+5daf287408,gf5289d68f6+c4f2338d90,gfda6b12a05+3bcad770a9,w.2024.42
LSST Data Management Base Package
Loading...
Searching...
No Matches
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 pandas as pd
23import numpy as np
24
25import lsst.pex.config
27import lsst.pipe.base
28import lsst.geom
30import lsst.afw.geom
31import lsst.afw.image
32import lsst.afw.table
33import lsst.sphgeom
34
35from lsst.pipe.base import PipelineTaskConnections
36import lsst.pipe.base.connectionTypes as cT
37
38import lsst.pipe.base as pipeBase
39from lsst.skymap import BaseSkyMap
40
41from .forcedMeasurement import ForcedMeasurementTask
42from .applyApCorr import ApplyApCorrTask
43from .catalogCalculation import CatalogCalculationTask
44from ._id_generator import DetectorVisitIdGeneratorConfig
45
46__all__ = ("ForcedPhotCcdConfig", "ForcedPhotCcdTask",
47 "ForcedPhotCcdFromDataFrameTask", "ForcedPhotCcdFromDataFrameConfig")
48
49
50class ForcedPhotCcdConnections(PipelineTaskConnections,
51 dimensions=("instrument", "visit", "detector", "skymap", "tract"),
52 defaultTemplates={"inputCoaddName": "deep",
53 "inputName": "calexp"}):
54 inputSchema = cT.InitInput(
55 doc="Schema for the input measurement catalogs.",
56 name="{inputCoaddName}Coadd_ref_schema",
57 storageClass="SourceCatalog",
58 )
59 outputSchema = cT.InitOutput(
60 doc="Schema for the output forced measurement catalogs.",
61 name="forced_src_schema",
62 storageClass="SourceCatalog",
63 )
64 exposure = cT.Input(
65 doc="Input exposure to perform photometry on.",
66 name="{inputName}",
67 storageClass="ExposureF",
68 dimensions=["instrument", "visit", "detector"],
69 )
70 refCat = cT.Input(
71 doc="Catalog of shapes and positions at which to force photometry.",
72 name="{inputCoaddName}Coadd_ref",
73 storageClass="SourceCatalog",
74 dimensions=["skymap", "tract", "patch"],
75 multiple=True,
76 deferLoad=True,
77 )
78 skyMap = cT.Input(
79 doc="SkyMap dataset that defines the coordinate system of the reference catalog.",
80 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
81 storageClass="SkyMap",
82 dimensions=["skymap"],
83 )
84 skyCorr = cT.Input(
85 doc="Input Sky Correction to be subtracted from the calexp if doApplySkyCorr=True",
86 name="skyCorr",
87 storageClass="Background",
88 dimensions=("instrument", "visit", "detector"),
89 )
90 visitSummary = cT.Input(
91 doc="Input visit-summary catalog with updated calibration objects.",
92 name="finalVisitSummary",
93 storageClass="ExposureCatalog",
94 dimensions=("instrument", "visit"),
95 )
96 measCat = cT.Output(
97 doc="Output forced photometry catalog.",
98 name="forced_src",
99 storageClass="SourceCatalog",
100 dimensions=["instrument", "visit", "detector", "skymap", "tract"],
101 )
102
103 def __init__(self, *, config=None):
104 super().__init__(config=config)
105 if not config.doApplySkyCorr:
106 self.inputs.remove("skyCorr")
107
108
109class ForcedPhotCcdConfig(pipeBase.PipelineTaskConfig,
110 pipelineConnections=ForcedPhotCcdConnections):
111 """Config class for forced measurement driver task."""
113 target=ForcedMeasurementTask,
114 doc="subtask to do forced measurement"
115 )
116 coaddName = lsst.pex.config.Field(
117 doc="coadd name: typically one of deep or goodSeeing",
118 dtype=str,
119 default="deep",
120 )
121 doApCorr = lsst.pex.config.Field(
122 dtype=bool,
123 default=True,
124 doc="Run subtask to apply aperture corrections"
125 )
127 target=ApplyApCorrTask,
128 doc="Subtask to apply aperture corrections"
129 )
130 catalogCalculation = lsst.pex.config.ConfigurableField(
131 target=CatalogCalculationTask,
132 doc="Subtask to run catalogCalculation plugins on catalog"
133 )
134 doApplySkyCorr = lsst.pex.config.Field(
135 dtype=bool,
136 default=False,
137 doc="Apply sky correction?",
138 )
139 includePhotoCalibVar = lsst.pex.config.Field(
140 dtype=bool,
141 default=False,
142 doc="Add photometric calibration variance to warp variance plane?",
143 )
144 footprintSource = lsst.pex.config.ChoiceField(
145 dtype=str,
146 doc="Where to obtain footprints to install in the measurement catalog, prior to measurement.",
147 allowed={
148 "transformed": "Transform footprints from the reference catalog (downgrades HeavyFootprints).",
149 "psf": ("Use the scaled shape of the PSF at the position of each source (does not generate "
150 "HeavyFootprints)."),
151 },
152 optional=True,
153 default="transformed",
154 )
155 psfFootprintScaling = lsst.pex.config.Field(
156 dtype=float,
157 doc="Scaling factor to apply to the PSF shape when footprintSource='psf' (ignored otherwise).",
158 default=3.0,
159 )
160 idGenerator = DetectorVisitIdGeneratorConfig.make_field()
161
162 def setDefaults(self):
163 # Docstring inherited.
164 super().setDefaults()
165 # Footprints here will not be entirely correct, so don't try to make
166 # a biased correction for blended neighbors.
167 self.measurement.doReplaceWithNoise = False
168 # Only run a minimal set of plugins, as these measurements are only
169 # needed for PSF-like sources.
170 self.measurement.plugins.names = ["base_PixelFlags",
171 "base_TransformedCentroid",
172 "base_PsfFlux",
173 "base_LocalBackground",
174 "base_LocalPhotoCalib",
175 "base_LocalWcs",
176 ]
177 self.measurement.slots.shape = None
178 # Make catalogCalculation a no-op by default as no modelFlux is setup
179 # by default in ForcedMeasurementTask.
180 self.catalogCalculation.plugins.names = []
181
182
183class ForcedPhotCcdTask(pipeBase.PipelineTask):
184 """A pipeline task for performing forced measurement on CCD images.
185
186 Parameters
187 ----------
188 refSchema : `lsst.afw.table.Schema`, optional
189 The schema of the reference catalog, passed to the constructor of the
190 references subtask. Optional, but must be specified if ``initInputs``
191 is not; if both are specified, ``initInputs`` takes precedence.
192 initInputs : `dict`
193 Dictionary that can contain a key ``inputSchema`` containing the
194 schema. If present will override the value of ``refSchema``.
195 **kwargs
196 Keyword arguments are passed to the supertask constructor.
197 """
198
199 ConfigClass = ForcedPhotCcdConfig
200 _DefaultName = "forcedPhotCcd"
201 dataPrefix = ""
202
203 def __init__(self, refSchema=None, initInputs=None, **kwargs):
204 super().__init__(**kwargs)
205
206 if initInputs is not None:
207 refSchema = initInputs['inputSchema'].schema
208
209 if refSchema is None:
210 raise ValueError("No reference schema provided.")
211
212 self.makeSubtask("measurement", refSchema=refSchema)
213 # It is necessary to get the schema internal to the forced measurement
214 # task until such a time that the schema is not owned by the
215 # measurement task, but is passed in by an external caller.
216 if self.config.doApCorr:
217 self.makeSubtask("applyApCorr", schema=self.measurement.schema)
218 self.makeSubtask('catalogCalculation', schema=self.measurement.schema)
219 self.outputSchema = lsst.afw.table.SourceCatalog(self.measurement.schema)
220
221 def runQuantum(self, butlerQC, inputRefs, outputRefs):
222 inputs = butlerQC.get(inputRefs)
223
224 tract = butlerQC.quantum.dataId['tract']
225 skyMap = inputs.pop('skyMap')
226 inputs['refWcs'] = skyMap[tract].getWcs()
227
228 # Connections only exist if they are configured to be used.
229 skyCorr = inputs.pop('skyCorr', None)
230
231 inputs['exposure'] = self.prepareCalibratedExposure(
232 inputs['exposure'],
233 skyCorr=skyCorr,
234 visitSummary=inputs.pop("visitSummary"),
235 )
236
237 inputs['refCat'] = self.mergeAndFilterReferences(inputs['exposure'], inputs['refCat'],
238 inputs['refWcs'])
239
240 if inputs['refCat'] is None:
241 self.log.info("No WCS for exposure %s. No %s catalog will be written.",
242 butlerQC.quantum.dataId, outputRefs.measCat.datasetType.name)
243 else:
244 inputs['measCat'], inputs['exposureId'] = self.generateMeasCat(inputRefs.exposure.dataId,
245 inputs['exposure'],
246 inputs['refCat'], inputs['refWcs'])
247 self.attachFootprints(inputs['measCat'], inputs['refCat'], inputs['exposure'], inputs['refWcs'])
248 outputs = self.run(**inputs)
249 butlerQC.put(outputs, outputRefs)
250
251 def prepareCalibratedExposure(self, exposure, skyCorr=None, visitSummary=None):
252 """Prepare a calibrated exposure and apply external calibrations
253 and sky corrections if so configured.
254
255 Parameters
256 ----------
257 exposure : `lsst.afw.image.exposure.Exposure`
258 Input exposure to adjust calibrations.
259 skyCorr : `lsst.afw.math.backgroundList`, optional
260 Sky correction frame to apply if doApplySkyCorr=True.
261 visitSummary : `lsst.afw.table.ExposureCatalog`, optional
262 Exposure catalog with update calibrations; any not-None calibration
263 objects attached will be used. These are applied first and may be
264 overridden by other arguments.
265
266 Returns
267 -------
268 exposure : `lsst.afw.image.exposure.Exposure`
269 Exposure with adjusted calibrations.
270 """
271 detectorId = exposure.getInfo().getDetector().getId()
272
273 if visitSummary is not None:
274 row = visitSummary.find(detectorId)
275 if row is None:
276 raise RuntimeError(f"Detector id {detectorId} not found in visitSummary.")
277 if (photoCalib := row.getPhotoCalib()) is not None:
278 exposure.setPhotoCalib(photoCalib)
279 if (skyWcs := row.getWcs()) is not None:
280 exposure.setWcs(skyWcs)
281 if (psf := row.getPsf()) is not None:
282 exposure.setPsf(psf)
283 if (apCorrMap := row.getApCorrMap()) is not None:
284 exposure.info.setApCorrMap(apCorrMap)
285
286 if skyCorr is not None:
287 exposure.maskedImage -= skyCorr.getImage()
288
289 return exposure
290
291 def mergeAndFilterReferences(self, exposure, refCats, refWcs):
292 """Filter reference catalog so that all sources are within the
293 boundaries of the exposure.
294
295 Parameters
296 ----------
297 exposure : `lsst.afw.image.exposure.Exposure`
298 Exposure to generate the catalog for.
299 refCats : sequence of `lsst.daf.butler.DeferredDatasetHandle`
300 Handles for catalogs of shapes and positions at which to force
301 photometry.
302 refWcs : `lsst.afw.image.SkyWcs`
303 Reference world coordinate system.
304
305 Returns
306 -------
307 refSources : `lsst.afw.table.SourceCatalog`
308 Filtered catalog of forced sources to measure.
309
310 Notes
311 -----
312 The majority of this code is based on the methods of
313 lsst.meas.algorithms.loadReferenceObjects.ReferenceObjectLoader
314
315 """
316 mergedRefCat = None
317
318 # Step 1: Determine bounds of the exposure photometry will
319 # be performed on.
320 expWcs = exposure.getWcs()
321 if expWcs is None:
322 self.log.info("Exposure has no WCS. Returning None for mergedRefCat.")
323 else:
324 expRegion = exposure.getBBox(lsst.afw.image.PARENT)
325 expBBox = lsst.geom.Box2D(expRegion)
326 expBoxCorners = expBBox.getCorners()
327 expSkyCorners = [expWcs.pixelToSky(corner).getVector() for
328 corner in expBoxCorners]
329 expPolygon = lsst.sphgeom.ConvexPolygon(expSkyCorners)
330
331 # Step 2: Filter out reference catalog sources that are
332 # not contained within the exposure boundaries, or whose
333 # parents are not within the exposure boundaries. Note
334 # that within a single input refCat, the parents always
335 # appear before the children.
336 for refCat in refCats:
337 refCat = refCat.get()
338 if mergedRefCat is None:
339 mergedRefCat = lsst.afw.table.SourceCatalog(refCat.table)
340 containedIds = {0} # zero as a parent ID means "this is a parent"
341 for record in refCat:
342 if (expPolygon.contains(record.getCoord().getVector()) and record.getParent()
343 in containedIds):
344 record.setFootprint(record.getFootprint())
345 mergedRefCat.append(record)
346 containedIds.add(record.getId())
347 if mergedRefCat is None:
348 raise RuntimeError("No reference objects for forced photometry.")
349 mergedRefCat.sort(lsst.afw.table.SourceTable.getParentKey())
350 return mergedRefCat
351
352 def generateMeasCat(self, dataId, exposure, refCat, refWcs):
353 """Generate a measurement catalog.
354
355 Parameters
356 ----------
357 dataId : `lsst.daf.butler.DataCoordinate`
358 Butler data ID for this image, with ``{visit, detector}`` keys.
359 exposure : `lsst.afw.image.exposure.Exposure`
360 Exposure to generate the catalog for.
361 refCat : `lsst.afw.table.SourceCatalog`
362 Catalog of shapes and positions at which to force photometry.
363 refWcs : `lsst.afw.image.SkyWcs`
364 Reference world coordinate system.
365 This parameter is not currently used.
366
367 Returns
368 -------
369 measCat : `lsst.afw.table.SourceCatalog`
370 Catalog of forced sources to measure.
371 expId : `int`
372 Unique binary id associated with the input exposure
373 """
374 id_generator = self.config.idGenerator.apply(dataId)
375 measCat = self.measurement.generateMeasCat(exposure, refCat, refWcs,
376 idFactory=id_generator.make_table_id_factory())
377 return measCat, id_generator.catalog_id
378
379 def run(self, measCat, exposure, refCat, refWcs, exposureId=None):
380 """Perform forced measurement on a single exposure.
381
382 Parameters
383 ----------
384 measCat : `lsst.afw.table.SourceCatalog`
385 The measurement catalog, based on the sources listed in the
386 reference catalog.
387 exposure : `lsst.afw.image.Exposure`
388 The measurement image upon which to perform forced detection.
389 refCat : `lsst.afw.table.SourceCatalog`
390 The reference catalog of sources to measure.
391 refWcs : `lsst.afw.image.SkyWcs`
392 The WCS for the references.
393 exposureId : `int`
394 Optional unique exposureId used for random seed in measurement
395 task.
396
397 Returns
398 -------
399 result : `lsst.pipe.base.Struct`
400 Structure with fields:
401
402 ``measCat``
403 Catalog of forced measurement results
404 (`lsst.afw.table.SourceCatalog`).
405 """
406 self.measurement.run(measCat, exposure, refCat, refWcs, exposureId=exposureId)
407 if self.config.doApCorr:
408 apCorrMap = exposure.getInfo().getApCorrMap()
409 if apCorrMap is None:
410 self.log.warning("Forced exposure image does not have valid aperture correction; skipping.")
411 else:
412 self.applyApCorr.run(
413 catalog=measCat,
414 apCorrMap=apCorrMap,
415 )
416 self.catalogCalculation.run(measCat)
417
418 return pipeBase.Struct(measCat=measCat)
419
420 def attachFootprints(self, sources, refCat, exposure, refWcs):
421 """Attach footprints to blank sources prior to measurements.
422
423 Notes
424 -----
425 `~lsst.afw.detection.Footprint` objects for forced photometry must
426 be in the pixel coordinate system of the image being measured, while
427 the actual detections may start out in a different coordinate system.
428
429 Subclasses of this class may implement this method to define how
430 those `~lsst.afw.detection.Footprint` objects should be generated.
431
432 This default implementation transforms depends on the
433 ``footprintSource`` configuration parameter.
434 """
435 if self.config.footprintSource == "transformed":
436 return self.measurement.attachTransformedFootprints(sources, refCat, exposure, refWcs)
437 elif self.config.footprintSource == "psf":
438 return self.measurement.attachPsfShapeFootprints(sources, exposure,
439 scaling=self.config.psfFootprintScaling)
440
441
442class ForcedPhotCcdFromDataFrameConnections(PipelineTaskConnections,
443 dimensions=("instrument", "visit", "detector", "skymap", "tract"),
444 defaultTemplates={"inputCoaddName": "goodSeeing",
445 "inputName": "calexp",
446 }):
447 refCat = cT.Input(
448 doc="Catalog of positions at which to force photometry.",
449 name="{inputCoaddName}Diff_fullDiaObjTable",
450 storageClass="DataFrame",
451 dimensions=["skymap", "tract", "patch"],
452 multiple=True,
453 deferLoad=True,
454 )
455 exposure = cT.Input(
456 doc="Input exposure to perform photometry on.",
457 name="{inputName}",
458 storageClass="ExposureF",
459 dimensions=["instrument", "visit", "detector"],
460 )
461 skyCorr = cT.Input(
462 doc="Input Sky Correction to be subtracted from the calexp if doApplySkyCorr=True",
463 name="skyCorr",
464 storageClass="Background",
465 dimensions=("instrument", "visit", "detector"),
466 )
467 visitSummary = cT.Input(
468 doc="Input visit-summary catalog with updated calibration objects.",
469 name="finalVisitSummary",
470 storageClass="ExposureCatalog",
471 dimensions=("instrument", "visit"),
472 )
473 skyMap = cT.Input(
474 doc="SkyMap dataset that defines the coordinate system of the reference catalog.",
475 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
476 storageClass="SkyMap",
477 dimensions=["skymap"],
478 )
479 measCat = cT.Output(
480 doc="Output forced photometry catalog.",
481 name="forced_src_diaObject",
482 storageClass="SourceCatalog",
483 dimensions=["instrument", "visit", "detector", "skymap", "tract"],
484 )
485 outputSchema = cT.InitOutput(
486 doc="Schema for the output forced measurement catalogs.",
487 name="forced_src_diaObject_schema",
488 storageClass="SourceCatalog",
489 )
490
491 def __init__(self, *, config=None):
492 super().__init__(config=config)
493 if not config.doApplySkyCorr:
494 self.inputs.remove("skyCorr")
495
496
497class ForcedPhotCcdFromDataFrameConfig(ForcedPhotCcdConfig,
498 pipelineConnections=ForcedPhotCcdFromDataFrameConnections):
499 def setDefaults(self):
500 super().setDefaults()
501 self.footprintSource = "psf"
502 self.measurement.doReplaceWithNoise = False
503 # Only run a minimal set of plugins, as these measurements are only
504 # needed for PSF-like sources.
505 self.measurement.plugins.names = ["base_PixelFlags",
506 "base_TransformedCentroidFromCoord",
507 "base_PsfFlux",
508 "base_LocalBackground",
509 "base_LocalPhotoCalib",
510 "base_LocalWcs",
511 ]
512 self.measurement.slots.shape = None
513 # Make catalogCalculation a no-op by default as no modelFlux is setup
514 # by default in ForcedMeasurementTask.
515 self.catalogCalculation.plugins.names = []
516
517 self.measurement.copyColumns = {'id': 'diaObjectId', 'coord_ra': 'coord_ra', 'coord_dec': 'coord_dec'}
518 self.measurement.slots.centroid = "base_TransformedCentroidFromCoord"
519 self.measurement.slots.psfFlux = "base_PsfFlux"
520
521 def validate(self):
522 super().validate()
523 if self.footprintSource == "transformed":
524 raise ValueError("Cannot transform footprints from reference catalog, "
525 "because DataFrames can't hold footprints.")
526
527
528class ForcedPhotCcdFromDataFrameTask(ForcedPhotCcdTask):
529 """Force Photometry on a per-detector exposure with coords from a DataFrame
530
531 Uses input from a DataFrame instead of SourceCatalog
532 like the base class ForcedPhotCcd does.
533 Writes out a SourceCatalog so that the downstream
534 WriteForcedSourceTableTask can be reused with output from this Task.
535 """
536 _DefaultName = "forcedPhotCcdFromDataFrame"
537 ConfigClass = ForcedPhotCcdFromDataFrameConfig
538
539 def __init__(self, refSchema=None, initInputs=None, **kwargs):
540 # Parent's init assumes that we have a reference schema; Cannot reuse
541 pipeBase.PipelineTask.__init__(self, **kwargs)
542
543 self.makeSubtask("measurement", refSchema=lsst.afw.table.SourceTable.makeMinimalSchema())
544
545 if self.config.doApCorr:
546 self.makeSubtask("applyApCorr", schema=self.measurement.schema)
547 self.makeSubtask('catalogCalculation', schema=self.measurement.schema)
548 self.outputSchema = lsst.afw.table.SourceCatalog(self.measurement.schema)
549
550 def runQuantum(self, butlerQC, inputRefs, outputRefs):
551 inputs = butlerQC.get(inputRefs)
552
553 tract = butlerQC.quantum.dataId["tract"]
554 skyMap = inputs.pop("skyMap")
555 inputs["refWcs"] = skyMap[tract].getWcs()
556
557 # Connections only exist if they are configured to be used.
558 skyCorr = inputs.pop('skyCorr', None)
559
560 inputs['exposure'] = self.prepareCalibratedExposure(
561 inputs['exposure'],
562 skyCorr=skyCorr,
563 visitSummary=inputs.pop("visitSummary"),
564 )
565
566 self.log.info("Filtering ref cats: %s", ','.join([str(i.dataId) for i in inputs['refCat']]))
567 if inputs["exposure"].getWcs() is not None:
568 refCat = self.df2RefCat([i.get(parameters={"columns": ['diaObjectId', 'ra', 'dec']})
569 for i in inputs['refCat']],
570 inputs['exposure'].getBBox(), inputs['exposure'].getWcs())
571 inputs['refCat'] = refCat
572 # generateMeasCat does not use the refWcs.
573 inputs['measCat'], inputs['exposureId'] = self.generateMeasCat(
574 inputRefs.exposure.dataId, inputs['exposure'], inputs['refCat'], inputs['refWcs']
575 )
576 # attachFootprints only uses refWcs in ``transformed`` mode, which is not
577 # supported in the DataFrame-backed task.
578 self.attachFootprints(inputs["measCat"], inputs["refCat"], inputs["exposure"], inputs["refWcs"])
579 outputs = self.run(**inputs)
580
581 butlerQC.put(outputs, outputRefs)
582 else:
583 self.log.info("No WCS for %s. Skipping and no %s catalog will be written.",
584 butlerQC.quantum.dataId, outputRefs.measCat.datasetType.name)
585
586 def df2RefCat(self, dfList, exposureBBox, exposureWcs):
587 """Convert list of DataFrames to reference catalog
588
589 Concatenate list of DataFrames presumably from multiple patches and
590 downselect rows that overlap the exposureBBox using the exposureWcs.
591
592 Parameters
593 ----------
594 dfList : `list` of `pandas.DataFrame`
595 Each element containst diaObjects with ra/dec position in degrees
596 Columns 'diaObjectId', 'ra', 'dec' are expected
597 exposureBBox : `lsst.geom.Box2I`
598 Bounding box on which to select rows that overlap
599 exposureWcs : `lsst.afw.geom.SkyWcs`
600 World coordinate system to convert sky coords in ref cat to
601 pixel coords with which to compare with exposureBBox
602
603 Returns
604 -------
605 refCat : `lsst.afw.table.SourceTable`
606 Source Catalog with minimal schema that overlaps exposureBBox
607 """
608 df = pd.concat(dfList)
609 # translate ra/dec coords in dataframe to detector pixel coords
610 # to down select rows that overlap the detector bbox
611 mapping = exposureWcs.getTransform().getMapping()
612 x, y = mapping.applyInverse(np.array(df[['ra', 'dec']].values*2*np.pi/360).T)
613 inBBox = lsst.geom.Box2D(exposureBBox).contains(x, y)
614 refCat = self.df2SourceCat(df[inBBox])
615 return refCat
616
617 def df2SourceCat(self, df):
618 """Create minimal schema SourceCatalog from a pandas DataFrame.
619
620 The forced measurement subtask expects this as input.
621
622 Parameters
623 ----------
624 df : `pandas.DataFrame`
625 DiaObjects with locations and ids.
626
627 Returns
628 -------
629 outputCatalog : `lsst.afw.table.SourceTable`
630 Output catalog with minimal schema.
631 """
633 outputCatalog = lsst.afw.table.SourceCatalog(schema)
634 outputCatalog.reserve(len(df))
635
636 for diaObjectId, ra, dec in df[['ra', 'dec']].itertuples():
637 outputRecord = outputCatalog.addNew()
638 outputRecord.setId(diaObjectId)
639 outputRecord.setCoord(lsst.geom.SpherePoint(ra, dec, lsst.geom.degrees))
640 return outputCatalog
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
Point in an unspecified spherical coordinate system.
Definition SpherePoint.h:57
ConvexPolygon is a closed convex polygon on the unit sphere.
df2RefCat(self, dfList, exposureBBox, exposureWcs)
runQuantum(self, butlerQC, inputRefs, outputRefs)