diff --git a/pipelines/coaddColumnValidate.yaml b/pipelines/coaddColumnValidate.yaml index d193a0c5a..a6af284bb 100644 --- a/pipelines/coaddColumnValidate.yaml +++ b/pipelines/coaddColumnValidate.yaml @@ -7,10 +7,9 @@ tasks: connections.outputName: objectTableColumnValidate atools.validPsfFluxMetric: ValidFracColumnMetric atools.validPsfFluxMetric.vectorKey: 'psfFlux' - atools.validPsfFluxMetric.applyContext: CoaddContext + atools.validPsfFluxMetric.context: 'coadd' atools.validCmodelFluxMetric: ValidFracColumnMetric atools.validCmodelFluxMetric.vectorKey: 'cModelFlux' - atools.validCmodelFluxMetric.applyContext: CoaddContext + atools.validCmodelFluxMetric.context: 'coadd' python: | from lsst.analysis.tools.atools import * - from lsst.analysis.tools.contexts import * diff --git a/pipelines/coaddDiffMatchedQualityExtended.yaml b/pipelines/coaddDiffMatchedQualityExtended.yaml index 8b682e7a6..a3c4dc924 100644 --- a/pipelines/coaddDiffMatchedQualityExtended.yaml +++ b/pipelines/coaddDiffMatchedQualityExtended.yaml @@ -8,53 +8,50 @@ tasks: # plots atools.matchedRefCModelMagDiffPlot: MatchedRefCoaddCModelFluxPlot - atools.matchedRefCModelMagDiffPlot.applyContext: MatchedRefDiffContext + atools.matchedRefCModelMagDiffPlot.context: 'diff' atools.matchedRefCModelFluxChiPlot: MatchedRefCoaddCModelFluxPlot - atools.matchedRefCModelFluxChiPlot.applyContext: MatchedRefChiContext + atools.matchedRefCModelFluxChiPlot.context: 'chi' # TODO: Can this be a one liner? atools.matchedRefPositionXDiffPlot: MatchedRefCoaddPositionPlot - # TODO: variable must be defined before applyContext; can this be enforced? - # (the resulting error if not is not very informative) atools.matchedRefPositionXDiffPlot.variable: x - atools.matchedRefPositionXDiffPlot.applyContext: MatchedRefDiffContext + atools.matchedRefPositionXDiffPlot.context: 'diff' atools.matchedRefPositionXChiPlot: MatchedRefCoaddPositionPlot atools.matchedRefPositionXChiPlot.variable: x - atools.matchedRefPositionXChiPlot.applyContext: MatchedRefChiContext + atools.matchedRefPositionXChiPlot.context: 'chi' atools.matchedRefPositionYDiffPlot: MatchedRefCoaddPositionPlot atools.matchedRefPositionYDiffPlot.variable: y - atools.matchedRefPositionYDiffPlot.applyContext: MatchedRefDiffContext + atools.matchedRefPositionYDiffPlot.context: 'diff' atools.matchedRefPositionYChiPlot: MatchedRefCoaddPositionPlot atools.matchedRefPositionYChiPlot.variable: y - atools.matchedRefPositionYChiPlot.applyContext: MatchedRefChiContext + atools.matchedRefPositionYChiPlot.context: 'chi' # metrics atools.matchedRefCModelMagDiffMetric: MatchedRefCoaddCModelFluxMetric - atools.matchedRefCModelMagDiffMetric.applyContext: MatchedRefDiffContext + atools.matchedRefCModelMagDiffMetric.context: 'diff' atools.matchedRefCModelFluxChiMetric: MatchedRefCoaddCModelFluxMetric - atools.matchedRefCModelFluxChiMetric.applyContext: MatchedRefChiContext + atools.matchedRefCModelFluxChiMetric.context: 'chi' atools.matchedRefPositionXDiffMetric: MatchedRefCoaddPositionMetric atools.matchedRefPositionXDiffMetric.variable: x - atools.matchedRefPositionXDiffMetric.applyContext: MatchedRefChiContext + atools.matchedRefPositionXDiffMetric.context: 'chi' atools.matchedRefPositionXChiMetric: MatchedRefCoaddPositionMetric atools.matchedRefPositionXChiMetric.variable: x - atools.matchedRefPositionXChiMetric.applyContext: MatchedRefChiContext + atools.matchedRefPositionXChiMetric.context: 'chi' atools.matchedRefPositionYDiffMetric: MatchedRefCoaddPositionMetric atools.matchedRefPositionYDiffMetric.variable: y - atools.matchedRefPositionYDiffMetric.applyContext: MatchedRefDiffContext + atools.matchedRefPositionYDiffMetric.context: 'diff' atools.matchedRefPositionYChiMetric: MatchedRefCoaddPositionMetric atools.matchedRefPositionYChiMetric.variable: y - atools.matchedRefPositionYChiMetric.applyContext: MatchedRefChiContext - + atools.matchedRefPositionYChiMetric.context: 'chi' python: | from lsst.analysis.tools.atools.diffMatched import ( @@ -66,8 +63,3 @@ tasks: MatchedRefCoaddCModelFluxPlot, MatchedRefCoaddPositionPlot, ) - - from lsst.analysis.tools.contexts._contexts import ( - MatchedRefDiffContext, - MatchedRefChiContext, - ) diff --git a/pipelines/coaddQualityCore.yaml b/pipelines/coaddQualityCore.yaml index e278e6d5d..5a9ce90e2 100644 --- a/pipelines/coaddQualityCore.yaml +++ b/pipelines/coaddQualityCore.yaml @@ -9,7 +9,7 @@ tasks: atools.e1Diff: E1Diff atools.e2Diff: E2Diff atools.skyFluxStatisticMetric: SkyFluxStatisticMetric - atools.skyFluxStatisticMetric.applyContext: CoaddContext + atools.skyFluxStatisticMetric.context: 'coadd' atools.wPerpPSFP: WPerpPSF atools.wPerpCModel: WPerpCModel atools.xPerpPSFP: XPerpPSF @@ -20,7 +20,6 @@ tasks: atools.skyObjectHistPlot: SkyObjectHistPlot python: | from lsst.analysis.tools.atools import * - from lsst.analysis.tools.contexts import * analyzeObjectTableSurveyCore: class: lsst.analysis.tools.tasks.ObjectTableSurveyAnalysisTask config: @@ -29,7 +28,6 @@ tasks: bands: ["i"] python: | from lsst.analysis.tools.atools import * - from lsst.analysis.tools.contexts import * from lsst.analysis.tools.actions.plot import * catalogMatchTract: class: lsst.analysis.tools.tasks.catalogMatch.CatalogMatchTask diff --git a/pipelines/visitColumnValidate.yaml b/pipelines/visitColumnValidate.yaml index 37c4d3a41..110ad2eea 100644 --- a/pipelines/visitColumnValidate.yaml +++ b/pipelines/visitColumnValidate.yaml @@ -7,40 +7,39 @@ tasks: connections.outputName: visitTableColumnValidate atools.validPsfFluxMetric: ValidFracColumnMetric atools.validPsfFluxMetric.vectorKey: 'psfFlux' - atools.validPsfFluxMetric.applyContext: VisitContext + atools.validPsfFluxMetric.context: 'visit' atools.validAp03FluxMetric: ValidFracColumnMetric atools.validAp03FluxMetric.vectorKey: 'ap03Flux' - atools.validAp03FluxMetric.applyContext: VisitContext + atools.validAp03FluxMetric.context: 'visit' atools.validAp06FluxMetric: ValidFracColumnMetric atools.validAp06FluxMetric.vectorKey: 'ap06Flux' - atools.validAp06FluxMetric.applyContext: VisitContext + atools.validAp06FluxMetric.context: 'visit' atools.validAp09FluxMetric: ValidFracColumnMetric atools.validAp09FluxMetric.vectorKey: 'ap09Flux' - atools.validAp09FluxMetric.applyContext: VisitContext + atools.validAp09FluxMetric.context: 'visit' atools.validAp12FluxMetric: ValidFracColumnMetric atools.validAp12FluxMetric.vectorKey: 'ap12Flux' - atools.validAp12FluxMetric.applyContext: VisitContext + atools.validAp12FluxMetric.context: 'visit' atools.validAp17FluxMetric: ValidFracColumnMetric atools.validAp17FluxMetric.vectorKey: 'ap17Flux' - atools.validAp17FluxMetric.applyContext: VisitContext + atools.validAp17FluxMetric.context: 'visit' atools.validAp25FluxMetric: ValidFracColumnMetric atools.validAp25FluxMetric.vectorKey: 'ap25Flux' - atools.validAp25FluxMetric.applyContext: VisitContext + atools.validAp25FluxMetric.context: 'visit' atools.validAp35FluxMetric: ValidFracColumnMetric atools.validAp35FluxMetric.vectorKey: 'ap35Flux' - atools.validAp35FluxMetric.applyContext: VisitContext + atools.validAp35FluxMetric.context: 'visit' atools.validAp50FluxMetric: ValidFracColumnMetric atools.validAp50FluxMetric.vectorKey: 'ap50Flux' - atools.validAp50FluxMetric.applyContext: VisitContext + atools.validAp50FluxMetric.context: 'visit' atools.validAp70FluxMetric: ValidFracColumnMetric atools.validAp70FluxMetric.vectorKey: 'ap70Flux' - atools.validAp70FluxMetric.applyContext: VisitContext + atools.validAp70FluxMetric.context: 'visit' atools.validCalibFluxMetric: ValidFracColumnMetric atools.validCalibFluxMetric.vectorKey: 'calibFlux' - atools.validCalibFluxMetric.applyContext: VisitContext + atools.validCalibFluxMetric.context: 'visit' atools.validGaussianFluxMetric: ValidFracColumnMetric atools.validGaussianFluxMetric.vectorKey: 'gaussianFlux' - atools.validGaussianFluxMetric.applyContext: VisitContext + atools.validGaussianFluxMetric.context: 'visit' python: | from lsst.analysis.tools.atools import * - from lsst.analysis.tools.contexts import * diff --git a/pipelines/visitQualityCore.yaml b/pipelines/visitQualityCore.yaml index fa6a1ed9a..bcb3733d9 100644 --- a/pipelines/visitQualityCore.yaml +++ b/pipelines/visitQualityCore.yaml @@ -9,9 +9,8 @@ tasks: # metrics.name = AMetricsClass connections.outputName: sourceTableCore atools.skyFluxVisitStatisticMetric: SkyFluxStatisticMetric - atools.skyFluxVisitStatisticMetric.applyContext: VisitContext + atools.skyFluxVisitStatisticMetric.context: 'visit' atools.skySourceSkyPlot: SkySourceSkyPlot atools.skySourceHistPlot: SkySourceHistPlot python: | from lsst.analysis.tools.atools import * - from lsst.analysis.tools.contexts import * diff --git a/python/lsst/analysis/tools/atools/astrometryWithReference.py b/python/lsst/analysis/tools/atools/astrometryWithReference.py index cf43977c5..6ad1ecf83 100644 --- a/python/lsst/analysis/tools/atools/astrometryWithReference.py +++ b/python/lsst/analysis/tools/atools/astrometryWithReference.py @@ -21,13 +21,14 @@ from __future__ import annotations __all__ = ( + "CoordinateConfig", "TargetRefCatDeltaRAScatterPlot", "TargetRefCatDeltaDecScatterPlot", "TargetRefCatDeltaRASkyPlot", "TargetRefCatDeltaDecSkyPlot", ) -from lsst.pex.config import Field +from lsst.pex.config import ChoiceField, Config, Field from ..actions.plot.scatterplotWithTwoHists import ScatterPlotStatsAction, ScatterPlotWithTwoHists from ..actions.plot.skyPlot import SkyPlot @@ -41,10 +42,19 @@ VectorSelector, ) from ..interfaces import AnalysisTool +from .coaddVisit import CoaddVisitConfig from .genericPrep import CoaddPrep, VisitPrep -class TargetRefCatDelta(AnalysisTool): +class CoordinateConfig(Config): + coordinate = ChoiceField[str]( + doc="The name of the sky coordinate", + allowed={"RA": "Right Ascension", "Dec": "Declination"}, + optional=False, + ) + + +class TargetRefCatDelta(AnalysisTool, CoaddVisitConfig, CoordinateConfig): """Plot the difference in milliseconds between a target catalog and a reference catalog for the coordinate set in `setDefaults`. """ @@ -53,37 +63,42 @@ class TargetRefCatDelta(AnalysisTool): doc="Does this AnalysisTool support band as a name parameter", default=True ) - def coaddContext(self) -> None: - self.prep = CoaddPrep() - self.process.buildActions.starSelector.vectorKey = "{band}_extendedness" - self.process.buildActions.mags = MagColumnNanoJansky(vectorKey="{band}_psfFlux") - self.process.filterActions.psfFlux = DownselectVector( - vectorKey="{band}_psfFlux", selector=VectorSelector(vectorKey="starSelector") - ) - self.process.filterActions.psfFluxErr = DownselectVector( - vectorKey="{band}_psfFluxErr", selector=VectorSelector(vectorKey="starSelector") - ) - - def visitContext(self) -> None: - self.parameterizedBand = False - self.prep = VisitPrep() - self.process.buildActions.starSelector.vectorKey = "extendedness" - self.process.buildActions.mags = MagColumnNanoJansky(vectorKey="psfFlux") - self.process.filterActions.psfFlux = DownselectVector( - vectorKey="psfFlux", selector=VectorSelector(vectorKey="starSelector") - ) - self.process.filterActions.psfFluxErr = DownselectVector( - vectorKey="psfFluxErr", selector=VectorSelector(vectorKey="starSelector") + def finalize(self): + AnalysisTool().finalize() + if self.context == "coadd": + self.prep = CoaddPrep() + self.process.buildActions.starSelector.vectorKey = "{band}_extendedness" + self.process.buildActions.mags = MagColumnNanoJansky(vectorKey="{band}_psfFlux") + self.process.filterActions.psfFlux = DownselectVector( + vectorKey="{band}_psfFlux", selector=VectorSelector(vectorKey="starSelector") + ) + self.process.filterActions.psfFluxErr = DownselectVector( + vectorKey="{band}_psfFluxErr", selector=VectorSelector(vectorKey="starSelector") + ) + elif self.context == "visit": + self.parameterizedBand = False + self.prep = VisitPrep() + self.process.buildActions.starSelector.vectorKey = "extendedness" + self.process.buildActions.mags = MagColumnNanoJansky(vectorKey="psfFlux") + self.process.filterActions.psfFlux = DownselectVector( + vectorKey="psfFlux", selector=VectorSelector(vectorKey="starSelector") + ) + self.process.filterActions.psfFluxErr = DownselectVector( + vectorKey="psfFluxErr", selector=VectorSelector(vectorKey="starSelector") + ) + else: + raise ValueError(f"Unsupported {self.context=}") + + coordStr = self.coordinate.lower() + self.process.buildActions.astromDiff = AstromDiff( + col1=f"coord_{coordStr}_target", col2=f"coord_{coordStr}_ref" ) + self.produce.plot.yAxisLabel = f"${self.coordinate}_{{target}} - {self.coordinate}_{{ref}}$ (marcsec)" - def setDefaults(self, coordinate): + def setDefaults(self): super().setDefaults() self.process.buildActions.starSelector = StarSelector() - coordStr = coordinate.lower() - self.process.buildActions.astromDiff = AstromDiff( - col1=f"coord_{coordStr}_target", col2=f"coord_{coordStr}_ref" - ) self.process.filterActions.xStars = DownselectVector( vectorKey="mags", selector=VectorSelector(vectorKey="starSelector") @@ -101,7 +116,8 @@ def setDefaults(self, coordinate): self.produce.plot.plotTypes = ["stars"] self.produce.plot.xAxisLabel = "PSF Magnitude (mag)" - self.produce.plot.yAxisLabel = f"${coordinate}_{{target}} - {coordinate}_{{ref}}$ (marcsec)" + # Placeholder + self.produce.plot.yAxisLabel = "" self.produce.plot.magLabel = "PSF Magnitude (mag)" @@ -111,7 +127,8 @@ class TargetRefCatDeltaRAScatterPlot(TargetRefCatDelta): """ def setDefaults(self): - super().setDefaults(coordinate="RA") + super().setDefaults() + self.coordinate = "RA" class TargetRefCatDeltaDecScatterPlot(TargetRefCatDelta): @@ -120,10 +137,11 @@ class TargetRefCatDeltaDecScatterPlot(TargetRefCatDelta): """ def setDefaults(self): - super().setDefaults(coordinate="Dec") + super().setDefaults() + self.coordinate = "Dec" -class TargetRefCatDeltaSkyPlot(AnalysisTool): +class TargetRefCatDeltaSkyPlot(AnalysisTool, CoaddVisitConfig, CoordinateConfig): """Base class for plotting the RA/Dec distribution of stars, with the difference between the RA or Dec of the target and reference catalog as the color. @@ -133,26 +151,32 @@ class TargetRefCatDeltaSkyPlot(AnalysisTool): doc="Does this AnalysisTool support band as a name parameter", default=True ) - def coaddContext(self) -> None: - self.prep = CoaddPrep() + def finalize(self): + AnalysisTool().finalize() + if self.context == "coadd": + self.prep = CoaddPrep() - self.process.buildActions.starStatMask = SnSelector() - self.process.buildActions.starStatMask.fluxType = "{band}_psfFlux" + self.process.buildActions.starStatMask = SnSelector() + self.process.buildActions.starStatMask.fluxType = "{band}_psfFlux" + elif self.context == "visit": + self.parameterizedBand = False + self.prep = VisitPrep() - def visitContext(self) -> None: - self.parameterizedBand = False - self.prep = VisitPrep() - - self.process.buildActions.starStatMask = SnSelector() - self.process.buildActions.starStatMask.fluxType = "psfFlux" - - def setDefaults(self, coordinate): - super().setDefaults() + self.process.buildActions.starStatMask = SnSelector() + self.process.buildActions.starStatMask.fluxType = "psfFlux" + else: + raise ValueError(f"Unsupported {self.context=}") - coordStr = coordinate.lower() + coordStr = self.coordinate.lower() self.process.buildActions.zStars = AstromDiff( col1=f"coord_{coordStr}_target", col2=f"coord_{coordStr}_ref" ) + self.produce.plotName = f"astromDiffSky_{self.coordinate}" + self.produce.zAxisLabel = f"${self.coordinate}_{{target}} - {self.coordinate}_{{ref}}$ (marcsec)" + + def setDefaults(self): + super().setDefaults() + self.process.buildActions.xStars = LoadVector() self.process.buildActions.xStars.vectorKey = "coord_ra_target" self.process.buildActions.yStars = LoadVector() @@ -160,10 +184,11 @@ def setDefaults(self, coordinate): self.produce = SkyPlot() self.produce.plotTypes = ["stars"] - self.produce.plotName = f"astromDiffSky_{coordinate}" self.produce.xAxisLabel = "R.A. (degrees)" self.produce.yAxisLabel = "Dec. (degrees)" - self.produce.zAxisLabel = f"${coordinate}_{{target}} - {coordinate}_{{ref}}$ (marcsec)" + # Placeholders + self.produce.zAxisLabel = "" + self.produce.plotName = "" self.produce.plotOutlines = False @@ -173,7 +198,8 @@ class TargetRefCatDeltaRASkyPlot(TargetRefCatDeltaSkyPlot): """ def setDefaults(self): - super().setDefaults(coordinate="RA") + super().setDefaults() + self.coordinate = "RA" class TargetRefCatDeltaDecSkyPlot(TargetRefCatDeltaSkyPlot): @@ -182,4 +208,5 @@ class TargetRefCatDeltaDecSkyPlot(TargetRefCatDeltaSkyPlot): """ def setDefaults(self): - super().setDefaults(coordinate="Dec") + super().setDefaults() + self.coordinate = "Dec" diff --git a/python/lsst/analysis/tools/atools/coaddVisit.py b/python/lsst/analysis/tools/atools/coaddVisit.py new file mode 100644 index 000000000..11ce3b747 --- /dev/null +++ b/python/lsst/analysis/tools/atools/coaddVisit.py @@ -0,0 +1,35 @@ +# This file is part of analysis_tools. +# +# 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 . + +from lsst.pex.config import ChoiceField, Config + +__all__ = ("CoaddVisitConfig",) + + +class CoaddVisitConfig(Config): + """Config for tools that can be applied in coadd and visit contexts.""" + + context: ChoiceField = ChoiceField( + doc="The analysis context for this class", + dtype=str, + allowed={"coadd": "Coadded images", "visit": "Single visit images"}, + optional=False, + ) diff --git a/python/lsst/analysis/tools/atools/diffMatched.py b/python/lsst/analysis/tools/atools/diffMatched.py index a3e12277c..e1038aeff 100644 --- a/python/lsst/analysis/tools/atools/diffMatched.py +++ b/python/lsst/analysis/tools/atools/diffMatched.py @@ -197,26 +197,30 @@ class MatchedRefCoaddDiffMagTool(MatchedRefCoaddToolBase): The default model flux is cModel. """ - def matchedRefDiffContext(self): - self.process.buildActions.diff = SubtractVector( - actionA=MagColumnNanoJansky( - vectorKey=self.process.buildActions.fluxes_meas.vectorKey, returnMillimags=True - ), - actionB=DivideVector( - actionA=self.process.buildActions.mags_ref, - # To convert to mmag - actionB=ConstantValue(value=1e-3), - ), - ) - - def matchedRefChiContext(self): - self.process.buildActions.diff = DivideVector( - actionA=SubtractVector( - actionA=LoadVector(vectorKey=self.process.buildActions.fluxes_meas.vectorKey), - actionB=LoadVector(vectorKey=self.process.buildActions.fluxes_ref.vectorKey), - ), - actionB=LoadVector(vectorKey=f"{self.process.buildActions.fluxes_meas.vectorKey}Err"), - ) + def finalize(self): + super().finalize() + if not hasattr(self.process.buildActions, "diff"): + if self.context == "diff": + self.process.buildActions.diff = SubtractVector( + actionA=MagColumnNanoJansky( + vectorKey=self.process.buildActions.fluxes_meas.vectorKey, returnMillimags=True + ), + actionB=DivideVector( + actionA=self.process.buildActions.mags_ref, + # To convert to mmag + actionB=ConstantValue(value=1e-3), + ), + ) + elif self.context == "chi": + self.process.buildActions.diff = DivideVector( + actionA=SubtractVector( + actionA=LoadVector(vectorKey=self.process.buildActions.fluxes_meas.vectorKey), + actionB=LoadVector(vectorKey=self.process.buildActions.fluxes_ref.vectorKey), + ), + actionB=LoadVector(vectorKey=f"{self.process.buildActions.fluxes_meas.vectorKey}Err"), + ) + else: + raise ValueError(f"Unrecognized {self.context=}") def setDefaults(self): super().setDefaults() @@ -237,20 +241,19 @@ def setDefaults(self): class MatchedRefCoaddCModelFluxMetric(MatchedRefCoaddDiffMagTool, MatchedRefCoaddMetric): """Metric for diffs between reference and CModel coadd mags.""" - def matchedRefDiffContext(self): - super().matchedRefDiffContext() - self.unit = "mmag" - self.name_prefix = "photom_mag_cModelFlux_{name_class}_diff_" - self.produce.metric.units = self.configureMetrics() - - def matchedRefChiContext(self): - super().matchedRefChiContext() - self.unit = "" - self.name_prefix = "photom_mag_cModelFlux_{name_class}_chi_" - self.produce.metric.units = self.configureMetrics() - - def setDefaults(self): - super().setDefaults() + def finalize(self): + super().finalize() + if not hasattr(self.process.metric, "units"): + if self.context == "diff": + self.unit = "mmag" + self.name_prefix = "photom_mag_cModelFlux_{name_class}_diff_" + self.produce.metric.units = self.configureMetrics() + elif self.context == "chi": + self.unit = "" + self.name_prefix = "photom_mag_cModelFlux_{name_class}_chi_" + self.produce.metric.units = self.configureMetrics() + else: + raise ValueError(f"Unrecognized {self.context=}") class MatchedRefCoaddDiffPositionTool(MatchedRefCoaddToolBase): @@ -265,30 +268,26 @@ class MatchedRefCoaddDiffPositionTool(MatchedRefCoaddToolBase): optional=False, ) - # TODO: Determine if this can be put back into setDefaults w/o this: - # lsst.pex.config.config.FieldValidationError: - # Field 'process.buildActions.pos_meas.vectorKey' failed validation: - # Required value cannot be None - def _setPos(self): - self.process.buildActions.pos_meas = LoadVector(vectorKey=self.variable) - self.process.buildActions.pos_ref = LoadVector(vectorKey=f"refcat_{self.variable}") - - def matchedRefDiffContext(self): - self._setPos() - self.process.buildActions.diff = SubtractVector( - actionA=self.process.buildActions.pos_meas, - actionB=self.process.buildActions.pos_ref, - ) - - def matchedRefChiContext(self): - self._setPos() - self.process.buildActions.diff = DivideVector( - actionA=SubtractVector( - actionA=self.process.buildActions.pos_meas, - actionB=self.process.buildActions.pos_ref, - ), - actionB=LoadVector(vectorKey=f"{self.process.buildActions.pos_meas.vectorKey}Err"), - ) + def finalize(self): + super().finalize() + if not hasattr(self.process.buildActions, "pos_meas"): + self.process.buildActions.pos_meas = LoadVector(vectorKey=self.variable) + self.process.buildActions.pos_ref = LoadVector(vectorKey=f"refcat_{self.variable}") + if self.context == "diff": + self.process.buildActions.diff = SubtractVector( + actionA=self.process.buildActions.pos_meas, + actionB=self.process.buildActions.pos_ref, + ) + elif self.context == "chi": + self.process.buildActions.diff = DivideVector( + actionA=SubtractVector( + actionA=self.process.buildActions.pos_meas, + actionB=self.process.buildActions.pos_ref, + ), + actionB=LoadVector(vectorKey=f"{self.process.buildActions.pos_meas.vectorKey}Err"), + ) + else: + raise ValueError(f"Unrecognized {self.context=}") def setDefaults(self): super().setDefaults() @@ -307,20 +306,19 @@ def setDefaults(self): class MatchedRefCoaddPositionMetric(MatchedRefCoaddDiffPositionTool, MatchedRefCoaddMetric): """Metric for diffs between reference and base coadd centroids.""" - def matchedRefDiffContext(self): - super().matchedRefDiffContext() - self.unit = "pix" - self.name_prefix = f"astrom_{self.variable}_{{name_class}}_diff_" - self.produce.metric.units = self.configureMetrics() - - def matchedRefChiContext(self): - super().matchedRefChiContext() - self.unit = "" - self.name_prefix = f"astrom_{self.variable}_{{name_class}}_diff_" - self.produce.metric.units = self.configureMetrics() - - def setDefaults(self): - super().setDefaults() + def finalize(self): + super().finalize() + if not hasattr(self.process.metric, "units"): + if self.context == "diff": + self.unit = "pix" + self.name_prefix = f"astrom_{self.variable}_{{name_class}}_diff_" + self.produce.metric.units = self.configureMetrics() + elif self.context == "chi": + self.unit = "" + self.name_prefix = f"astrom_{self.variable}_{{name_class}}_diff_" + self.produce.metric.units = self.configureMetrics() + else: + raise ValueError(f"Unrecognized {self.context=}") class MatchedRefCoaddPlot(AnalysisTool): @@ -360,26 +358,24 @@ def setDefaults(self): class MatchedRefCoaddCModelFluxPlot(MatchedRefCoaddCModelPlot, MatchedRefCoaddDiffMagTool): - def matchedRefDiffContext(self): - super().matchedRefDiffContext() - self.produce.plot.yAxisLabel = "cModel - Reference mmag" - - def matchedRefChiContext(self): - super().matchedRefChiContext() - self.produce.plot.yAxisLabel = "chi = (cModel - Reference mag)/error" - - def setDefaults(self): - super().setDefaults() + def finalize(self): + MatchedRefCoaddCModelPlot(self).finalize() + MatchedRefCoaddDiffMagTool(self).finalize() + if self.context == "diff": + self.produce.plot.yAxisLabel = "cModel - Reference mmag" + elif self.context == "chi": + self.produce.plot.yAxisLabel = "chi = (cModel - Reference mag)/error" + else: + raise ValueError(f"Unrecognized {self.context=}") class MatchedRefCoaddPositionPlot(MatchedRefCoaddCModelPlot, MatchedRefCoaddDiffPositionTool): - def matchedRefDiffContext(self): - super().matchedRefDiffContext() - self.produce.plot.yAxisLabel = f"{self.variable} position (pix)" - - def matchedRefChiContext(self): - super().matchedRefChiContext() - self.produce.plot.yAxisLabel = f"chi = (slot - Reference {self.variable} position)/error" - - def setDefaults(self): - super().setDefaults() + def finalize(self): + MatchedRefCoaddCModelPlot(self).finalize() + MatchedRefCoaddDiffPositionTool(self).finalize() + if self.context == "diff": + self.produce.plot.yAxisLabel = f"{self.variable} position (pix)" + elif self.context == "chi": + self.produce.plot.yAxisLabel = f"chi = (slot - Reference {self.variable} position)/error" + else: + raise ValueError(f"Unrecognized {self.context=}") diff --git a/python/lsst/analysis/tools/atools/numericalValidity.py b/python/lsst/analysis/tools/atools/numericalValidity.py index 34bb8ca1b..c11d652f5 100644 --- a/python/lsst/analysis/tools/atools/numericalValidity.py +++ b/python/lsst/analysis/tools/atools/numericalValidity.py @@ -27,9 +27,10 @@ from ..actions.scalar import FracInRange, FracNan from ..actions.vector import LoadVector from ..interfaces import AnalysisTool +from .coaddVisit import CoaddVisitConfig -class ValidFracColumnMetric(AnalysisTool): +class ValidFracColumnMetric(AnalysisTool, CoaddVisitConfig): """Calculate the fraction of values in a column that have valid numerical values (i.e., not NaN), and that fall within the specified "reasonable" range for the values. @@ -37,25 +38,26 @@ class ValidFracColumnMetric(AnalysisTool): vectorKey = Field[str](doc="Key of column to validate", default="psfFlux") - def visitContext(self) -> None: - self.process.buildActions.loadVector = LoadVector() - self.process.buildActions.loadVector.vectorKey = f"{self.vectorKey}" - self._setActions(f"{self.vectorKey}") + def _setActions(self) -> None: + if self.context == "coadd": + name = "{band}_" + f"{self.vectorKey}" + self.process.buildActions.loadVector = LoadVector() + self.process.buildActions.loadVector.vectorKey = "{band}_" + f"{self.vectorKey}" - def coaddContext(self) -> None: - self.process.buildActions.loadVector = LoadVector() - self.process.buildActions.loadVector.vectorKey = "{band}_" + f"{self.vectorKey}" - self._setActions("{band}_" + f"{self.vectorKey}") + # Need to pass a mapping of new names so the default names get the + # band prepended. Otherwise, each subsequent band's metric will + # overwrite the current one. + self.produce.metric.newNames = { + "validFracColumn": "{band}_validFracColumn", + "nanFracColumn": "{band}_nanFracColumn", + } + elif self.context == "visit": + name = f"{self.vectorKey}" + self.process.buildActions.loadVector = LoadVector() + self.process.buildActions.loadVector.vectorKey = f"{self.vectorKey}" + else: + raise ValueError(f"Unsupported {self.context=}") - # Need to pass a mapping of new names so the default names get the - # band prepended. Otherwise, each subsequent band's metric will - # overwrite the current one. - self.produce.newNames = { # type: ignore - "validFracColumn": "{band}_validFracColumn", - "nanFracColumn": "{band}_nanFracColumn", - } - - def _setActions(self, name: str) -> None: # The default flux limits of 1e-1 < flux < 1e9 correspond to a # magnitude range of 34 < mag < 9 (i.e., reasonably well-measured) # objects should all be within this range). @@ -77,3 +79,9 @@ def setDefaults(self): "validFracColumn": "percent", "nanFracColumn": "percent", } + + def finalize(self): + AnalysisTool(self).finalize() + CoaddVisitConfig(self).finalize() + if not hasattr(self.process.buildActions, "loadVector"): + self._setActions() diff --git a/python/lsst/analysis/tools/atools/skyFluxStatisticMetrics.py b/python/lsst/analysis/tools/atools/skyFluxStatisticMetrics.py index 4320676e2..3a420a0bc 100644 --- a/python/lsst/analysis/tools/atools/skyFluxStatisticMetrics.py +++ b/python/lsst/analysis/tools/atools/skyFluxStatisticMetrics.py @@ -27,46 +27,52 @@ from ..actions.scalar.scalarActions import MeanAction, MedianAction, SigmaMadAction, StdevAction from ..actions.vector.selectors import SkyObjectSelector, SkySourceSelector from ..interfaces import AnalysisTool +from .coaddVisit import CoaddVisitConfig -class SkyFluxStatisticMetric(AnalysisTool): - """Calculate sky flux statistics. This uses the 9-pixel aperture flux for - sky sources/objects, and returns multiple statistics on the measured - fluxes. Note that either visitContext (measurement on sourceTable) or - coaddContext (measurement on objectTable) must be specified. +class SkyFluxStatisticMetric(AnalysisTool, CoaddVisitConfig): + """Calculate sky flux statistics. + + This uses the 9-pixel aperture flux for sky sources/objects, and returns + multiple statistics on the measured fluxes. Note that self.context must be + set to "visit" (for measurement on sourceTables) or "coadd" + (for measurement on objectTables). """ fluxType = Field[str](doc="Key to use to retrieve flux column", default="ap09Flux") - def visitContext(self) -> None: - self.prep.selectors.skySourceSelector = SkySourceSelector() - - def coaddContext(self) -> None: - self.prep.selectors.skyObjectSelector = SkyObjectSelector() - self.prep.selectors.skyObjectSelector.bands = [] - self.fluxType = f"{{band}}_{self.fluxType}" + def _setActions(self) -> None: + if self.context == "coadd": + name = f"{{band}}_{self.fluxType}" + self.prep.selectors.skyObjectSelector = SkyObjectSelector() + self.prep.selectors.skyObjectSelector.bands = [] - # Need to pass a mapping of new names so the default names get the - # band prepended. Otherwise, each subsequent band's metric will - # overwrite the current one (e.g., running with g, r bands without - # this, you would get "meanSky," "meanSky"; with it: "g_meanSky," - # "r_meanSky"). - self.produce.metric.newNames = { # type: ignore - "medianSky": "{band}_medianSky", - "meanSky": "{band}_meanSky", - "stdevSky": "{band}_stdevSky", - "sigmaMADSky": "{band}_sigmaMADSky", - } + # Need to pass a mapping of new names so the default names get the + # band prepended. Otherwise, each subsequent band's metric will + # overwrite the current one (e.g., running with g, r bands without + # this, you would get "meanSky," "meanSky"; with it: "g_meanSky," + # "r_meanSky"). + self.produce.metric.newNames = { + "medianSky": "{band}_medianSky", + "meanSky": "{band}_meanSky", + "stdevSky": "{band}_stdevSky", + "sigmaMADSky": "{band}_sigmaMADSky", + } + elif self.context == "visit": + name = f"{self.fluxType}" + self.prep.selectors.skySourceSelector = SkySourceSelector() + else: + raise ValueError(f"Unsupported {self.context=}") - def _setActions(self, name: str) -> None: self.process.calculateActions.medianSky = MedianAction(vectorKey=name) self.process.calculateActions.meanSky = MeanAction(vectorKey=name) self.process.calculateActions.stdevSky = StdevAction(vectorKey=name) self.process.calculateActions.sigmaMADSky = SigmaMadAction(vectorKey=name) def finalize(self) -> None: - super().finalize() - self._setActions(self.fluxType) + AnalysisTool(self).finalize() + if not hasattr(self.process.calculateActions, "medianSky"): + self._setActions() def setDefaults(self): super().setDefaults() diff --git a/python/lsst/analysis/tools/contexts/_contexts.py b/python/lsst/analysis/tools/contexts/_contexts.py index 68c7d33e0..29db4f6c3 100644 --- a/python/lsst/analysis/tools/contexts/_contexts.py +++ b/python/lsst/analysis/tools/contexts/_contexts.py @@ -24,34 +24,10 @@ be a subclass of `Context`, and should contain a description of what the context is for as it's docstring. """ -__all__ = ("VisitContext", "CoaddContext", "MatchedRefDiffContext", "MatchedRefChiContext") +__all__ = ("ExampleContext",) from ._baseContext import Context -class VisitContext(Context): - """A context which indicates `AnalysisAction`s are being run in the context - of visit level data. - """ - - pass - - -class CoaddContext(Context): - """A context which indicates `AnalysisAction`s are being run in the context - of coadd level data. - """ - - pass - - -class MatchedRefDiffContext(Context): - """A context which indicates `AnalysisAction`s are computing differences - between matches to reference objects. - """ - - -class MatchedRefChiContext(Context): - """A context which indicates `AnalysisAction`s are computing error-scaled - differences between matches to reference objects. - """ +class ExampleContext(Context): + """An example context which should not actually be used.""" diff --git a/python/lsst/analysis/tools/tasks/refCatObjectAnalysis.py b/python/lsst/analysis/tools/tasks/refCatObjectAnalysis.py index abd59ba79..a472e86ce 100644 --- a/python/lsst/analysis/tools/tasks/refCatObjectAnalysis.py +++ b/python/lsst/analysis/tools/tasks/refCatObjectAnalysis.py @@ -30,7 +30,6 @@ TargetRefCatDeltaRAScatterPlot, TargetRefCatDeltaRASkyPlot, ) -from ..contexts import CoaddContext from ..interfaces import AnalysisBaseConfig, AnalysisBaseConnections, AnalysisPipelineTask @@ -51,23 +50,13 @@ class RefCatObjectAnalysisConnections( class RefCatObjectAnalysisConfig(AnalysisBaseConfig, pipelineConnections=RefCatObjectAnalysisConnections): def setDefaults(self): super().setDefaults() + kwargs = {"context": "coadd", "parameterizedBand": True} # set plots to run - self.plots.astromDiffRAScatterPlot = TargetRefCatDeltaRAScatterPlot() - self.plots.astromDiffRAScatterPlot.parameterizedBand = True - self.plots.astromDiffRAScatterPlot.applyContext(CoaddContext) - - self.plots.astromDiffDecScatterPlot = TargetRefCatDeltaDecScatterPlot() - self.plots.astromDiffDecScatterPlot.parameterizedBand = True - self.plots.astromDiffDecScatterPlot.applyContext(CoaddContext) - - self.plots.astromDiffRASkyPlot = TargetRefCatDeltaRASkyPlot() - self.plots.astromDiffRASkyPlot.parameterizedBand = True - self.plots.astromDiffRASkyPlot.applyContext(CoaddContext) - - self.plots.astromDiffDecSkyPlot = TargetRefCatDeltaDecSkyPlot() - self.plots.astromDiffDecSkyPlot.parameterizedBand = True - self.plots.astromDiffDecSkyPlot.applyContext(CoaddContext) + self.plots.astromDiffRAScatterPlot = TargetRefCatDeltaRAScatterPlot(**kwargs) + self.plots.astromDiffDecScatterPlot = TargetRefCatDeltaDecScatterPlot(**kwargs) + self.plots.astromDiffRASkyPlot = TargetRefCatDeltaRASkyPlot(**kwargs) + self.plots.astromDiffDecSkyPlot = TargetRefCatDeltaDecSkyPlot(**kwargs) # set metrics to run - none so far diff --git a/python/lsst/analysis/tools/tasks/refCatSourceAnalysis.py b/python/lsst/analysis/tools/tasks/refCatSourceAnalysis.py index 7466b1696..4d796589b 100644 --- a/python/lsst/analysis/tools/tasks/refCatSourceAnalysis.py +++ b/python/lsst/analysis/tools/tasks/refCatSourceAnalysis.py @@ -30,7 +30,6 @@ TargetRefCatDeltaRAScatterPlot, TargetRefCatDeltaRASkyPlot, ) -from ..contexts import VisitContext from ..interfaces import AnalysisBaseConfig, AnalysisBaseConnections, AnalysisPipelineTask @@ -51,19 +50,13 @@ class RefCatSourceAnalysisConnections( class RefCatSourceAnalysisConfig(AnalysisBaseConfig, pipelineConnections=RefCatSourceAnalysisConnections): def setDefaults(self): super().setDefaults() + kwargs = {"context": "visit"} # set plots to run - self.plots.astromDiffRAScatterPlot = TargetRefCatDeltaRAScatterPlot() - self.plots.astromDiffRAScatterPlot.applyContext(VisitContext) - - self.plots.astromDiffDecScatterPlot = TargetRefCatDeltaDecScatterPlot() - self.plots.astromDiffDecScatterPlot.applyContext(VisitContext) - - self.plots.astromDiffRASkyPlot = TargetRefCatDeltaRASkyPlot() - self.plots.astromDiffRASkyPlot.applyContext(VisitContext) - - self.plots.astromDiffDecSkyPlot = TargetRefCatDeltaDecSkyPlot() - self.plots.astromDiffDecSkyPlot.applyContext(VisitContext) + self.plots.astromDiffRAScatterPlot = TargetRefCatDeltaRAScatterPlot(**kwargs) + self.plots.astromDiffDecScatterPlot = TargetRefCatDeltaDecScatterPlot(**kwargs) + self.plots.astromDiffRASkyPlot = TargetRefCatDeltaRASkyPlot(**kwargs) + self.plots.astromDiffDecSkyPlot = TargetRefCatDeltaDecSkyPlot(**kwargs) # set metrics to run - none so far diff --git a/tests/test_metrics.py b/tests/test_metrics.py index 5b53b2cb5..4721dddb9 100644 --- a/tests/test_metrics.py +++ b/tests/test_metrics.py @@ -29,19 +29,17 @@ MatchedRefCoaddMetric, MatchedRefCoaddPositionMetric, ) -from lsst.analysis.tools.contexts import MatchedRefChiContext, MatchedRefDiffContext class TestDiffMatched(TestCase): def setUp(self) -> None: super().setUp() self.band_default = "analysisTools" - self.contexts = (MatchedRefChiContext, MatchedRefDiffContext) + self.contexts = ("chi", "diff") def _testMatchedRefCoaddMetricDerived(self, type_metric: type[MatchedRefCoaddMetric], **kwargs): for context in self.contexts: - tester = type_metric(**kwargs) - tester.applyContext(context) + tester = type_metric(**kwargs, context=context) tester.finalize() keys = list(k[0] for k in tester.getInputSchema())