Skip to content

Commit

Permalink
Merge pull request #244 from lsst/tickets/DM-37058
Browse files Browse the repository at this point in the history
DM-37058: Disable unnecessary measurements in CCD forced photometry
  • Loading branch information
parejkoj committed Jun 7, 2023
2 parents 6e98745 + a296f6f commit b94f486
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 25 deletions.
58 changes: 33 additions & 25 deletions python/lsst/meas/base/forcedPhotCcd.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

import warnings

import pandas as pd
import numpy as np

Expand Down Expand Up @@ -252,10 +250,22 @@ class ForcedPhotCcdConfig(pipeBase.PipelineTaskConfig,

def setDefaults(self):
# Docstring inherited.
super().setDefaults()
# Footprints here will not be entirely correct, so don't try to make
# a biased correction for blended neighbors.
self.measurement.doReplaceWithNoise = False
# Only run a minimal set of plugins, as these measurements are only
# needed for PSF-like sources.
self.measurement.plugins.names = ["base_PixelFlags",
"base_TransformedCentroid",
"base_PsfFlux",
"base_LocalBackground",
"base_LocalPhotoCalib",
"base_LocalWcs",
]
self.measurement.slots.shape = None
# Make catalogCalculation a no-op by default as no modelFlux is setup
# by default in ForcedMeasurementTask.
super().setDefaults()
self.measurement.plugins.names |= ['base_LocalPhotoCalib', 'base_LocalWcs']
self.catalogCalculation.plugins.names = []


Expand All @@ -264,30 +274,23 @@ class ForcedPhotCcdTask(pipeBase.PipelineTask):
Parameters
----------
butler : `None`
Deprecated and unused. Should always be `None`.
refSchema : `lsst.afw.table.Schema`, optional
The schema of the reference catalog, passed to the constructor of the
references subtask. Optional, but must be specified if ``initInputs``
is not; if both are specified, ``initInputs`` takes precedence.
initInputs : `dict`
Dictionary that can contain a key ``inputSchema`` containing the
schema. If present will override the value of ``refSchema``.
**kwds
**kwargs
Keyword arguments are passed to the supertask constructor.
"""

ConfigClass = ForcedPhotCcdConfig
_DefaultName = "forcedPhotCcd"
dataPrefix = ""

def __init__(self, butler=None, refSchema=None, initInputs=None, **kwds):
super().__init__(**kwds)

if butler is not None:
warnings.warn("The 'butler' parameter is no longer used and can be safely removed.",
category=FutureWarning, stacklevel=2)
butler = None
def __init__(self, refSchema=None, initInputs=None, **kwargs):
super().__init__(**kwargs)

if initInputs is not None:
refSchema = initInputs['inputSchema'].schema
Expand Down Expand Up @@ -675,13 +678,23 @@ def setDefaults(self):
super().setDefaults()
self.footprintSource = "psf"
self.measurement.doReplaceWithNoise = False
self.measurement.plugins.names = ["base_LocalPhotoCalib", "base_LocalWcs", "base_LocalBackground",
"base_TransformedCentroidFromCoord", "base_PsfFlux",
"base_PixelFlags"]
# Only run a minimal set of plugins, as these measurements are only
# needed for PSF-like sources.
self.measurement.plugins.names = ["base_PixelFlags",
"base_TransformedCentroidFromCoord",
"base_PsfFlux",
"base_LocalBackground",
"base_LocalPhotoCalib",
"base_LocalWcs",
]
self.measurement.slots.shape = None
# Make catalogCalculation a no-op by default as no modelFlux is setup
# by default in ForcedMeasurementTask.
self.catalogCalculation.plugins.names = []

self.measurement.copyColumns = {'id': 'diaObjectId', 'coord_ra': 'coord_ra', 'coord_dec': 'coord_dec'}
self.measurement.slots.centroid = "base_TransformedCentroidFromCoord"
self.measurement.slots.psfFlux = "base_PsfFlux"
self.measurement.slots.shape = None

def validate(self):
super().validate()
Expand All @@ -701,14 +714,9 @@ class ForcedPhotCcdFromDataFrameTask(ForcedPhotCcdTask):
_DefaultName = "forcedPhotCcdFromDataFrame"
ConfigClass = ForcedPhotCcdFromDataFrameConfig

def __init__(self, butler=None, refSchema=None, initInputs=None, **kwds):
def __init__(self, refSchema=None, initInputs=None, **kwargs):
# Parent's init assumes that we have a reference schema; Cannot reuse
pipeBase.PipelineTask.__init__(self, **kwds)

if butler is not None:
warnings.warn("The 'butler' parameter is no longer used and can be safely removed.",
category=FutureWarning, stacklevel=2)
butler = None
pipeBase.PipelineTask.__init__(self, **kwargs)

self.makeSubtask("measurement", refSchema=lsst.afw.table.SourceTable.makeMinimalSchema())

Expand Down
115 changes: 115 additions & 0 deletions tests/test_forcedPhot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# This file is part of meas_base.
#
# Developed for the LSST Data Management System.
# This product includes software developed by the LSST Project
# (https://www.lsst.org).
# See the COPYRIGHT file at the top-level directory of this distribution
# for details of code ownership.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

"""Tests of the various forced photometry tasks.
These tests primarily confirm that their respective Tasks can be configured and
run without errors, but do not check anything about their algorithmic quality.
"""

import unittest

import numpy as np

import lsst.afw.image
from lsst.afw.math import ChebyshevBoundedField
from lsst.meas.base import ForcedPhotCcdTask, ForcedPhotCcdFromDataFrameTask
import lsst.meas.base.tests
import lsst.utils.tests

skyCenter = lsst.geom.SpherePoint(245.0, -45.0, lsst.geom.degrees)


class ForcedPhotometryTests:
"""Base class for tests of forced photometry tasks.
Creates a simple test image and catalog to run forced photometry on.
"""
def setUp(self):
bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Point2I(100, 100))
dataset = lsst.meas.base.tests.TestDataset(bbox, crval=skyCenter)
dataset.addSource(instFlux=1000, centroid=lsst.geom.Point2D(30, 30))
dataset.addSource(instFlux=10000, centroid=lsst.geom.Point2D(60, 70))

schema = dataset.makeMinimalSchema()
self.exposure, self.refCat = dataset.realize(noise=10, schema=schema)
lsst.afw.table.updateSourceCoords(self.exposure.wcs, self.refCat)
# Simple aperture correction map in case the task needs it.
apCorrMap = lsst.afw.image.ApCorrMap()
apCorrMap["base_PsfFlux_instFlux"] = ChebyshevBoundedField(bbox, np.array([[2.0]]))
apCorrMap["base_PsfFlux_instFluxErr"] = ChebyshevBoundedField(bbox, np.array([[3.0]]))
self.exposure.info.setApCorrMap(apCorrMap)

# Offset WCS so that the forced coordinates don't match the truth.
self.offsetWcs = dataset.makePerturbedWcs(self.exposure.wcs)


class ForcedPhotCcdTaskTestCase(ForcedPhotometryTests, lsst.utils.tests.TestCase):
def testRun(self):
config = ForcedPhotCcdTask.ConfigClass()
task = ForcedPhotCcdTask(refSchema=self.refCat.schema, config=config)
measCat = task.measurement.generateMeasCat(self.exposure, self.refCat, self.exposure.wcs)

task.run(measCat, self.exposure, self.refCat, self.offsetWcs)

# Check that something was measured.
self.assertTrue(np.isfinite(measCat["base_TransformedCentroid_x"]).all())
self.assertTrue(np.isfinite(measCat["base_TransformedCentroid_y"]).all())
self.assertTrue(np.isfinite(measCat["base_PsfFlux_instFlux"]).all())
# We use an offset WCS, so the transformed centroids should not exactly
# match the original positions.
self.assertFloatsNotEqual(measCat["base_TransformedCentroid_x"], self.refCat['truth_x'])
self.assertFloatsNotEqual(measCat["base_TransformedCentroid_y"], self.refCat['truth_y'])


class ForcedPhotCcdFromDataFrameTaskTestCase(ForcedPhotometryTests, lsst.utils.tests.TestCase):
def testRun(self):
"""Testing run() for this task ignores the dataframe->SourceCatalog
conversion that happens in runQuantum, but that should be tested
separately.
"""
config = ForcedPhotCcdFromDataFrameTask.ConfigClass()
task = ForcedPhotCcdFromDataFrameTask(refSchema=self.refCat.schema, config=config)
measCat = task.measurement.generateMeasCat(self.exposure, self.refCat, self.exposure.wcs)

task.run(measCat, self.exposure, self.refCat, self.offsetWcs)

# Check that something was measured.
self.assertTrue(np.isfinite(measCat["base_TransformedCentroidFromCoord_x"]).all())
self.assertTrue(np.isfinite(measCat["base_TransformedCentroidFromCoord_y"]).all())
self.assertTrue(np.isfinite(measCat["base_PsfFlux_instFlux"]).all())
# We use an offset WCS, so the transformed centroids should not exactly
# match the original positions.
self.assertFloatsNotEqual(measCat["base_TransformedCentroidFromCoord_x"], self.refCat['truth_x'])
self.assertFloatsNotEqual(measCat["base_TransformedCentroidFromCoord_y"], self.refCat['truth_y'])


class MemoryTester(lsst.utils.tests.MemoryTestCase):
pass


def setup_module(module):
lsst.utils.tests.init()


if __name__ == "__main__":
lsst.utils.tests.init()
unittest.main()

0 comments on commit b94f486

Please sign in to comment.