LSST Applications g04e9c324dd+8c5ae1fdc5,g134cb467dc+b203dec576,g18429d2f64+358861cd2c,g199a45376c+0ba108daf9,g1fd858c14a+dd066899e3,g262e1987ae+ebfced1d55,g29ae962dfc+72fd90588e,g2cef7863aa+aef1011c0b,g35bb328faa+8c5ae1fdc5,g3fd5ace14f+b668f15bc5,g4595892280+3897dae354,g47891489e3+abcf9c3559,g4d44eb3520+fb4ddce128,g53246c7159+8c5ae1fdc5,g67b6fd64d1+abcf9c3559,g67fd3c3899+1f72b5a9f7,g74acd417e5+cb6b47f07b,g786e29fd12+668abc6043,g87389fa792+8856018cbb,g89139ef638+abcf9c3559,g8d7436a09f+bcf525d20c,g8ea07a8fe4+9f5ccc88ac,g90f42f885a+6054cc57f1,g97be763408+06f794da49,g9dd6db0277+1f72b5a9f7,ga681d05dcb+7e36ad54cd,gabf8522325+735880ea63,gac2eed3f23+abcf9c3559,gb89ab40317+abcf9c3559,gbf99507273+8c5ae1fdc5,gd8ff7fe66e+1f72b5a9f7,gdab6d2f7ff+cb6b47f07b,gdc713202bf+1f72b5a9f7,gdfd2d52018+8225f2b331,ge365c994fd+375fc21c71,ge410e46f29+abcf9c3559,geaed405ab2+562b3308c0,gf9a733ac38+8c5ae1fdc5,w.2025.35
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 dataclasses
23from deprecated.sphinx import deprecated
24
25import astropy.table
26import pandas as pd
27import numpy as np
28
29import lsst.pex.config
31import lsst.pipe.base
32import lsst.geom
34import lsst.afw.geom
35import lsst.afw.image
36import lsst.afw.table
37import lsst.sphgeom
38
39from lsst.pipe.base import PipelineTaskConnections, NoWorkFound
40import lsst.pipe.base.connectionTypes as cT
41
42import lsst.pipe.base as pipeBase
43from lsst.skymap import BaseSkyMap
44
45from .forcedMeasurement import ForcedMeasurementTask
46from .applyApCorr import ApplyApCorrTask
47from .catalogCalculation import CatalogCalculationTask
48from ._id_generator import DetectorVisitIdGeneratorConfig
49
50__all__ = ("ForcedPhotCcdConfig", "ForcedPhotCcdTask",
51 "ForcedPhotCcdFromDataFrameTask", "ForcedPhotCcdFromDataFrameConfig")
52
53
54@deprecated(reason="This task is replaced by lsst.pipe.tasks.ForcedPhotCcdTask. "
55 "This task will be removed after v30.",
56 version="v29.0", category=FutureWarning)
57class ForcedPhotCcdConnections(PipelineTaskConnections,
58 dimensions=("instrument", "visit", "detector", "skymap", "tract"),
59 defaultTemplates={"inputCoaddName": "deep",
60 "inputName": "calexp"}):
61 inputSchema = cT.InitInput(
62 doc="Schema for the input measurement catalogs.",
63 name="{inputCoaddName}Coadd_ref_schema",
64 storageClass="SourceCatalog",
65 )
66 outputSchema = cT.InitOutput(
67 doc="Schema for the output forced measurement catalogs.",
68 name="forced_src_schema",
69 storageClass="SourceCatalog",
70 )
71 exposure = cT.Input(
72 doc="Input exposure to perform photometry on.",
73 name="{inputName}",
74 storageClass="ExposureF",
75 dimensions=["instrument", "visit", "detector"],
76 )
77 refCat = cT.Input(
78 doc="Catalog of shapes and positions at which to force photometry.",
79 name="{inputCoaddName}Coadd_ref",
80 storageClass="SourceCatalog",
81 dimensions=["skymap", "tract", "patch"],
82 multiple=True,
83 deferLoad=True,
84 )
85 skyMap = cT.Input(
86 doc="SkyMap dataset that defines the coordinate system of the reference catalog.",
87 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
88 storageClass="SkyMap",
89 dimensions=["skymap"],
90 )
91 skyCorr = cT.Input(
92 doc="Input Sky Correction to be subtracted from the calexp if doApplySkyCorr=True",
93 name="skyCorr",
94 storageClass="Background",
95 dimensions=("instrument", "visit", "detector"),
96 )
97 visitSummary = cT.Input(
98 doc="Input visit-summary catalog with updated calibration objects.",
99 name="finalVisitSummary",
100 storageClass="ExposureCatalog",
101 dimensions=("instrument", "visit"),
102 )
103 measCat = cT.Output(
104 doc="Output forced photometry catalog.",
105 name="forced_src",
106 storageClass="SourceCatalog",
107 dimensions=["instrument", "visit", "detector", "skymap", "tract"],
108 )
109
110 def __init__(self, *, config=None):
111 super().__init__(config=config)
112 if not config.doApplySkyCorr:
113 del self.skyCorr
114 if not config.useVisitSummary:
115 del self.visitSummary
116 if config.refCatStorageClass != "SourceCatalog":
117 del self.inputSchema
118 # Connections are immutable, so we have to replace them entirely
119 # rather than edit them in-place.
120 self.refCat = dataclasses.replace(self.refCat, storageClass=config.refCatStorageClass)
121
122
123@deprecated(reason="This task is replaced by lsst.pipe.tasks.ForcedPhotCcdTask. "
124 "This task will be removed after v30.",
125 version="v29.0", category=FutureWarning)
126class ForcedPhotCcdConfig(pipeBase.PipelineTaskConfig,
127 pipelineConnections=ForcedPhotCcdConnections):
128 """Config class for forced measurement driver task."""
130 target=ForcedMeasurementTask,
131 doc="subtask to do forced measurement"
132 )
133 coaddName = lsst.pex.config.Field(
134 doc="coadd name: typically one of deep or goodSeeing",
135 dtype=str,
136 default="deep",
137 )
138 doApCorr = lsst.pex.config.Field(
139 dtype=bool,
140 default=True,
141 doc="Run subtask to apply aperture corrections"
142 )
144 target=ApplyApCorrTask,
145 doc="Subtask to apply aperture corrections"
146 )
147 catalogCalculation = lsst.pex.config.ConfigurableField(
148 target=CatalogCalculationTask,
149 doc="Subtask to run catalogCalculation plugins on catalog"
150 )
151 doApplySkyCorr = lsst.pex.config.Field(
152 dtype=bool,
153 default=False,
154 doc="Apply sky correction?",
155 )
156 useVisitSummary = lsst.pex.config.Field(
157 dtype=bool,
158 default=True,
159 doc=(
160 "Use updated WCS, PhotoCalib, ApCorr, and PSF from visit summary? "
161 "This should be False if and only if the input image already has the best-available calibration "
162 "objects attached."
163 ),
164 )
165 refCatStorageClass = lsst.pex.config.ChoiceField(
166 dtype=str,
167 allowed={
168 "SourceCatalog": "Read an lsst.afw.table.SourceCatalog.",
169 "DataFrame": "Read a pandas.DataFrame.",
170 "ArrowAstropy": "Read an astropy.table.Table saved to Parquet.",
171 },
172 default="SourceCatalog",
173 doc=(
174 "The butler storage class for the refCat connection. "
175 "If set to something other than 'SourceCatalog', the "
176 "'inputSchema' connection will be ignored."
177 )
178 )
179 refCatIdColumn = lsst.pex.config.Field(
180 dtype=str,
181 default="diaObjectId",
182 doc=(
183 "Name of the column that provides the object ID from the refCat connection. "
184 "measurement.copyColumns['id'] must be set to this value as well."
185 "Ignored if refCatStorageClass='SourceCatalog'."
186 )
187 )
188 refCatRaColumn = lsst.pex.config.Field(
189 dtype=str,
190 default="ra",
191 doc=(
192 "Name of the column that provides the right ascension (in floating-point degrees) from the "
193 "refCat connection. "
194 "Ignored if refCatStorageClass='SourceCatalog'."
195 )
196 )
197 refCatDecColumn = lsst.pex.config.Field(
198 dtype=str,
199 default="dec",
200 doc=(
201 "Name of the column that provides the declination (in floating-point degrees) from the "
202 "refCat connection. "
203 "Ignored if refCatStorageClass='SourceCatalog'."
204 )
205 )
206 # TODO[DM-49400]: remove this config option; it already does nothing.
207 includePhotoCalibVar = lsst.pex.config.Field(
208 dtype=bool,
209 default=False,
210 doc="Add photometric calibration variance to warp variance plane?",
211 deprecated="Deprecated and unused; will be removed after v29.",
212 )
213 footprintSource = lsst.pex.config.ChoiceField(
214 dtype=str,
215 doc="Where to obtain footprints to install in the measurement catalog, prior to measurement.",
216 allowed={
217 "transformed": "Transform footprints from the reference catalog (downgrades HeavyFootprints).",
218 "psf": ("Use the scaled shape of the PSF at the position of each source (does not generate "
219 "HeavyFootprints)."),
220 },
221 optional=True,
222 default="transformed",
223 )
224 psfFootprintScaling = lsst.pex.config.Field(
225 dtype=float,
226 doc="Scaling factor to apply to the PSF shape when footprintSource='psf' (ignored otherwise).",
227 default=3.0,
228 )
229 idGenerator = DetectorVisitIdGeneratorConfig.make_field()
230
231 def setDefaults(self):
232 # Docstring inherited.
233 super().setDefaults()
234 # Footprints here will not be entirely correct, so don't try to make
235 # a biased correction for blended neighbors.
236 self.measurement.doReplaceWithNoise = False
237 # Only run a minimal set of plugins, as these measurements are only
238 # needed for PSF-like sources.
239 self.measurement.plugins.names = ["base_PixelFlags",
240 "base_TransformedCentroid",
241 "base_PsfFlux",
242 "base_LocalBackground",
243 "base_LocalPhotoCalib",
244 "base_LocalWcs",
245 ]
246 self.measurement.slots.psfFlux = "base_PsfFlux"
247 self.measurement.slots.shape = None
248 # Make catalogCalculation a no-op by default as no modelFlux is setup
249 # by default in ForcedMeasurementTask.
250 self.catalogCalculation.plugins.names = []
251
252 def validate(self):
253 super().validate()
254 if self.refCatStorageClass != "SourceCatalog":
255 if self.footprintSource == "transformed":
256 raise ValueError("Cannot transform footprints from reference catalog, because "
257 f"{self.config.refCatStorageClass} datasets can't hold footprints.")
258 if self.measurement.copyColumns["id"] != self.refCatIdColumn:
259 raise ValueError(
260 f"measurement.copyColumns['id'] should be set to {self.refCatIdColumn} "
261 f"(refCatIdColumn) when refCatStorageClass={self.refCatStorageClass}."
262 )
263
264 def configureParquetRefCat(self, refCatStorageClass: str = "ArrowAstropy"):
265 """Set the refCatStorageClass option to a Parquet-based type, and
266 reconfigure the measurement subtask and footprintSources accordingly.
267 """
268 self.refCatStorageClass = refCatStorageClass
269 self.footprintSource = "psf"
270 self.measurement.doReplaceWithNoise = False
271 self.measurement.plugins.names -= {"base_TransformedCentroid"}
272 self.measurement.plugins.names |= {"base_TransformedCentroidFromCoord"}
273 self.measurement.copyColumns["id"] = self.refCatIdColumn
274 self.measurement.copyColumns.pop("deblend_nChild", None)
275 self.measurement.slots.centroid = "base_TransformedCentroidFromCoord"
276
277
278@deprecated(reason="This task is replaced by lsst.pipe.tasks.ForcedPhotCcdTask. "
279 "This task will be removed after v30.",
280 version="v29.0", category=FutureWarning)
281class ForcedPhotCcdTask(pipeBase.PipelineTask):
282 """A pipeline task for performing forced measurement on CCD images.
283
284 Parameters
285 ----------
286 refSchema : `lsst.afw.table.Schema`, optional
287 The schema of the reference catalog, passed to the constructor of the
288 references subtask. Optional, but must be specified if ``initInputs``
289 is not; if both are specified, ``initInputs`` takes precedence.
290 initInputs : `dict`
291 Dictionary that can contain a key ``inputSchema`` containing the
292 schema. If present will override the value of ``refSchema``.
293 **kwargs
294 Keyword arguments are passed to the supertask constructor.
295 """
296
297 ConfigClass = ForcedPhotCcdConfig
298 _DefaultName = "forcedPhotCcd"
299 dataPrefix = ""
300
301 def __init__(self, refSchema=None, initInputs=None, **kwargs):
302 super().__init__(**kwargs)
303
304 if initInputs:
305 refSchema = initInputs['inputSchema'].schema
306
307 if refSchema is None:
309
310 self.makeSubtask("measurement", refSchema=refSchema)
311 # It is necessary to get the schema internal to the forced measurement
312 # task until such a time that the schema is not owned by the
313 # measurement task, but is passed in by an external caller.
314 if self.config.doApCorr:
315 self.makeSubtask("applyApCorr", schema=self.measurement.schema)
316 self.makeSubtask('catalogCalculation', schema=self.measurement.schema)
317 self.outputSchema = lsst.afw.table.SourceCatalog(self.measurement.schema)
318
319 def runQuantum(self, butlerQC, inputRefs, outputRefs):
320 inputs = butlerQC.get(inputRefs)
321
322 tract = butlerQC.quantum.dataId['tract']
323 skyMap = inputs.pop('skyMap')
324 inputs['refWcs'] = skyMap[tract].getWcs()
325
326 # Connections only exist if they are configured to be used.
327 skyCorr = inputs.pop('skyCorr', None)
328
329 inputs['exposure'] = self.prepareCalibratedExposure(
330 inputs['exposure'],
331 skyCorr=skyCorr,
332 visitSummary=inputs.pop("visitSummary", None),
333 )
334
335 if inputs["exposure"].getWcs() is None:
336 raise NoWorkFound("Exposure has no WCS.")
337
338 match self.config.refCatStorageClass:
339 case "SourceCatalog":
340 prepFunc = self._prepSourceCatalogRefCat
341 case "DataFrame":
342 prepFunc = self._prepDataFrameRefCat
343 case "ArrowAstropy":
344 prepFunc = self._prepArrowAstropyRefCat
345 case _:
346 raise AssertionError("Configuration should not have passed validation.")
347 self.log.info("Filtering ref cats: %s", ','.join([str(i.dataId) for i in inputs['refCat']]))
348 inputs['refCat'] = prepFunc(
349 inputs['refCat'],
350 inputs['exposure'].getBBox(),
351 inputs['exposure'].getWcs(),
352 )
353 # generateMeasCat does not actually use the refWcs; parameter is
354 # passed for signature backwards compatibility.
355 inputs['measCat'], inputs['exposureId'] = self.generateMeasCat(
356 inputRefs.exposure.dataId, inputs['exposure'], inputs['refCat'], inputs['refWcs']
357 )
358 # attachFootprints only uses refWcs in ``transformed`` mode, which is
359 # not supported unless refCatStorageClass='SourceCatalog'.
360 self.attachFootprints(inputs["measCat"], inputs["refCat"], inputs["exposure"], inputs["refWcs"])
361 outputs = self.run(**inputs)
362 butlerQC.put(outputs, outputRefs)
363
364 def prepareCalibratedExposure(self, exposure, skyCorr=None, visitSummary=None):
365 """Prepare a calibrated exposure and apply external calibrations
366 and sky corrections if so configured.
367
368 Parameters
369 ----------
370 exposure : `lsst.afw.image.exposure.Exposure`
371 Input exposure to adjust calibrations.
372 skyCorr : `lsst.afw.math.backgroundList`, optional
373 Sky correction frame to apply if doApplySkyCorr=True.
374 visitSummary : `lsst.afw.table.ExposureCatalog`, optional
375 Exposure catalog with update calibrations; any not-None calibration
376 objects attached will be used. These are applied first and may be
377 overridden by other arguments.
378
379 Returns
380 -------
381 exposure : `lsst.afw.image.exposure.Exposure`
382 Exposure with adjusted calibrations.
383 """
384 detectorId = exposure.getInfo().getDetector().getId()
385
386 if visitSummary is not None:
387 row = visitSummary.find(detectorId)
388 if row is None:
389 raise RuntimeError(f"Detector id {detectorId} not found in visitSummary.")
390 if (photoCalib := row.getPhotoCalib()) is not None:
391 exposure.setPhotoCalib(photoCalib)
392 if (skyWcs := row.getWcs()) is not None:
393 exposure.setWcs(skyWcs)
394 if (psf := row.getPsf()) is not None:
395 exposure.setPsf(psf)
396 if (apCorrMap := row.getApCorrMap()) is not None:
397 exposure.info.setApCorrMap(apCorrMap)
398
399 if skyCorr is not None:
400 exposure.maskedImage -= skyCorr.getImage()
401
402 return exposure
403
404 def generateMeasCat(self, dataId, exposure, refCat, refWcs):
405 """Generate a measurement catalog.
406
407 Parameters
408 ----------
409 dataId : `lsst.daf.butler.DataCoordinate`
410 Butler data ID for this image, with ``{visit, detector}`` keys.
411 exposure : `lsst.afw.image.exposure.Exposure`
412 Exposure to generate the catalog for.
413 refCat : `lsst.afw.table.SourceCatalog`
414 Catalog of shapes and positions at which to force photometry.
415 refWcs : `lsst.afw.image.SkyWcs`
416 Reference world coordinate system.
417 This parameter is not currently used.
418
419 Returns
420 -------
421 measCat : `lsst.afw.table.SourceCatalog`
422 Catalog of forced sources to measure.
423 expId : `int`
424 Unique binary id associated with the input exposure
425 """
426 id_generator = self.config.idGenerator.apply(dataId)
427 measCat = self.measurement.generateMeasCat(exposure, refCat, refWcs,
428 idFactory=id_generator.make_table_id_factory())
429 return measCat, id_generator.catalog_id
430
431 def run(self, measCat, exposure, refCat, refWcs, exposureId=None):
432 """Perform forced measurement on a single exposure.
433
434 Parameters
435 ----------
436 measCat : `lsst.afw.table.SourceCatalog`
437 The measurement catalog, based on the sources listed in the
438 reference catalog.
439 exposure : `lsst.afw.image.Exposure`
440 The measurement image upon which to perform forced detection.
441 refCat : `lsst.afw.table.SourceCatalog`
442 The reference catalog of sources to measure.
443 refWcs : `lsst.afw.image.SkyWcs`
444 The WCS for the references.
445 exposureId : `int`
446 Optional unique exposureId used for random seed in measurement
447 task.
448
449 Returns
450 -------
451 result : `lsst.pipe.base.Struct`
452 Structure with fields:
453
454 ``measCat``
455 Catalog of forced measurement results
456 (`lsst.afw.table.SourceCatalog`).
457 """
458 self.measurement.run(measCat, exposure, refCat, refWcs, exposureId=exposureId)
459 if self.config.doApCorr:
460 apCorrMap = exposure.getInfo().getApCorrMap()
461 if apCorrMap is None:
462 self.log.warning("Forced exposure image does not have valid aperture correction; skipping.")
463 else:
464 self.applyApCorr.run(
465 catalog=measCat,
466 apCorrMap=apCorrMap,
467 )
468 self.catalogCalculation.run(measCat)
469
470 return pipeBase.Struct(measCat=measCat)
471
472 def attachFootprints(self, sources, refCat, exposure, refWcs):
473 """Attach footprints to blank sources prior to measurements.
474
475 Notes
476 -----
477 `~lsst.afw.detection.Footprint` objects for forced photometry must
478 be in the pixel coordinate system of the image being measured, while
479 the actual detections may start out in a different coordinate system.
480
481 Subclasses of this class may implement this method to define how
482 those `~lsst.afw.detection.Footprint` objects should be generated.
483
484 This default implementation transforms depends on the
485 ``footprintSource`` configuration parameter.
486 """
487 if self.config.footprintSource == "transformed":
488 return self.measurement.attachTransformedFootprints(sources, refCat, exposure, refWcs)
489 elif self.config.footprintSource == "psf":
490 return self.measurement.attachPsfShapeFootprints(sources, exposure,
491 scaling=self.config.psfFootprintScaling)
492
493 def _prepSourceCatalogRefCat(self, refCatHandles, exposureBBox, exposureWcs):
494 """Prepare a merged, filtered reference catalog from SourceCatalog
495 inputs.
496
497 Parameters
498 ----------
499 refCatHandles : sequence of `lsst.daf.butler.DeferredDatasetHandle`
500 Handles for catalogs of shapes and positions at which to force
501 photometry.
502 exposureBBox : `lsst.geom.Box2I`
503 Bounding box on which to select rows that overlap
504 exposureWcs : `lsst.afw.geom.SkyWcs`
505 World coordinate system to convert sky coords in ref cat to
506 pixel coords with which to compare with exposureBBox
507
508 Returns
509 -------
510 refSources : `lsst.afw.table.SourceCatalog`
511 Filtered catalog of forced sources to measure.
512
513 Notes
514 -----
515 The majority of this code is based on the methods of
516 lsst.meas.algorithms.loadReferenceObjects.ReferenceObjectLoader
517
518 """
519 mergedRefCat = None
520
521 # Step 1: Determine bounds of the exposure photometry will
522 # be performed on.
523 expBBox = lsst.geom.Box2D(exposureBBox)
524 expBoxCorners = expBBox.getCorners()
525 expSkyCorners = [exposureWcs.pixelToSky(corner).getVector() for corner in expBoxCorners]
526 expPolygon = lsst.sphgeom.ConvexPolygon(expSkyCorners)
527
528 # Step 2: Filter out reference catalog sources that are
529 # not contained within the exposure boundaries, or whose
530 # parents are not within the exposure boundaries. Note
531 # that within a single input refCat, the parents always
532 # appear before the children.
533 for refCat in refCatHandles:
534 refCat = refCat.get()
535 if mergedRefCat is None:
536 mergedRefCat = lsst.afw.table.SourceCatalog(refCat.table)
537 # zero as a parent ID means "this is a parent"
538 containedIds = np.array([0])
539
540 coordKey = refCat.getCoordKey()
541 inside = expPolygon.contains(lon=refCat[coordKey.getRa()], lat=refCat[coordKey.getDec()])
542 parentIds = refCat[refCat.getParentKey()]
543 inside &= np.isin(parentIds, containedIds)
544
545 mergedRefCat.extend(refCat[inside])
546 containedIds = np.union1d(containedIds, refCat[refCat.getIdKey()][inside])
547
548 if mergedRefCat is None:
549 raise RuntimeError("No reference objects for forced photometry.")
550 mergedRefCat.sort(lsst.afw.table.SourceTable.getParentKey())
551 return mergedRefCat
552
553 def _prepDataFrameRefCat(self, refCatHandles, exposureBBox, exposureWcs):
554 """Prepare a merged, filtered reference catalog from DataFrame
555 inputs.
556
557 Parameters
558 ----------
559 refCatHandles : sequence of `lsst.daf.butler.DeferredDatasetHandle`
560 Handles for catalogs of shapes and positions at which to force
561 photometry.
562 exposureBBox : `lsst.geom.Box2I`
563 Bounding box on which to select rows that overlap
564 exposureWcs : `lsst.afw.geom.SkyWcs`
565 World coordinate system to convert sky coords in ref cat to
566 pixel coords with which to compare with exposureBBox
567
568 Returns
569 -------
570 refCat : `lsst.afw.table.SourceTable`
571 Source Catalog with minimal schema that overlaps exposureBBox
572 """
573 dfList = [
574 i.get(
575 parameters={
576 "columns": [
577 self.config.refCatIdColumn,
578 self.config.refCatRaColumn,
579 self.config.refCatDecColumn,
580 ]
581 }
582 )
583 for i in refCatHandles
584 ]
585 df = pd.concat(dfList)
586 # translate ra/dec coords in dataframe to detector pixel coords
587 # to down select rows that overlap the detector bbox
588 x, y = exposureWcs.skyToPixelArray(
589 df[self.config.refCatRaColumn].values,
590 df[self.config.refCatDecColumn].values,
591 degrees=True,
592 )
593 inBBox = np.atleast_1d(lsst.geom.Box2D(exposureBBox).contains(x, y))
594 refCat = self._makeMinimalSourceCatalogFromDataFrame(df[inBBox])
595 return refCat
596
597 def _prepArrowAstropyRefCat(self, refCatHandles, exposureBBox, exposureWcs):
598 """Prepare a merged, filtered reference catalog from ArrowAstropy
599 inputs.
600
601 Parameters
602 ----------
603 refCatHandles : sequence of `lsst.daf.butler.DeferredDatasetHandle`
604 Handles for catalogs of shapes and positions at which to force
605 photometry.
606 exposureBBox : `lsst.geom.Box2I`
607 Bounding box on which to select rows that overlap
608 exposureWcs : `lsst.afw.geom.SkyWcs`
609 World coordinate system to convert sky coords in ref cat to
610 pixel coords with which to compare with exposureBBox
611
612 Returns
613 -------
614 refCat : `lsst.afw.table.SourceTable`
615 Source Catalog with minimal schema that overlaps exposureBBox
616 """
617 table_list = [
618 i.get(
619 parameters={
620 "columns": [
621 self.config.refCatIdColumn,
622 self.config.refCatRaColumn,
623 self.config.refCatDecColumn,
624 ]
625 }
626 )
627 for i in refCatHandles
628 ]
629 full_table = astropy.table.vstack(table_list)
630 # translate ra/dec coords in table to detector pixel coords
631 # to down-select rows that overlap the detector bbox
632 x, y = exposureWcs.skyToPixelArray(
633 full_table[self.config.refCatRaColumn],
634 full_table[self.config.refCatDecColumn],
635 degrees=True,
636 )
637 inBBox = lsst.geom.Box2D(exposureBBox).contains(x, y)
638 refCat = self._makeMinimalSourceCatalogFromAstropy(full_table[inBBox])
639 return refCat
640
641 def _makeMinimalSourceCatalogFromDataFrame(self, df):
642 """Create minimal schema SourceCatalog from a pandas DataFrame.
643
644 The forced measurement subtask expects this as input.
645
646 Parameters
647 ----------
648 df : `pandas.DataFrame`
649 Table with locations and ids.
650
651 Returns
652 -------
653 outputCatalog : `lsst.afw.table.SourceTable`
654 Output catalog with minimal schema.
655 """
657 outputCatalog = lsst.afw.table.SourceCatalog(schema)
658 outputCatalog.reserve(len(df))
659
660 for objectId, ra, dec in df[['ra', 'dec']].itertuples():
661 outputRecord = outputCatalog.addNew()
662 outputRecord.setId(objectId)
663 outputRecord.setCoord(lsst.geom.SpherePoint(ra, dec, lsst.geom.degrees))
664 return outputCatalog
665
666 def _makeMinimalSourceCatalogFromAstropy(self, table):
667 """Create minimal schema SourceCatalog from an Astropy Table.
668
669 The forced measurement subtask expects this as input.
670
671 Parameters
672 ----------
673 table : `astropy.table.Table`
674 Table with locations and ids.
675
676 Returns
677 -------
678 outputCatalog : `lsst.afw.table.SourceTable`
679 Output catalog with minimal schema.
680 """
682 outputCatalog = lsst.afw.table.SourceCatalog(schema)
683 outputCatalog.reserve(len(table))
684
685 for objectId, ra, dec in table.iterrows():
686 outputRecord = outputCatalog.addNew()
687 outputRecord.setId(objectId)
688 outputRecord.setCoord(lsst.geom.SpherePoint(ra, dec, lsst.geom.degrees))
689 return outputCatalog
690
691
692@deprecated(reason="This task is replaced by lsst.pipe.tasks.ForcedPhotCcdTask. "
693 "This task will be removed after v30.",
694 version="v29.0", category=FutureWarning)
695class ForcedPhotCcdFromDataFrameConnections(ForcedPhotCcdConnections,
696 dimensions=("instrument", "visit", "detector", "skymap", "tract"),
697 defaultTemplates={"inputCoaddName": "goodSeeing",
698 "inputName": "calexp",
699 }):
700 pass
701
702
703@deprecated(reason="This task is replaced by lsst.pipe.tasks.ForcedPhotCcdTask. "
704 "This task will be removed after v30.",
705 version="v29.0", category=FutureWarning)
706class ForcedPhotCcdFromDataFrameConfig(ForcedPhotCcdConfig,
707 pipelineConnections=ForcedPhotCcdFromDataFrameConnections):
708 def setDefaults(self):
709 super().setDefaults()
710 self.configureParquetRefCat("DataFrame")
711 self.connections.refCat = "{inputCoaddName}Diff_fullDiaObjTable"
712 self.connections.outputSchema = "forced_src_diaObject_schema"
713 self.connections.measCat = "forced_src_diaObject"
714
715
716@deprecated(reason="This task is replaced by lsst.pipe.tasks.ForcedPhotCcdTask. "
717 "This task will be removed after v30.",
718 version="v29.0", category=FutureWarning)
719class ForcedPhotCcdFromDataFrameTask(ForcedPhotCcdTask):
720 """Force Photometry on a per-detector exposure with coords from a DataFrame
721
722 Uses input from a DataFrame instead of SourceCatalog
723 like the base class ForcedPhotCcd does.
724 Writes out a SourceCatalog so that the downstream
725 WriteForcedSourceTableTask can be reused with output from this Task.
726 """
727 _DefaultName = "forcedPhotCcdFromDataFrame"
728 ConfigClass = ForcedPhotCcdFromDataFrameConfig
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.